From 9faef65cccd78c3ae66d7b96b1257768f5e395b7 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 3 Dec 2013 17:32:02 -0800 Subject: [PATCH] first cut at splitting out octree base classes --- animation-server/CMakeLists.txt | 3 + assignment-client/CMakeLists.txt | 1 + interface/CMakeLists.txt | 1 + interface/src/Application.cpp | 65 +- interface/src/Application.h | 2 +- interface/src/Menu.cpp | 4 +- interface/src/VoxelImporter.cpp | 6 +- interface/src/VoxelSystem.cpp | 393 ++-- interface/src/VoxelSystem.h | 73 +- interface/src/avatar/AvatarVoxelSystem.cpp | 2 +- interface/src/renderer/FBXReader.cpp | 11 +- interface/src/ui/LodToolsDialog.cpp | 4 +- interface/src/ui/VoxelStatsDialog.cpp | 12 +- libraries/avatars/CMakeLists.txt | 1 + libraries/octree/CMakeLists.txt | 27 + libraries/{voxels => octree}/src/AABox.cpp | 0 libraries/{voxels => octree}/src/AABox.h | 0 .../{voxels => octree}/src/CoverageMap.cpp | 18 +- .../{voxels => octree}/src/CoverageMap.h | 18 +- .../{voxels => octree}/src/CoverageMapV2.cpp | 4 +- .../{voxels => octree}/src/CoverageMapV2.h | 8 +- .../src/JurisdictionListener.cpp | 0 .../src/JurisdictionListener.h | 0 .../src/JurisdictionMap.cpp | 2 +- .../{voxels => octree}/src/JurisdictionMap.h | 0 .../src/JurisdictionSender.cpp | 0 .../src/JurisdictionSender.h | 2 +- libraries/octree/src/Octree.cpp | 1512 ++++++++++++ libraries/octree/src/Octree.h | 313 +++ libraries/octree/src/OctreeConstants.h | 47 + .../src/OctreeElement.cpp} | 508 ++-- .../src/OctreeElement.h} | 140 +- .../src/OctreeElementBag.cpp} | 56 +- libraries/octree/src/OctreeElementBag.h | 47 + libraries/octree/src/OctreePacketData.cpp | 325 +++ libraries/octree/src/OctreePacketData.h | 182 ++ .../src/OctreeProjectedPolygon.cpp} | 50 +- .../src/OctreeProjectedPolygon.h} | 22 +- libraries/octree/src/OctreeQuery.cpp | 152 ++ libraries/octree/src/OctreeQuery.h | 115 + libraries/octree/src/OctreeSceneStats.cpp | 842 +++++++ libraries/octree/src/OctreeSceneStats.h | 276 +++ libraries/{voxels => octree}/src/Plane.cpp | 0 libraries/{voxels => octree}/src/Plane.h | 0 .../{voxels => octree}/src/ViewFrustum.cpp | 8 +- .../{voxels => octree}/src/ViewFrustum.h | 4 +- .../particle-server/CMakeLists.txt.disabled | 42 + .../src/ParticlePersistThread.cpp | 86 + .../src/ParticlePersistThread.h | 44 + .../src/ParticleReceiverData.cpp | 276 +++ .../src/ParticleReceiverData.h | 123 + .../src/ParticleSendThread.cpp | 551 +++++ .../particle-server/src/ParticleSendThread.h | 47 + .../particle-server/src/ParticleServer.cpp | 802 +++++++ .../particle-server/src/ParticleServer.h | 91 + .../src/ParticleServerConsts.h | 28 + .../src/ParticleServerPacketProcessor.cpp | 222 ++ .../src/ParticleServerPacketProcessor.h | 83 + libraries/particles/CMakeLists.txt | 28 + libraries/particles/src/ParticleTree.cpp | 1 + .../{voxels => shared}/src/GeometryUtil.cpp | 3 +- .../{voxels => shared}/src/GeometryUtil.h | 0 libraries/shared/src/SharedUtil.cpp | 31 +- libraries/shared/src/SharedUtil.h | 8 +- libraries/voxel-server-library/CMakeLists.txt | 6 +- .../src/VoxelNodeData.cpp | 14 +- .../voxel-server-library/src/VoxelNodeData.h | 4 +- .../src/VoxelPersistThread.cpp | 14 +- .../src/VoxelSendThread.cpp | 19 +- .../src/VoxelSendThread.h | 2 +- .../voxel-server-library/src/VoxelServer.cpp | 62 +- .../src/VoxelServerPacketProcessor.cpp | 2 +- libraries/voxels/CMakeLists.txt | 1 + libraries/voxels/src/VoxelConstants.h | 22 +- libraries/voxels/src/VoxelNodeBag.h | 47 - libraries/voxels/src/VoxelPacketData.cpp | 317 +-- libraries/voxels/src/VoxelPacketData.h | 149 +- libraries/voxels/src/VoxelQuery.cpp | 140 +- libraries/voxels/src/VoxelQuery.h | 94 +- libraries/voxels/src/VoxelSceneStats.cpp | 830 +------ libraries/voxels/src/VoxelSceneStats.h | 256 +- libraries/voxels/src/VoxelTree.cpp | 2088 ++--------------- libraries/voxels/src/VoxelTree.h | 300 +-- libraries/voxels/src/VoxelTreeElement.cpp | 225 ++ libraries/voxels/src/VoxelTreeElement.h | 86 + voxel-edit/CMakeLists.txt | 3 + voxel-edit/src/main.cpp | 141 +- 87 files changed, 7659 insertions(+), 4885 deletions(-) create mode 100644 libraries/octree/CMakeLists.txt rename libraries/{voxels => octree}/src/AABox.cpp (100%) rename libraries/{voxels => octree}/src/AABox.h (100%) rename libraries/{voxels => octree}/src/CoverageMap.cpp (96%) rename libraries/{voxels => octree}/src/CoverageMap.h (84%) rename libraries/{voxels => octree}/src/CoverageMapV2.cpp (98%) rename libraries/{voxels => octree}/src/CoverageMapV2.h (88%) rename libraries/{voxels => octree}/src/JurisdictionListener.cpp (100%) rename libraries/{voxels => octree}/src/JurisdictionListener.h (100%) rename libraries/{voxels => octree}/src/JurisdictionMap.cpp (99%) rename libraries/{voxels => octree}/src/JurisdictionMap.h (100%) rename libraries/{voxels => octree}/src/JurisdictionSender.cpp (100%) rename libraries/{voxels => octree}/src/JurisdictionSender.h (98%) create mode 100644 libraries/octree/src/Octree.cpp create mode 100644 libraries/octree/src/Octree.h create mode 100644 libraries/octree/src/OctreeConstants.h rename libraries/{voxels/src/VoxelNode.cpp => octree/src/OctreeElement.cpp} (70%) rename libraries/{voxels/src/VoxelNode.h => octree/src/OctreeElement.h} (65%) rename libraries/{voxels/src/VoxelNodeBag.cpp => octree/src/OctreeElementBag.cpp} (65%) create mode 100644 libraries/octree/src/OctreeElementBag.h create mode 100644 libraries/octree/src/OctreePacketData.cpp create mode 100644 libraries/octree/src/OctreePacketData.h rename libraries/{voxels/src/VoxelProjectedPolygon.cpp => octree/src/OctreeProjectedPolygon.cpp} (96%) rename libraries/{voxels/src/VoxelProjectedPolygon.h => octree/src/OctreeProjectedPolygon.h} (84%) create mode 100644 libraries/octree/src/OctreeQuery.cpp create mode 100644 libraries/octree/src/OctreeQuery.h create mode 100644 libraries/octree/src/OctreeSceneStats.cpp create mode 100644 libraries/octree/src/OctreeSceneStats.h rename libraries/{voxels => octree}/src/Plane.cpp (100%) rename libraries/{voxels => octree}/src/Plane.h (100%) rename libraries/{voxels => octree}/src/ViewFrustum.cpp (99%) rename libraries/{voxels => octree}/src/ViewFrustum.h (98%) create mode 100644 libraries/particle-server/CMakeLists.txt.disabled create mode 100644 libraries/particle-server/src/ParticlePersistThread.cpp create mode 100644 libraries/particle-server/src/ParticlePersistThread.h create mode 100644 libraries/particle-server/src/ParticleReceiverData.cpp create mode 100644 libraries/particle-server/src/ParticleReceiverData.h create mode 100644 libraries/particle-server/src/ParticleSendThread.cpp create mode 100644 libraries/particle-server/src/ParticleSendThread.h create mode 100644 libraries/particle-server/src/ParticleServer.cpp create mode 100644 libraries/particle-server/src/ParticleServer.h create mode 100644 libraries/particle-server/src/ParticleServerConsts.h create mode 100644 libraries/particle-server/src/ParticleServerPacketProcessor.cpp create mode 100644 libraries/particle-server/src/ParticleServerPacketProcessor.h create mode 100644 libraries/particles/CMakeLists.txt create mode 100644 libraries/particles/src/ParticleTree.cpp rename libraries/{voxels => shared}/src/GeometryUtil.cpp (99%) rename libraries/{voxels => shared}/src/GeometryUtil.h (100%) delete mode 100644 libraries/voxels/src/VoxelNodeBag.h create mode 100644 libraries/voxels/src/VoxelTreeElement.cpp create mode 100644 libraries/voxels/src/VoxelTreeElement.h diff --git a/animation-server/CMakeLists.txt b/animation-server/CMakeLists.txt index de04acd8f7..5a696afc10 100644 --- a/animation-server/CMakeLists.txt +++ b/animation-server/CMakeLists.txt @@ -20,6 +20,9 @@ setup_hifi_project(${TARGET_NAME} TRUE) include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) +# link in the hifi octree library +link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) + # link in the hifi voxels library link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR}) diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 900965e405..5ba5a1d4e4 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -24,6 +24,7 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(avatars ${TARGET_NAME} ${ROOT_DIR}) +link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(voxel-server-library ${TARGET_NAME} ${ROOT_DIR}) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index fda6862857..546761f36a 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -83,6 +83,7 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake) # link required hifi libraries link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) +link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(avatars ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR}) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b531473a1c..13b5726c42 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -247,7 +247,7 @@ Application::~Application() { _audio.shutdown(); - VoxelNode::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown + VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown delete Menu::getInstance(); delete _settings; @@ -307,16 +307,6 @@ void Application::initializeGL() { init(); qDebug( "Init() complete.\n" ); - // Check to see if the user passed in a command line option for randomizing colors - bool wantColorRandomizer = !arguments().contains("--NoColorRandomizer"); - - // Check to see if the user passed in a command line option for loading a local - // Voxel File. If so, load it now. - if (!_voxelsFilename.isEmpty()) { - _voxels.loadVoxelsFile(_voxelsFilename.constData(), wantColorRandomizer); - qDebug("Local Voxel File loaded.\n"); - } - // create thread for receipt of data via UDP if (_enableNetworkThread) { pthread_create(&_networkReceiveThread, NULL, networkReceive, NULL); @@ -1561,10 +1551,11 @@ struct SendVoxelsOperationArgs { const unsigned char* newBaseOctCode; }; -bool Application::sendVoxelsOperation(VoxelNode* node, void* extraData) { +bool Application::sendVoxelsOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; SendVoxelsOperationArgs* args = (SendVoxelsOperationArgs*)extraData; - if (node->isColored()) { - const unsigned char* nodeOctalCode = node->getOctalCode(); + if (voxel->isColored()) { + const unsigned char* nodeOctalCode = voxel->getOctalCode(); unsigned char* codeColorBuffer = NULL; int codeLength = 0; int bytesInCode = 0; @@ -1585,9 +1576,9 @@ bool Application::sendVoxelsOperation(VoxelNode* node, void* extraData) { } // copy the colors over - codeColorBuffer[bytesInCode + RED_INDEX] = node->getColor()[RED_INDEX]; - codeColorBuffer[bytesInCode + GREEN_INDEX] = node->getColor()[GREEN_INDEX]; - codeColorBuffer[bytesInCode + BLUE_INDEX] = node->getColor()[BLUE_INDEX]; + codeColorBuffer[bytesInCode + RED_INDEX] = voxel->getColor()[RED_INDEX]; + codeColorBuffer[bytesInCode + GREEN_INDEX] = voxel->getColor()[GREEN_INDEX]; + codeColorBuffer[bytesInCode + BLUE_INDEX] = voxel->getColor()[BLUE_INDEX]; getInstance()->_voxelEditSender.queueVoxelEditMessage(PACKET_TYPE_SET_VOXEL_DESTRUCTIVE, codeColorBuffer, codeAndColorLength); @@ -1604,7 +1595,7 @@ void Application::exportVoxels() { tr("Sparse Voxel Octree Files (*.svo)")); QByteArray fileNameAscii = fileNameString.toLocal8Bit(); const char* fileName = fileNameAscii.data(); - VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + VoxelTreeElement* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); if (selectedNode) { VoxelTree exportTree; _voxels.copySubTreeIntoNewTree(selectedNode, &exportTree, true); @@ -1635,12 +1626,12 @@ void Application::copyVoxels() { // switch to and clear the clipboard first... _sharedVoxelSystem.killLocalVoxels(); if (_sharedVoxelSystem.getTree() != &_clipboard) { - _clipboard.eraseAllVoxels(); + _clipboard.eraseAllOctreeElements(); _sharedVoxelSystem.changeTree(&_clipboard); } // then copy onto it if there is something to copy - VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + VoxelTreeElement* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); if (selectedNode) { _voxels.copySubTreeIntoNewTree(selectedNode, &_sharedVoxelSystem, true); } @@ -1663,7 +1654,7 @@ void Application::pasteVoxelsToOctalCode(const unsigned char* octalCodeDestinati void Application::pasteVoxels() { unsigned char* calculatedOctCode = NULL; - VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + VoxelTreeElement* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); // we only need the selected voxel to get the newBaseOctCode, which we can actually calculate from the // voxel size/position details. If we don't have an actual selectedNode then use the mouseVoxel to create a @@ -1702,7 +1693,7 @@ void Application::findAxisAlignment() { } void Application::nudgeVoxels() { - VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + VoxelTreeElement* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); if (!Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode) && selectedNode) { Menu::getInstance()->triggerOption(MenuOption::VoxelSelectMode); } @@ -1716,7 +1707,7 @@ void Application::nudgeVoxels() { // calculate nudgeVec glm::vec3 nudgeVec(_nudgeGuidePosition.x - _nudgeVoxel.x, _nudgeGuidePosition.y - _nudgeVoxel.y, _nudgeGuidePosition.z - _nudgeVoxel.z); - VoxelNode* nodeToNudge = _voxels.getVoxelAt(_nudgeVoxel.x, _nudgeVoxel.y, _nudgeVoxel.z, _nudgeVoxel.s); + VoxelTreeElement* nodeToNudge = _voxels.getVoxelAt(_nudgeVoxel.x, _nudgeVoxel.y, _nudgeVoxel.z, _nudgeVoxel.s); if (nodeToNudge) { _voxels.getTree()->nudgeSubTree(nodeToNudge, nudgeVec, _voxelEditSender); @@ -1749,7 +1740,7 @@ void Application::init() { _sharedVoxelSystemViewFrustum.calculate(); _sharedVoxelSystem.setViewFrustum(&_sharedVoxelSystemViewFrustum); - VoxelNode::removeUpdateHook(&_sharedVoxelSystem); + VoxelTreeElement::removeUpdateHook(&_sharedVoxelSystem); _sharedVoxelSystem.init(); VoxelTree* tmpTree = _sharedVoxelSystem.getTree(); @@ -2103,7 +2094,7 @@ void Application::updateHoverVoxels(float deltaTime, glm::vec3& mouseRayOrigin, // If we have clicked on a voxel, update it's color if (_isHoverVoxelSounding) { - VoxelNode* hoveredNode = _voxels.getVoxelAt(_hoverVoxel.x, _hoverVoxel.y, _hoverVoxel.z, _hoverVoxel.s); + VoxelTreeElement* hoveredNode = _voxels.getVoxelAt(_hoverVoxel.x, _hoverVoxel.y, _hoverVoxel.z, _hoverVoxel.s); if (hoveredNode) { float bright = _audio.getCollisionSoundMagnitude(); nodeColor clickColor = { 255 * bright + _hoverVoxelOriginalColor[0] * (1.f - bright), @@ -2629,7 +2620,7 @@ void Application::queryVoxels() { _voxelQuery.setCameraNearClip(_viewFrustum.getNearClip()); _voxelQuery.setCameraFarClip(_viewFrustum.getFarClip()); _voxelQuery.setCameraEyeOffsetPosition(_viewFrustum.getEyeOffsetPosition()); - _voxelQuery.setVoxelSizeScale(Menu::getInstance()->getVoxelSizeScale()); + _voxelQuery.setOctreeSizeScale(Menu::getInstance()->getVoxelSizeScale()); _voxelQuery.setBoundaryLevelAdjust(Menu::getInstance()->getBoundaryLevelAdjust()); unsigned char voxelQueryPacket[MAX_PACKET_SIZE]; @@ -2743,7 +2734,7 @@ void Application::queryVoxels() { } if (inView) { - _voxelQuery.setMaxVoxelPacketsPerSecond(perServerPPS); + _voxelQuery.setMaxOctreePacketsPerSecond(perServerPPS); } else if (unknownView) { if (wantExtraDebugging) { qDebug() << "no known jurisdiction for node " << *node << ", give it budget of " @@ -2767,9 +2758,9 @@ void Application::queryVoxels() { qDebug() << "Using regular camera position for node " << *node << "\n"; } } - _voxelQuery.setMaxVoxelPacketsPerSecond(perUnknownServer); + _voxelQuery.setMaxOctreePacketsPerSecond(perUnknownServer); } else { - _voxelQuery.setMaxVoxelPacketsPerSecond(0); + _voxelQuery.setMaxOctreePacketsPerSecond(0); } // set up the packet for sending... unsigned char* endOfVoxelQueryPacket = voxelQueryPacket; @@ -3452,7 +3443,7 @@ void Application::displayStats() { } // calculate server node totals - totalNodes += stats.getTotalVoxels(); + totalNodes += stats.getTotalElements(); totalInternal += stats.getTotalInternal(); totalLeaves += stats.getTotalLeaves(); } @@ -3479,9 +3470,9 @@ void Application::displayStats() { statsVerticalOffset += PELS_PER_LINE; drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str()); - unsigned long localTotal = VoxelNode::getNodeCount(); - unsigned long localInternal = VoxelNode::getInternalNodeCount(); - unsigned long localLeaves = VoxelNode::getLeafNodeCount(); + unsigned long localTotal = VoxelTreeElement::getNodeCount(); + unsigned long localInternal = VoxelTreeElement::getInternalNodeCount(); + unsigned long localLeaves = VoxelTreeElement::getLeafNodeCount(); QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' '); QString localInternalString = locale.toString((uint)localInternal); QString localLeavesString = locale.toString((uint)localLeaves); @@ -3498,7 +3489,7 @@ void Application::displayStats() { // Local Voxel Memory Usage voxelStats.str(""); voxelStats << - "Voxels Memory Nodes: " << VoxelNode::getTotalMemoryUsage() / 1000000.f << "MB " + "Voxels Memory Nodes: " << VoxelTreeElement::getTotalMemoryUsage() / 1000000.f << "MB " "Geometry RAM: " << _voxels.getVoxelMemoryUsageRAM() / 1000000.f << "MB " << "VBO: " << _voxels.getVoxelMemoryUsageVBO() / 1000000.f << "MB "; if (_voxels.hasVoxelMemoryUsageGPU()) { @@ -3680,7 +3671,7 @@ void Application::renderCoverageMap() { void Application::renderCoverageMapsRecursively(CoverageMap* map) { for (int i = 0; i < map->getPolygonCount(); i++) { - VoxelProjectedPolygon* polygon = map->getPolygon(i); + OctreeProjectedPolygon* polygon = map->getPolygon(i); if (polygon->getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_BOTTOM)) { glColor3f(.5,0,0); // dark red @@ -4058,7 +4049,7 @@ void Application::deleteVoxelUnderCursor() { } void Application::eyedropperVoxelUnderCursor() { - VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + VoxelTreeElement* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); if (selectedNode && selectedNode->isColored()) { QColor selectedColor(selectedNode->getColor()[RED_INDEX], selectedNode->getColor()[GREEN_INDEX], @@ -4217,7 +4208,7 @@ void Application::trackIncomingVoxelPacket(unsigned char* messageData, ssize_t m _voxelSceneStatsLock.lockForWrite(); if (_voxelServerSceneStats.find(nodeUUID) != _voxelServerSceneStats.end()) { VoxelSceneStats& stats = _voxelServerSceneStats[nodeUUID]; - stats.trackIncomingVoxelPacket(messageData, messageLength, wasStatsPacket); + stats.trackIncomingOctreePacket(messageData, messageLength, wasStatsPacket); } _voxelSceneStatsLock.unlock(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 3e798c10e8..c954f036d6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -236,7 +236,7 @@ private: void updateProjectionMatrix(); void updateProjectionMatrix(Camera& camera, bool updateViewFrustum = true); - static bool sendVoxelsOperation(VoxelNode* node, void* extraData); + static bool sendVoxelsOperation(OctreeElement* node, void* extraData); static void processAvatarURLsMessage(unsigned char* packetData, size_t dataBytes); static void processAvatarFaceVideoMessage(unsigned char* packetData, size_t dataBytes); static void sendPingPackets(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 6abb1cd3b7..8a3debfa5f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -67,7 +67,7 @@ Menu::Menu() : _voxelStatsDialog(NULL), _lodToolsDialog(NULL), _maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM), - _voxelSizeScale(DEFAULT_VOXEL_SIZE_SCALE), + _voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE), _boundaryLevelAdjust(0), _maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS) { @@ -518,7 +518,7 @@ void Menu::loadSettings(QSettings* settings) { _faceshiftEyeDeflection = loadSetting(settings, "faceshiftEyeDeflection", DEFAULT_FACESHIFT_EYE_DEFLECTION); _maxVoxels = loadSetting(settings, "maxVoxels", DEFAULT_MAX_VOXELS_PER_SYSTEM); _maxVoxelPacketsPerSecond = loadSetting(settings, "maxVoxelsPPS", DEFAULT_MAX_VOXEL_PPS); - _voxelSizeScale = loadSetting(settings, "voxelSizeScale", DEFAULT_VOXEL_SIZE_SCALE); + _voxelSizeScale = loadSetting(settings, "voxelSizeScale", DEFAULT_OCTREE_SIZE_SCALE); _boundaryLevelAdjust = loadSetting(settings, "boundaryLevelAdjust", 0); settings->beginGroup("View Frustum Offset Camera"); diff --git a/interface/src/VoxelImporter.cpp b/interface/src/VoxelImporter.cpp index b3ca39bbb0..ce8d5d3894 100644 --- a/interface/src/VoxelImporter.cpp +++ b/interface/src/VoxelImporter.cpp @@ -51,7 +51,7 @@ VoxelImporter::~VoxelImporter() { } void VoxelImporter::reset() { - _voxelTree.eraseAllVoxels(); + _voxelTree.eraseAllOctreeElements(); _importDialog.reset(); _filename = ""; @@ -78,7 +78,7 @@ int VoxelImporter::exec() { if (_importDialog.getImportIntoClipboard()) { VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem(); - voxelSystem->copySubTreeIntoNewTree(voxelSystem->getTree()->rootNode, + voxelSystem->copySubTreeIntoNewTree(voxelSystem->getTree()->getRoot(), Application::getInstance()->getClipboard(), true); voxelSystem->changeTree(Application::getInstance()->getClipboard()); @@ -184,5 +184,5 @@ void ImportTask::run() { qDebug("[ERROR] Invalid file extension.\n"); } - voxelSystem->getTree()->reaverageVoxelColors(voxelSystem->getTree()->rootNode); + voxelSystem->getTree()->reaverageOctreeElements(); } diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 324f293110..6a3408321d 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -74,13 +74,13 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) _readRenderFullVBO = true; _tree = new VoxelTree(); - _tree->rootNode->setVoxelSystem(this); + _tree->getRoot()->setVoxelSystem(this); pthread_mutex_init(&_bufferWriteLock, NULL); pthread_mutex_init(&_treeLock, NULL); pthread_mutex_init(&_freeIndexLock, NULL); - VoxelNode::addDeleteHook(this); - VoxelNode::addUpdateHook(this); + VoxelTreeElement::addDeleteHook(this); + VoxelTreeElement::addUpdateHook(this); _abandonedVBOSlots = 0; _falseColorizeBySource = false; _dataSourceUUID = QUuid(); @@ -113,13 +113,14 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) _treeIsBusy = false; } -void VoxelSystem::voxelDeleted(VoxelNode* node) { - if (node->getVoxelSystem() == this) { +void VoxelSystem::elementDeleted(OctreeElement* element) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; + if (voxel->getVoxelSystem() == this) { if (_voxelsInWriteArrays != 0) { - forceRemoveNodeFromArrays(node); + forceRemoveNodeFromArrays(voxel); } else { if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) { - printf("VoxelSystem::voxelDeleted() while _voxelsInWriteArrays==0, is that expected? \n"); + printf("VoxelSystem::elementDeleted() while _voxelsInWriteArrays==0, is that expected? \n"); } } } @@ -132,50 +133,51 @@ void VoxelSystem::setDisableFastVoxelPipeline(bool disableFastVoxelPipeline) { setupNewVoxelsForDrawing(); } -void VoxelSystem::voxelUpdated(VoxelNode* node) { +void VoxelSystem::elementUpdated(OctreeElement* element) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; // If we're in SetupNewVoxelsForDrawing() or _writeRenderFullVBO then bail.. if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) { return; } - if (node->getVoxelSystem() == this) { + if (voxel->getVoxelSystem() == this) { bool shouldRender = false; // assume we don't need to render it // if it's colored, we might need to render it! float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale(); int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); - shouldRender = node->calculateShouldRender(_viewFrustum, voxelSizeScale, boundaryLevelAdjust); + shouldRender = voxel->calculateShouldRender(_viewFrustum, voxelSizeScale, boundaryLevelAdjust); - if (node->getShouldRender() != shouldRender) { - node->setShouldRender(shouldRender); + if (voxel->getShouldRender() != shouldRender) { + voxel->setShouldRender(shouldRender); } - if (!node->isLeaf()) { + if (!voxel->isLeaf()) { // As we check our children, see if any of them went from shouldRender to NOT shouldRender // then we probably dropped LOD and if we don't have color, we want to average our children // for a new color. int childrenGotHiddenCount = 0; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* childNode = node->getChildAtIndex(i); - if (childNode) { - bool wasShouldRender = childNode->getShouldRender(); - bool isShouldRender = childNode->calculateShouldRender(_viewFrustum, voxelSizeScale, boundaryLevelAdjust); + VoxelTreeElement* childVoxel = voxel->getChildAtIndex(i); + if (childVoxel) { + bool wasShouldRender = childVoxel->getShouldRender(); + bool isShouldRender = childVoxel->calculateShouldRender(_viewFrustum, voxelSizeScale, boundaryLevelAdjust); if (wasShouldRender && !isShouldRender) { childrenGotHiddenCount++; } } } if (childrenGotHiddenCount > 0) { - node->setColorFromAverageOfChildren(); + voxel->calculateAverageFromChildren(); } } const bool REUSE_INDEX = true; const bool DONT_FORCE_REDRAW = false; - updateNodeInArrays(node, REUSE_INDEX, DONT_FORCE_REDRAW); + updateNodeInArrays(voxel, REUSE_INDEX, DONT_FORCE_REDRAW); _voxelsUpdated++; - node->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things. + voxel->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things. setupNewVoxelsForDrawingSingleNode(); } @@ -199,7 +201,7 @@ glBufferIndex VoxelSystem::getNextBufferIndex() { return output; } -// Release responsibility of the buffer/vbo index from the VoxelNode, and makes the index available for some other node to use +// Release responsibility of the buffer/vbo index from the VoxelTreeElement, and makes the index available for some other node to use // will also "clean up" the index data for the buffer/vbo slot, so that if it's in the middle of the draw range, the triangles // will be "invisible" void VoxelSystem::freeBufferIndex(glBufferIndex index) { @@ -260,8 +262,8 @@ VoxelSystem::~VoxelSystem() { pthread_mutex_destroy(&_treeLock); pthread_mutex_destroy(&_freeIndexLock); - VoxelNode::removeDeleteHook(this); - VoxelNode::removeUpdateHook(this); + VoxelTreeElement::removeDeleteHook(this); + VoxelTreeElement::removeUpdateHook(this); } void VoxelSystem::setMaxVoxels(int maxVoxels) { @@ -534,13 +536,8 @@ void VoxelSystem::initVoxelMemory() { pthread_mutex_unlock(&_bufferWriteLock); } -void VoxelSystem::loadVoxelsFile(const char* fileName, bool wantColorRandomizer) { - _tree->loadVoxelsFile(fileName, wantColorRandomizer); - setupNewVoxelsForDrawing(); -} - -void VoxelSystem::writeToSVOFile(const char* filename, VoxelNode* node) const { - _tree->writeToSVOFile(filename, node); +void VoxelSystem::writeToSVOFile(const char* filename, VoxelTreeElement* element) const { + _tree->writeToSVOFile(filename, element); } bool VoxelSystem::readFromSVOFile(const char* filename) { @@ -567,30 +564,6 @@ bool VoxelSystem::readFromSchematicFile(const char* filename) { return result; } -long int VoxelSystem::getVoxelsCreated() { - return _tree->voxelsCreated; -} - -float VoxelSystem::getVoxelsCreatedPerSecondAverage() { - return (1 / _tree->voxelsCreatedStats.getEventDeltaAverage()); -} - -long int VoxelSystem::getVoxelsColored() { - return _tree->voxelsColored; -} - -float VoxelSystem::getVoxelsColoredPerSecondAverage() { - return (1 / _tree->voxelsColoredStats.getEventDeltaAverage()); -} - -long int VoxelSystem::getVoxelsBytesRead() { - return _tree->voxelsBytesRead; -} - -float VoxelSystem::getVoxelsBytesReadPerSecondAverage() { - return _tree->voxelsBytesReadStats.getAverageSampleValuePerSecond(); -} - int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) { unsigned char command = *sourceBuffer; @@ -702,7 +675,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() { if (_writeRenderFullVBO) { clearFreeBufferIndexes(); } - _voxelsUpdated = newTreeToArrays(_tree->rootNode); + _voxelsUpdated = newTreeToArrays(_tree->getRoot()); _tree->clearDirtyBit(); // after we pull the trees into the array, we can consider the tree clean if (_writeRenderFullVBO) { @@ -955,56 +928,56 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) { } } -int VoxelSystem::newTreeToArrays(VoxelNode* node) { +int VoxelSystem::newTreeToArrays(VoxelTreeElement* voxel) { int voxelsUpdated = 0; bool shouldRender = false; // assume we don't need to render it // if it's colored, we might need to render it! float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale();; int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); - shouldRender = node->calculateShouldRender(_viewFrustum, voxelSizeScale, boundaryLevelAdjust); + shouldRender = voxel->calculateShouldRender(_viewFrustum, voxelSizeScale, boundaryLevelAdjust); - node->setShouldRender(shouldRender); + voxel->setShouldRender(shouldRender); // let children figure out their renderness - if (!node->isLeaf()) { + if (!voxel->isLeaf()) { // As we check our children, see if any of them went from shouldRender to NOT shouldRender // then we probably dropped LOD and if we don't have color, we want to average our children // for a new color. int childrenGotHiddenCount = 0; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* childNode = node->getChildAtIndex(i); - if (childNode) { - bool wasShouldRender = childNode->getShouldRender(); - voxelsUpdated += newTreeToArrays(childNode); - bool isShouldRender = childNode->getShouldRender(); + VoxelTreeElement* childVoxel = voxel->getChildAtIndex(i); + if (childVoxel) { + bool wasShouldRender = childVoxel->getShouldRender(); + voxelsUpdated += newTreeToArrays(childVoxel); + bool isShouldRender = childVoxel->getShouldRender(); if (wasShouldRender && !isShouldRender) { childrenGotHiddenCount++; } } } if (childrenGotHiddenCount > 0) { - node->setColorFromAverageOfChildren(); + voxel->calculateAverageFromChildren(); } } if (_writeRenderFullVBO) { const bool DONT_REUSE_INDEX = false; const bool FORCE_REDRAW = true; - voxelsUpdated += updateNodeInArrays(node, DONT_REUSE_INDEX, FORCE_REDRAW); + voxelsUpdated += updateNodeInArrays(voxel, DONT_REUSE_INDEX, FORCE_REDRAW); } else { const bool REUSE_INDEX = true; const bool DONT_FORCE_REDRAW = false; - voxelsUpdated += updateNodeInArrays(node, REUSE_INDEX, DONT_FORCE_REDRAW); + voxelsUpdated += updateNodeInArrays(voxel, REUSE_INDEX, DONT_FORCE_REDRAW); } - node->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things. + voxel->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things. return voxelsUpdated; } -// called as response to voxelDeleted() in fast pipeline case. The node +// called as response to elementDeleted() in fast pipeline case. The node // is being deleted, but it's state is such that it thinks it should render // and therefore we can't use the normal render calculations. This method // will forcibly remove it from the VBOs because we know better!!! -int VoxelSystem::forceRemoveNodeFromArrays(VoxelNode* node) { +int VoxelSystem::forceRemoveNodeFromArrays(VoxelTreeElement* node) { if (!_initialized) { return 0; @@ -1021,7 +994,7 @@ int VoxelSystem::forceRemoveNodeFromArrays(VoxelNode* node) { return 0; // not-updated } -int VoxelSystem::updateNodeInArrays(VoxelNode* node, bool reuseIndex, bool forceDraw) { +int VoxelSystem::updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, bool forceDraw) { // If we've run out of room, then just bail... if (_voxelsInWriteArrays >= _maxVoxels && (_freeIndexes.size() == 0)) { // We need to think about what else we can do in this case. This basically means that all of our available @@ -1129,7 +1102,7 @@ void VoxelSystem::init() { initVoxelMemory(); // our own _removedVoxels doesn't need to be notified of voxel deletes - VoxelNode::removeDeleteHook(&_removedVoxels); + VoxelTreeElement::removeDeleteHook(&_removedVoxels); } void VoxelSystem::changeTree(VoxelTree* newTree) { @@ -1137,7 +1110,7 @@ void VoxelSystem::changeTree(VoxelTree* newTree) { _tree = newTree; _tree->setDirtyBit(); - _tree->rootNode->setVoxelSystem(this); + _tree->getRoot()->setVoxelSystem(this); connect(_tree, SIGNAL(importSize(float,float,float)), SIGNAL(importSize(float,float,float))); connect(_tree, SIGNAL(importProgress(int)), SIGNAL(importProgress(int))); @@ -1457,7 +1430,7 @@ int VoxelSystem::_nodeCount = 0; void VoxelSystem::killLocalVoxels() { lockTree(); - _tree->eraseAllVoxels(); + _tree->eraseAllOctreeElements(); unlockTree(); clearFreeBufferIndexes(); _voxelsInReadArrays = 0; // do we need to do this? @@ -1469,9 +1442,10 @@ void VoxelSystem::redrawInViewVoxels() { } -bool VoxelSystem::clearAllNodesBufferIndexOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void* extraData) { _nodeCount++; - node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); + VoxelTreeElement* voxel = (VoxelTreeElement*)element; + voxel->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); return true; } @@ -1485,7 +1459,7 @@ void VoxelSystem::clearAllNodesBufferIndex() { } } -bool VoxelSystem::forceRedrawEntireTreeOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::forceRedrawEntireTreeOperation(OctreeElement* node, void* extraData) { _nodeCount++; node->setDirtyBit(); return true; @@ -1499,11 +1473,12 @@ void VoxelSystem::forceRedrawEntireTree() { setupNewVoxelsForDrawing(); } -bool VoxelSystem::randomColorOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::randomColorOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; _nodeCount++; - if (node->isColored()) { + if (voxel->isColored()) { nodeColor newColor = { 255, randomColorValue(150), randomColorValue(150), 1 }; - node->setColor(newColor); + voxel->setColor(newColor); } return true; } @@ -1516,10 +1491,11 @@ void VoxelSystem::randomizeVoxelColors() { setupNewVoxelsForDrawing(); } -bool VoxelSystem::falseColorizeRandomOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::falseColorizeRandomOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; _nodeCount++; // always false colorize - node->setFalseColor(255, randomColorValue(150), randomColorValue(150)); + voxel->setFalseColor(255, randomColorValue(150), randomColorValue(150)); return true; // keep going! } @@ -1531,9 +1507,10 @@ void VoxelSystem::falseColorizeRandom() { setupNewVoxelsForDrawing(); } -bool VoxelSystem::trueColorizeOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::trueColorizeOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; _nodeCount++; - node->setFalseColored(false); + voxel->setFalseColored(false); return true; } @@ -1547,13 +1524,14 @@ void VoxelSystem::trueColorize() { } // Will false colorize voxels that are not in view -bool VoxelSystem::falseColorizeInViewOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::falseColorizeInViewOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; const ViewFrustum* viewFrustum = (const ViewFrustum*) extraData; _nodeCount++; - if (node->isColored()) { - if (!node->isInView(*viewFrustum)) { + if (voxel->isColored()) { + if (!voxel->isInView(*viewFrustum)) { // Out of view voxels are colored RED - node->setFalseColor(255, 0, 0); + voxel->setFalseColor(255, 0, 0); } } return true; // keep going! @@ -1584,13 +1562,14 @@ public: }; // Will false colorize voxels that are not in view -bool VoxelSystem::falseColorizeBySourceOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::falseColorizeBySourceOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; colorizeBySourceArgs* args = (colorizeBySourceArgs*)extraData; _nodeCount++; - if (node->isColored()) { + if (voxel->isColored()) { // pick a color based on the source - we want each source to be obviously different - uint16_t nodeIDKey = node->getSourceUUIDKey(); - node->setFalseColor(args->colors[nodeIDKey].red, args->colors[nodeIDKey].green, args->colors[nodeIDKey].blue); + uint16_t nodeIDKey = voxel->getSourceUUIDKey(); + voxel->setFalseColor(args->colors[nodeIDKey].red, args->colors[nodeIDKey].green, args->colors[nodeIDKey].blue); } return true; // keep going! } @@ -1614,7 +1593,7 @@ void VoxelSystem::falseColorizeBySource() { NodeList* nodeList = NodeList::getInstance(); for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { if (node->getType() == NODE_TYPE_VOXEL_SERVER) { - uint16_t nodeID = VoxelNode::getSourceNodeUUIDKey(node->getUUID()); + uint16_t nodeID = VoxelTreeElement::getSourceNodeUUIDKey(node->getUUID()); int groupColor = voxelServerCount % NUMBER_OF_COLOR_GROUPS; args.colors[nodeID] = groupColors[groupColor]; @@ -1639,10 +1618,11 @@ void VoxelSystem::falseColorizeBySource() { } // Will false colorize voxels based on distance from view -bool VoxelSystem::falseColorizeDistanceFromViewOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::falseColorizeDistanceFromViewOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; ViewFrustum* viewFrustum = (ViewFrustum*) extraData; - if (node->isColored()) { - float distance = node->distanceToCamera(*viewFrustum); + if (voxel->isColored()) { + float distance = voxel->distanceToCamera(*viewFrustum); _nodeCount++; float distanceRatio = (_minDistance == _maxDistance) ? 1 : (distance - _minDistance) / (_maxDistance - _minDistance); @@ -1651,7 +1631,7 @@ bool VoxelSystem::falseColorizeDistanceFromViewOperation(VoxelNode* node, void* const unsigned char colorBands = 16; const unsigned char gradientOver = 128; unsigned char colorBand = (colorBands * distanceRatio); - node->setFalseColor((colorBand * (gradientOver / colorBands)) + (maxColor - gradientOver), 0, 0); + voxel->setFalseColor((colorBand * (gradientOver / colorBands)) + (maxColor - gradientOver), 0, 0); } return true; // keep going! } @@ -1662,11 +1642,12 @@ float VoxelSystem::_minDistance = FLT_MAX; // Helper function will get the distance from view range, would be nice if you could just keep track // of this as voxels are created and/or colored... seems like some transform math could do that so // we wouldn't need to do two passes of the tree -bool VoxelSystem::getDistanceFromViewRangeOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::getDistanceFromViewRangeOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; ViewFrustum* viewFrustum = (ViewFrustum*) extraData; // only do this for truly colored voxels... - if (node->isColored()) { - float distance = node->distanceToCamera(*viewFrustum); + if (voxel->isColored()) { + float distance = voxel->distanceToCamera(*viewFrustum); // calculate the range of distances if (distance > _maxDistance) { _maxDistance = distance; @@ -1695,16 +1676,16 @@ void VoxelSystem::falseColorizeDistanceFromView() { // combines the removeOutOfView args into a single class class removeOutOfViewArgs { public: - VoxelSystem* thisVoxelSystem; - ViewFrustum thisViewFrustum; - VoxelNodeBag dontRecurseBag; + VoxelSystem* thisVoxelSystem; + ViewFrustum thisViewFrustum; + OctreeElementBag dontRecurseBag; unsigned long nodesScanned; unsigned long nodesRemoved; unsigned long nodesInside; unsigned long nodesIntersect; unsigned long nodesOutside; - VoxelNode* insideRoot; - VoxelNode* outsideRoot; + VoxelTreeElement* insideRoot; + VoxelTreeElement* outsideRoot; removeOutOfViewArgs(VoxelSystem* voxelSystem, bool widenViewFrustum = true) : thisVoxelSystem(voxelSystem), @@ -1734,14 +1715,15 @@ void VoxelSystem::cancelImport() { // "Remove" voxels from the tree that are not in view. We don't actually delete them, // we remove them from the tree and place them into a holding area for later deletion -bool VoxelSystem::removeOutOfViewOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::removeOutOfViewOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*) element; removeOutOfViewArgs* args = (removeOutOfViewArgs*)extraData; // If our node was previously added to the don't recurse bag, then return false to // stop the further recursion. This means that the whole node and it's children are // known to be in view, so don't recurse them - if (args->dontRecurseBag.contains(node)) { - args->dontRecurseBag.remove(node); + if (args->dontRecurseBag.contains(voxel)) { + args->dontRecurseBag.remove(voxel); return false; // stop recursion } @@ -1749,14 +1731,14 @@ bool VoxelSystem::removeOutOfViewOperation(VoxelNode* node, void* extraData) { args->nodesScanned++; // Need to operate on our child nodes, so we can remove them for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* childNode = node->getChildAtIndex(i); + VoxelTreeElement* childNode = voxel->getChildAtIndex(i); if (childNode) { ViewFrustum::location inFrustum = childNode->inFrustum(args->thisViewFrustum); switch (inFrustum) { case ViewFrustum::OUTSIDE: { args->nodesOutside++; args->nodesRemoved++; - node->removeChildAtIndex(i); + voxel->removeChildAtIndex(i); thisVoxelSystem->_removedVoxels.insert(childNode); // by removing the child, it will not get recursed! } break; @@ -1850,24 +1832,25 @@ void VoxelSystem::showAllLocalVoxels() { } } -bool VoxelSystem::showAllLocalVoxelsOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::showAllLocalVoxelsOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; showAllLocalVoxelsArgs* args = (showAllLocalVoxelsArgs*)extraData; args->nodesScanned++; float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale();; int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); - bool shouldRender = node->calculateShouldRender(&args->thisViewFrustum, voxelSizeScale, boundaryLevelAdjust); - node->setShouldRender(shouldRender); + bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, voxelSizeScale, boundaryLevelAdjust); + voxel->setShouldRender(shouldRender); if (shouldRender) { bool falseColorize = false; if (falseColorize) { - node->setFalseColor(0,0,255); // false colorize + voxel->setFalseColor(0,0,255); // false colorize } // These are both needed to force redraw... - node->setDirtyBit(); - node->markWithChangedTime(); + voxel->setDirtyBit(); + voxel->markWithChangedTime(); } return true; // keep recursing! @@ -1991,7 +1974,8 @@ void VoxelSystem::hideOutOfView(bool forceFullFrustum) { _inhideOutOfView = false; } -bool VoxelSystem::hideAllSubTreeOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::hideAllSubTreeOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData; // If we've culled at least once, then we will use the status of this voxel in the last culled frustum to determine @@ -2000,7 +1984,7 @@ bool VoxelSystem::hideAllSubTreeOperation(VoxelNode* node, void* extraData) { ViewFrustum::location inLastCulledFrustum; if (args->culledOnce && args->wantDeltaFrustums) { - inLastCulledFrustum = node->inFrustum(args->lastViewFrustum); + inLastCulledFrustum = voxel->inFrustum(args->lastViewFrustum); // if this node is fully OUTSIDE our last culled view frustum, then we don't need to recurse further if (inLastCulledFrustum == ViewFrustum::OUTSIDE) { @@ -2010,14 +1994,14 @@ bool VoxelSystem::hideAllSubTreeOperation(VoxelNode* node, void* extraData) { } args->nodesOutside++; - if (node->isKnownBufferIndex()) { + if (voxel->isKnownBufferIndex()) { args->nodesRemoved++; bool falseColorize = false; if (falseColorize) { - node->setFalseColor(255,0,0); // false colorize + voxel->setFalseColor(255,0,0); // false colorize } else { VoxelSystem* thisVoxelSystem = args->thisVoxelSystem; - thisVoxelSystem->_voxelsUpdated += thisVoxelSystem->forceRemoveNodeFromArrays(node); + thisVoxelSystem->_voxelsUpdated += thisVoxelSystem->forceRemoveNodeFromArrays(voxel); thisVoxelSystem->setupNewVoxelsForDrawingSingleNode(); } @@ -2026,7 +2010,8 @@ bool VoxelSystem::hideAllSubTreeOperation(VoxelNode* node, void* extraData) { return true; } -bool VoxelSystem::showAllSubTreeOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::showAllSubTreeOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData; // If we've culled at least once, then we will use the status of this voxel in the last culled frustum to determine @@ -2035,7 +2020,7 @@ bool VoxelSystem::showAllSubTreeOperation(VoxelNode* node, void* extraData) { ViewFrustum::location inLastCulledFrustum; if (args->culledOnce && args->wantDeltaFrustums) { - inLastCulledFrustum = node->inFrustum(args->lastViewFrustum); + inLastCulledFrustum = voxel->inFrustum(args->lastViewFrustum); // if this node is fully inside our last culled view frustum, then we don't need to recurse further if (inLastCulledFrustum == ViewFrustum::INSIDE) { @@ -2048,17 +2033,17 @@ bool VoxelSystem::showAllSubTreeOperation(VoxelNode* node, void* extraData) { float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale(); int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); - bool shouldRender = node->calculateShouldRender(&args->thisViewFrustum, voxelSizeScale, boundaryLevelAdjust); - node->setShouldRender(shouldRender); + bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, voxelSizeScale, boundaryLevelAdjust); + voxel->setShouldRender(shouldRender); - if (shouldRender && !node->isKnownBufferIndex()) { + if (shouldRender && !voxel->isKnownBufferIndex()) { bool falseColorize = false; if (falseColorize) { - node->setFalseColor(0,0,255); // false colorize + voxel->setFalseColor(0,0,255); // false colorize } // These are both needed to force redraw... - node->setDirtyBit(); - node->markWithChangedTime(); + voxel->setDirtyBit(); + voxel->markWithChangedTime(); } return true; // keep recursing! @@ -2067,12 +2052,13 @@ bool VoxelSystem::showAllSubTreeOperation(VoxelNode* node, void* extraData) { // "hide" voxels in the VBOs that are still in the tree that but not in view. // We don't remove them from the tree, we don't delete them, we do remove them // from the VBOs and mark them as such in the tree. -bool VoxelSystem::hideOutOfViewOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData; // If we're still recursing the tree using this operator, then we don't know if we're inside or outside... // so before we move forward we need to determine our frustum location - ViewFrustum::location inFrustum = node->inFrustum(args->thisViewFrustum); + ViewFrustum::location inFrustum = voxel->inFrustum(args->thisViewFrustum); // If we've culled at least once, then we will use the status of this voxel in the last culled frustum to determine // how to proceed. If we've never culled, then we just consider all these voxels to be UNKNOWN so that we will not @@ -2080,7 +2066,7 @@ bool VoxelSystem::hideOutOfViewOperation(VoxelNode* node, void* extraData) { ViewFrustum::location inLastCulledFrustum; if (args->culledOnce && args->wantDeltaFrustums) { - inLastCulledFrustum = node->inFrustum(args->lastViewFrustum); + inLastCulledFrustum = voxel->inFrustum(args->lastViewFrustum); } // ok, now do some processing for this node... @@ -2099,7 +2085,7 @@ bool VoxelSystem::hideOutOfViewOperation(VoxelNode* node, void* extraData) { // if this node is fully OUTSIDE the view, but previously intersected and/or was inside the last view, then // we need to hide it. Additionally we know that ALL of it's children are also fully OUTSIDE so we can recurse // the children and simply mark them as hidden - args->tree->recurseNodeWithOperation(node, hideAllSubTreeOperation, args ); + args->tree->recurseNodeWithOperation(voxel, hideAllSubTreeOperation, args ); return false; @@ -2118,7 +2104,7 @@ bool VoxelSystem::hideOutOfViewOperation(VoxelNode* node, void* extraData) { // if this node is fully INSIDE the view, but previously INTERSECTED and/or was OUTSIDE the last view, then // we need to show it. Additionally we know that ALL of it's children are also fully INSIDE so we can recurse // the children and simply mark them as visible (as appropriate based on LOD) - args->tree->recurseNodeWithOperation(node, showAllSubTreeOperation, args); + args->tree->recurseNodeWithOperation(voxel, showAllSubTreeOperation, args); return false; } break; @@ -2138,8 +2124,8 @@ bool VoxelSystem::hideOutOfViewOperation(VoxelNode* node, void* extraData) { // if the child node INTERSECTs the view, then we want to check to see if it thinks it should render // if it should render but is missing it's VBO index, then we want to flip it on, and we can stop recursing from // here because we know will block any children anyway - if (node->getShouldRender() && !node->isKnownBufferIndex()) { - node->setDirtyBit(); // will this make it draw? + if (voxel->getShouldRender() && !voxel->isKnownBufferIndex()) { + voxel->setDirtyBit(); // will this make it draw? return false; } @@ -2159,18 +2145,19 @@ bool VoxelSystem::hideOutOfViewOperation(VoxelNode* node, void* extraData) { bool VoxelSystem::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, VoxelDetail& detail, float& distance, BoxFace& face) { lockTree(); - VoxelNode* node; - if (!_tree->findRayIntersection(origin, direction, node, distance, face)) { + OctreeElement* element; + if (!_tree->findRayIntersection(origin, direction, element, distance, face)) { unlockTree(); return false; } - detail.x = node->getCorner().x; - detail.y = node->getCorner().y; - detail.z = node->getCorner().z; - detail.s = node->getScale(); - detail.red = node->getColor()[0]; - detail.green = node->getColor()[1]; - detail.blue = node->getColor()[2]; + VoxelTreeElement* voxel = (VoxelTreeElement*)element; + detail.x = voxel->getCorner().x; + detail.y = voxel->getCorner().y; + detail.z = voxel->getCorner().z; + detail.s = voxel->getScale(); + detail.red = voxel->getColor()[0]; + detail.green = voxel->getColor()[1]; + detail.blue = voxel->getColor()[2]; unlockTree(); return true; } @@ -2198,14 +2185,15 @@ public: bool colorThis; }; -bool VoxelSystem::falseColorizeRandomEveryOtherOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::falseColorizeRandomEveryOtherOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; falseColorizeRandomEveryOtherArgs* args = (falseColorizeRandomEveryOtherArgs*)extraData; args->totalNodes++; - if (node->isColored()) { + if (voxel->isColored()) { args->colorableNodes++; if (args->colorThis) { args->coloredNodes++; - node->setFalseColor(255, randomColorValue(150), randomColorValue(150)); + voxel->setFalseColor(255, randomColorValue(150), randomColorValue(150)); } args->colorThis = !args->colorThis; } @@ -2257,42 +2245,43 @@ public: bool* hasIndexFound; }; -bool VoxelSystem::collectStatsForTreesAndVBOsOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::collectStatsForTreesAndVBOsOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; collectStatsForTreesAndVBOsArgs* args = (collectStatsForTreesAndVBOsArgs*)extraData; args->totalNodes++; - if (node->isLeaf()) { + if (voxel->isLeaf()) { args->leafNodes++; } - if (node->isColored()) { + if (voxel->isColored()) { args->coloredNodes++; } - if (node->getShouldRender()) { + if (voxel->getShouldRender()) { args->shouldRenderNodes++; } - if (node->isDirty()) { + if (voxel->isDirty()) { args->dirtyNodes++; } - if (node->isKnownBufferIndex()) { + if (voxel->isKnownBufferIndex()) { args->nodesInVBO++; - unsigned long nodeIndex = node->getBufferIndex(); + unsigned long nodeIndex = voxel->getBufferIndex(); const bool extraDebugging = false; // enable for extra debugging if (extraDebugging) { qDebug("node In VBO... [%f,%f,%f] %f ... index=%ld, isDirty=%s, shouldRender=%s \n", - node->getCorner().x, node->getCorner().y, node->getCorner().z, node->getScale(), - nodeIndex, debug::valueOf(node->isDirty()), debug::valueOf(node->getShouldRender())); + voxel->getCorner().x, voxel->getCorner().y, voxel->getCorner().z, voxel->getScale(), + nodeIndex, debug::valueOf(voxel->isDirty()), debug::valueOf(voxel->getShouldRender())); } if (args->hasIndexFound[nodeIndex]) { args->duplicateVBOIndex++; qDebug("duplicateVBO found... index=%ld, isDirty=%s, shouldRender=%s \n", nodeIndex, - debug::valueOf(node->isDirty()), debug::valueOf(node->getShouldRender())); + debug::valueOf(voxel->isDirty()), debug::valueOf(voxel->getShouldRender())); } else { args->hasIndexFound[nodeIndex] = true; } @@ -2301,7 +2290,7 @@ bool VoxelSystem::collectStatsForTreesAndVBOsOperation(VoxelNode* node, void* ex } // if it's in VBO but not-shouldRender, track that also... - if (!node->getShouldRender()) { + if (!voxel->getShouldRender()) { args->nodesInVBONotShouldRender++; } } @@ -2368,7 +2357,7 @@ void VoxelSystem::deleteVoxelAt(float x, float y, float z, float s) { }; -VoxelNode* VoxelSystem::getVoxelAt(float x, float y, float z, float s) const { +VoxelTreeElement* VoxelSystem::getVoxelAt(float x, float y, float z, float s) const { return _tree->getVoxelAt(x, y, z, s); }; @@ -2394,20 +2383,20 @@ void VoxelSystem::createSphere(float r,float xc, float yc, float zc, float s, bo setupNewVoxelsForDrawing(); }; -void VoxelSystem::copySubTreeIntoNewTree(VoxelNode* startNode, VoxelSystem* destination, bool rebaseToRoot) { +void VoxelSystem::copySubTreeIntoNewTree(VoxelTreeElement* startNode, VoxelSystem* destination, bool rebaseToRoot) { _tree->copySubTreeIntoNewTree(startNode, destination->_tree, rebaseToRoot); destination->setupNewVoxelsForDrawing(); } -void VoxelSystem::copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destination, bool rebaseToRoot) { +void VoxelSystem::copySubTreeIntoNewTree(VoxelTreeElement* startNode, VoxelTree* destination, bool rebaseToRoot) { _tree->copySubTreeIntoNewTree(startNode, destination, rebaseToRoot); } -void VoxelSystem::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode) { +void VoxelSystem::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelTreeElement* destinationNode) { _tree->copyFromTreeIntoSubTree(sourceTree, destinationNode); } -void VoxelSystem::recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData) { +void VoxelSystem::recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData) { _tree->recurseTreeWithOperation(operation, extraData); } @@ -2432,27 +2421,29 @@ struct FalseColorizeSubTreeOperationArgs { long voxelsTouched; }; -bool VoxelSystem::falseColorizeSubTreeOperation(VoxelNode* node, void* extraData) { - if (node->getShouldRender()) { +bool VoxelSystem::falseColorizeSubTreeOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; + if (voxel->getShouldRender()) { FalseColorizeSubTreeOperationArgs* args = (FalseColorizeSubTreeOperationArgs*) extraData; - node->setFalseColor(args->color[0], args->color[1], args->color[2]); + voxel->setFalseColor(args->color[0], args->color[1], args->color[2]); args->voxelsTouched++; } return true; } -bool VoxelSystem::falseColorizeOccludedOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::falseColorizeOccludedOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; FalseColorizeOccludedArgs* args = (FalseColorizeOccludedArgs*) extraData; args->totalVoxels++; // If we are a parent, let's see if we're completely occluded. - if (!node->isLeaf()) { + if (!voxel->isLeaf()) { args->nonLeaves++; - AABox voxelBox = node->getAABox(); + AABox voxelBox = voxel->getAABox(); voxelBox.scale(TREE_SCALE); - VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon(args->viewFrustum->getProjectedPolygon(voxelBox)); + OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon(args->viewFrustum->getProjectedPolygon(voxelBox)); // If we're not all in view, then ignore it, and just return. But keep searching... if (!voxelPolygon->getAllInView()) { @@ -2472,7 +2463,7 @@ bool VoxelSystem::falseColorizeOccludedOperation(VoxelNode* node, void* extraDat subArgs.color[2] = 0; subArgs.voxelsTouched = 0; - args->tree->recurseNodeWithOperation(node, falseColorizeSubTreeOperation, &subArgs ); + args->tree->recurseNodeWithOperation(voxel, falseColorizeSubTreeOperation, &subArgs ); args->subtreeVoxelsSkipped += (subArgs.voxelsTouched - 1); args->totalVoxels += (subArgs.voxelsTouched - 1); @@ -2484,12 +2475,12 @@ bool VoxelSystem::falseColorizeOccludedOperation(VoxelNode* node, void* extraDat return true; // keep looking... } - if (node->isLeaf() && node->isColored() && node->getShouldRender()) { + if (voxel->isLeaf() && voxel->isColored() && voxel->getShouldRender()) { args->coloredVoxels++; - AABox voxelBox = node->getAABox(); + AABox voxelBox = voxel->getAABox(); voxelBox.scale(TREE_SCALE); - VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon(args->viewFrustum->getProjectedPolygon(voxelBox)); + OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon(args->viewFrustum->getProjectedPolygon(voxelBox)); // If we're not all in view, then ignore it, and just return. But keep searching... if (!voxelPolygon->getAllInView()) { @@ -2500,7 +2491,7 @@ bool VoxelSystem::falseColorizeOccludedOperation(VoxelNode* node, void* extraDat CoverageMapStorageResult result = args->map->checkMap(voxelPolygon, true); if (result == OCCLUDED) { - node->setFalseColor(255, 0, 0); + voxel->setFalseColor(255, 0, 0); args->occludedVoxels++; } else if (result == STORED) { args->notOccludedVoxels++; @@ -2530,9 +2521,9 @@ void VoxelSystem::falseColorizeOccluded() { args.nonLeavesOccluded = 0; args.tree = _tree; - VoxelProjectedPolygon::pointInside_calls = 0; - VoxelProjectedPolygon::occludes_calls = 0; - VoxelProjectedPolygon::intersects_calls = 0; + OctreeProjectedPolygon::pointInside_calls = 0; + OctreeProjectedPolygon::occludes_calls = 0; + OctreeProjectedPolygon::intersects_calls = 0; glm::vec3 position = args.viewFrustum->getPosition() * (1.0f/TREE_SCALE); @@ -2543,9 +2534,9 @@ void VoxelSystem::falseColorizeOccluded() { args.totalVoxels, args.coloredVoxels, args.occludedVoxels, args.notOccludedVoxels, args.outOfView, args.subtreeVoxelsSkipped, args.nonLeaves, args.nonLeavesOutOfView, args.nonLeavesOccluded, - VoxelProjectedPolygon::pointInside_calls, - VoxelProjectedPolygon::occludes_calls, - VoxelProjectedPolygon::intersects_calls + OctreeProjectedPolygon::pointInside_calls, + OctreeProjectedPolygon::occludes_calls, + OctreeProjectedPolygon::intersects_calls ); @@ -2555,18 +2546,19 @@ void VoxelSystem::falseColorizeOccluded() { setupNewVoxelsForDrawing(); } -bool VoxelSystem::falseColorizeOccludedV2Operation(VoxelNode* node, void* extraData) { +bool VoxelSystem::falseColorizeOccludedV2Operation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; FalseColorizeOccludedArgs* args = (FalseColorizeOccludedArgs*) extraData; args->totalVoxels++; // If we are a parent, let's see if we're completely occluded. - if (!node->isLeaf()) { + if (!voxel->isLeaf()) { args->nonLeaves++; - AABox voxelBox = node->getAABox(); + AABox voxelBox = voxel->getAABox(); voxelBox.scale(TREE_SCALE); - VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon(args->viewFrustum->getProjectedPolygon(voxelBox)); + OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon(args->viewFrustum->getProjectedPolygon(voxelBox)); // If we're not all in view, then ignore it, and just return. But keep searching... if (!voxelPolygon->getAllInView()) { @@ -2586,7 +2578,7 @@ bool VoxelSystem::falseColorizeOccludedV2Operation(VoxelNode* node, void* extraD subArgs.color[2] = 0; subArgs.voxelsTouched = 0; - args->tree->recurseNodeWithOperation(node, falseColorizeSubTreeOperation, &subArgs ); + args->tree->recurseNodeWithOperation(voxel, falseColorizeSubTreeOperation, &subArgs ); args->subtreeVoxelsSkipped += (subArgs.voxelsTouched - 1); args->totalVoxels += (subArgs.voxelsTouched - 1); @@ -2598,12 +2590,12 @@ bool VoxelSystem::falseColorizeOccludedV2Operation(VoxelNode* node, void* extraD return true; // keep looking... } - if (node->isLeaf() && node->isColored() && node->getShouldRender()) { + if (voxel->isLeaf() && voxel->isColored() && voxel->getShouldRender()) { args->coloredVoxels++; - AABox voxelBox = node->getAABox(); + AABox voxelBox = voxel->getAABox(); voxelBox.scale(TREE_SCALE); - VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon(args->viewFrustum->getProjectedPolygon(voxelBox)); + OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon(args->viewFrustum->getProjectedPolygon(voxelBox)); // If we're not all in view, then ignore it, and just return. But keep searching... if (!voxelPolygon->getAllInView()) { @@ -2614,7 +2606,7 @@ bool VoxelSystem::falseColorizeOccludedV2Operation(VoxelNode* node, void* extraD CoverageMapV2StorageResult result = args->mapV2->checkMap(voxelPolygon, true); if (result == V2_OCCLUDED) { - node->setFalseColor(255, 0, 0); + voxel->setFalseColor(255, 0, 0); args->occludedVoxels++; } else if (result == V2_STORED) { args->notOccludedVoxels++; @@ -2634,9 +2626,9 @@ void VoxelSystem::falseColorizeOccludedV2() { CoverageMapV2::wantDebugging = true; - VoxelProjectedPolygon::pointInside_calls = 0; - VoxelProjectedPolygon::occludes_calls = 0; - VoxelProjectedPolygon::intersects_calls = 0; + OctreeProjectedPolygon::pointInside_calls = 0; + OctreeProjectedPolygon::occludes_calls = 0; + OctreeProjectedPolygon::intersects_calls = 0; FalseColorizeOccludedArgs args; args.viewFrustum = _viewFrustum; @@ -2661,9 +2653,9 @@ void VoxelSystem::falseColorizeOccludedV2() { args.totalVoxels, args.coloredVoxels, args.occludedVoxels, args.notOccludedVoxels, args.outOfView, args.subtreeVoxelsSkipped, args.nonLeaves, args.nonLeavesOutOfView, args.nonLeavesOccluded, - VoxelProjectedPolygon::pointInside_calls, - VoxelProjectedPolygon::occludes_calls, - VoxelProjectedPolygon::intersects_calls + OctreeProjectedPolygon::pointInside_calls, + OctreeProjectedPolygon::occludes_calls, + OctreeProjectedPolygon::intersects_calls ); //myCoverageMapV2.erase(); _tree->setDirtyBit(); @@ -2677,13 +2669,14 @@ void VoxelSystem::nodeAdded(Node* node) { } } -bool VoxelSystem::killSourceVoxelsOperation(VoxelNode* node, void* extraData) { +bool VoxelSystem::killSourceVoxelsOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; QUuid killedNodeID = *(QUuid*)extraData; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* childNode = node->getChildAtIndex(i); + VoxelTreeElement* childNode = voxel->getChildAtIndex(i); if (childNode) { if (childNode->matchesSourceUUID(killedNodeID)) { - node->safeDeepDeleteChildAtIndex(i); + voxel->safeDeepDeleteChildAtIndex(i); } } } diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h index d9c672fdf3..5499838edf 100644 --- a/interface/src/VoxelSystem.h +++ b/interface/src/VoxelSystem.h @@ -37,7 +37,7 @@ struct VoxelShaderVBOData }; -class VoxelSystem : public NodeData, public VoxelNodeDeleteHook, public VoxelNodeUpdateHook, +class VoxelSystem : public NodeData, public OctreeElementDeleteHook, public OctreeElementUpdateHook, public NodeListHook, public DomainChangeListener { Q_OBJECT public: @@ -64,8 +64,7 @@ public: ViewFrustum* getLastCulledViewFrustum() { return &_lastCulledViewFrustum; } - void loadVoxelsFile(const char* fileName,bool wantColorRandomizer); - void writeToSVOFile(const char* filename, VoxelNode* node) const; + void writeToSVOFile(const char* filename, VoxelTreeElement* element) const; bool readFromSVOFile(const char* filename); bool readFromSquareARGB32Pixels(const char* filename); bool readFromSchematicFile(const char* filename); @@ -76,12 +75,6 @@ public: unsigned long getVoxelMemoryUsageVBO() const { return _memoryUsageVBO; } bool hasVoxelMemoryUsageGPU() const { return _hasMemoryUsageGPU; } unsigned long getVoxelMemoryUsageGPU(); - long int getVoxelsCreated(); - long int getVoxelsColored(); - long int getVoxelsBytesRead(); - float getVoxelsCreatedPerSecondAverage(); - float getVoxelsColoredPerSecondAverage(); - float getVoxelsBytesReadPerSecondAverage(); void killLocalVoxels(); void redrawInViewVoxels(); @@ -98,24 +91,24 @@ public: bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration); void deleteVoxelAt(float x, float y, float z, float s); - VoxelNode* getVoxelAt(float x, float y, float z, float s) const; + VoxelTreeElement* getVoxelAt(float x, float y, float z, float s) const; void createVoxel(float x, float y, float z, float s, unsigned char red, unsigned char green, unsigned char blue, 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, creationMode mode, bool destructive = false, bool debug = false); - void copySubTreeIntoNewTree(VoxelNode* startNode, VoxelSystem* destinationTree, bool rebaseToRoot); - void copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinationTree, bool rebaseToRoot); - void copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode); + void copySubTreeIntoNewTree(VoxelTreeElement* startNode, VoxelSystem* destinationTree, bool rebaseToRoot); + void copySubTreeIntoNewTree(VoxelTreeElement* startNode, VoxelTree* destinationTree, bool rebaseToRoot); + void copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelTreeElement* destinationNode); - void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL); + void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData=NULL); CoverageMapV2 myCoverageMapV2; CoverageMap myCoverageMap; - virtual void voxelDeleted(VoxelNode* node); - virtual void voxelUpdated(VoxelNode* node); + virtual void elementDeleted(OctreeElement* element); + virtual void elementUpdated(OctreeElement* element); virtual void nodeAdded(Node* node); virtual void nodeKilled(Node* node); virtual void domainChanged(QString domain); @@ -176,33 +169,33 @@ private: bool _initialized; int _callsToTreesToArrays; - VoxelNodeBag _removedVoxels; + OctreeElementBag _removedVoxels; // Operation functions for tree recursion methods static int _nodeCount; - static bool randomColorOperation(VoxelNode* node, void* extraData); - static bool falseColorizeRandomOperation(VoxelNode* node, void* extraData); - static bool trueColorizeOperation(VoxelNode* node, void* extraData); - static bool falseColorizeInViewOperation(VoxelNode* node, void* extraData); - static bool falseColorizeDistanceFromViewOperation(VoxelNode* node, void* extraData); - static bool getDistanceFromViewRangeOperation(VoxelNode* node, void* extraData); - static bool removeOutOfViewOperation(VoxelNode* node, void* extraData); - static bool falseColorizeRandomEveryOtherOperation(VoxelNode* node, void* extraData); - static bool collectStatsForTreesAndVBOsOperation(VoxelNode* node, void* extraData); - static bool falseColorizeOccludedOperation(VoxelNode* node, void* extraData); - static bool falseColorizeSubTreeOperation(VoxelNode* node, void* extraData); - static bool falseColorizeOccludedV2Operation(VoxelNode* node, void* extraData); - static bool falseColorizeBySourceOperation(VoxelNode* node, void* extraData); - static bool killSourceVoxelsOperation(VoxelNode* node, void* extraData); - static bool forceRedrawEntireTreeOperation(VoxelNode* node, void* extraData); - static bool clearAllNodesBufferIndexOperation(VoxelNode* node, void* extraData); - static bool hideOutOfViewOperation(VoxelNode* node, void* extraData); - static bool hideAllSubTreeOperation(VoxelNode* node, void* extraData); - static bool showAllSubTreeOperation(VoxelNode* node, void* extraData); - static bool showAllLocalVoxelsOperation(VoxelNode* node, void* extraData); + static bool randomColorOperation(OctreeElement* node, void* extraData); + static bool falseColorizeRandomOperation(OctreeElement* node, void* extraData); + static bool trueColorizeOperation(OctreeElement* node, void* extraData); + static bool falseColorizeInViewOperation(OctreeElement* node, void* extraData); + static bool falseColorizeDistanceFromViewOperation(OctreeElement* node, void* extraData); + static bool getDistanceFromViewRangeOperation(OctreeElement* node, void* extraData); + static bool removeOutOfViewOperation(OctreeElement* node, void* extraData); + static bool falseColorizeRandomEveryOtherOperation(OctreeElement* node, void* extraData); + static bool collectStatsForTreesAndVBOsOperation(OctreeElement* node, void* extraData); + static bool falseColorizeOccludedOperation(OctreeElement* node, void* extraData); + static bool falseColorizeSubTreeOperation(OctreeElement* node, void* extraData); + static bool falseColorizeOccludedV2Operation(OctreeElement* node, void* extraData); + static bool falseColorizeBySourceOperation(OctreeElement* node, void* extraData); + static bool killSourceVoxelsOperation(OctreeElement* node, void* extraData); + static bool forceRedrawEntireTreeOperation(OctreeElement* node, void* extraData); + static bool clearAllNodesBufferIndexOperation(OctreeElement* node, void* extraData); + static bool hideOutOfViewOperation(OctreeElement* node, void* extraData); + static bool hideAllSubTreeOperation(OctreeElement* node, void* extraData); + static bool showAllSubTreeOperation(OctreeElement* node, void* extraData); + static bool showAllLocalVoxelsOperation(OctreeElement* node, void* extraData); - int updateNodeInArrays(VoxelNode* node, bool reuseIndex, bool forceDraw); - int forceRemoveNodeFromArrays(VoxelNode* node); + int updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, bool forceDraw); + int forceRemoveNodeFromArrays(VoxelTreeElement* node); void copyWrittenDataToReadArraysFullVBOs(); void copyWrittenDataToReadArraysPartialVBOs(); @@ -271,7 +264,7 @@ private: void setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndices[]); - int newTreeToArrays(VoxelNode *currentNode); + int newTreeToArrays(VoxelTreeElement *currentNode); void cleanupRemovedVoxels(); void copyWrittenDataToReadArrays(bool fullVBOs); diff --git a/interface/src/avatar/AvatarVoxelSystem.cpp b/interface/src/avatar/AvatarVoxelSystem.cpp index db02e20f85..1685187c57 100644 --- a/interface/src/avatar/AvatarVoxelSystem.cpp +++ b/interface/src/avatar/AvatarVoxelSystem.cpp @@ -83,7 +83,7 @@ void AvatarVoxelSystem::init() { _boneIndicesLocation = _skinProgram.attributeLocation("boneIndices"); _boneWeightsLocation = _skinProgram.attributeLocation("boneWeights"); - VoxelNode::removeUpdateHook(this); // we don't want this + VoxelTreeElement::removeUpdateHook(this); // we don't want this _initialized = true; } diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index e841780975..a1fd47731c 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -1397,8 +1397,9 @@ FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping) { return extractFBXGeometry(parseFBX(&modelBuffer), parseMapping(&mappingBuffer)); } -bool addMeshVoxelsOperation(VoxelNode* node, void* extraData) { - if (!node->isLeaf()) { +bool addMeshVoxelsOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; + if (!voxel->isLeaf()) { return true; } FBXMesh& mesh = *static_cast(extraData); @@ -1408,13 +1409,13 @@ bool addMeshVoxelsOperation(VoxelNode* node, void* extraData) { const int VERTICES_PER_FACE = 4; const int VERTEX_COUNT = FACE_COUNT * VERTICES_PER_FACE; const float EIGHT_BIT_MAXIMUM = 255.0f; - glm::vec3 color = glm::vec3(node->getColor()[0], node->getColor()[1], node->getColor()[2]) / EIGHT_BIT_MAXIMUM; + glm::vec3 color = glm::vec3(voxel->getColor()[0], voxel->getColor()[1], voxel->getColor()[2]) / EIGHT_BIT_MAXIMUM; for (int i = 0; i < VERTEX_COUNT; i++) { part.quadIndices.append(part.quadIndices.size()); mesh.colors.append(color); } - glm::vec3 corner = node->getCorner(); - float scale = node->getScale(); + glm::vec3 corner = voxel->getCorner(); + float scale = voxel->getScale(); mesh.vertices.append(glm::vec3(corner.x, corner.y, corner.z)); mesh.vertices.append(glm::vec3(corner.x, corner.y, corner.z + scale)); diff --git a/interface/src/ui/LodToolsDialog.cpp b/interface/src/ui/LodToolsDialog.cpp index 44cd66d0b6..788f7e5561 100644 --- a/interface/src/ui/LodToolsDialog.cpp +++ b/interface/src/ui/LodToolsDialog.cpp @@ -103,7 +103,7 @@ QString LodToolsDialog::getFeedbackText() { // distance feedback float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale(); - float relativeToDefault = voxelSizeScale / DEFAULT_VOXEL_SIZE_SCALE; + float relativeToDefault = voxelSizeScale / DEFAULT_OCTREE_SIZE_SCALE; QString result; if (relativeToDefault > 1.01) { result = QString("%1 further %2").arg(relativeToDefault,8,'f',2).arg(granularityFeedback); @@ -134,7 +134,7 @@ void LodToolsDialog::boundaryLevelValueChanged(int value) { } void LodToolsDialog::resetClicked(bool checked) { - int sliderValue = DEFAULT_VOXEL_SIZE_SCALE / TREE_SCALE; + int sliderValue = DEFAULT_OCTREE_SIZE_SCALE / TREE_SCALE; //sizeScaleValueChanged(sliderValue); _lodSize->setValue(sliderValue); _boundaryLevelAdjust->setValue(0); diff --git a/interface/src/ui/VoxelStatsDialog.cpp b/interface/src/ui/VoxelStatsDialog.cpp index 9def2a2326..4b28a6d315 100644 --- a/interface/src/ui/VoxelStatsDialog.cpp +++ b/interface/src/ui/VoxelStatsDialog.cpp @@ -136,7 +136,7 @@ void VoxelStatsDialog::paintEvent(QPaintEvent* event) { label = _labels[_localVoxelsMemory]; statsValue.str(""); statsValue << - "Nodes RAM: " << VoxelNode::getTotalMemoryUsage() / 1000000.f << "MB " + "Nodes RAM: " << OctreeElement::getTotalMemoryUsage() / 1000000.f << "MB " "Geometry RAM: " << voxels->getVoxelMemoryUsageRAM() / 1000000.f << "MB " << "VBO: " << voxels->getVoxelMemoryUsageVBO() / 1000000.f << "MB "; if (voxels->hasVoxelMemoryUsageGPU()) { @@ -146,9 +146,9 @@ void VoxelStatsDialog::paintEvent(QPaintEvent* event) { // Local Voxels label = _labels[_localVoxels]; - unsigned long localTotal = VoxelNode::getNodeCount(); - unsigned long localInternal = VoxelNode::getInternalNodeCount(); - unsigned long localLeaves = VoxelNode::getLeafNodeCount(); + unsigned long localTotal = OctreeElement::getNodeCount(); + unsigned long localInternal = OctreeElement::getInternalNodeCount(); + unsigned long localLeaves = OctreeElement::getLeafNodeCount(); QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' '); QString localInternalString = locale.toString((uint)localInternal); QString localLeavesString = locale.toString((uint)localLeaves); @@ -177,7 +177,7 @@ void VoxelStatsDialog::paintEvent(QPaintEvent* event) { serverCount++; // calculate server node totals - totalNodes += stats.getTotalVoxels(); + totalNodes += stats.getTotalElements(); totalInternal += stats.getTotalInternal(); totalLeaves += stats.getTotalLeaves(); @@ -313,7 +313,7 @@ void VoxelStatsDialog::showAllVoxelServers() { } } // fall through... since MOST has all of MORE case MORE: { - QString totalString = locale.toString((uint)stats.getTotalVoxels()); + QString totalString = locale.toString((uint)stats.getTotalElements()); QString internalString = locale.toString((uint)stats.getTotalInternal()); QString leavesString = locale.toString((uint)stats.getTotalLeaves()); diff --git a/libraries/avatars/CMakeLists.txt b/libraries/avatars/CMakeLists.txt index 2ae71ec21c..d7dedf5381 100644 --- a/libraries/avatars/CMakeLists.txt +++ b/libraries/avatars/CMakeLists.txt @@ -22,4 +22,5 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) # link in the hifi voxels library +link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR}) diff --git a/libraries/octree/CMakeLists.txt b/libraries/octree/CMakeLists.txt new file mode 100644 index 0000000000..37016966dc --- /dev/null +++ b/libraries/octree/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.8) + +set(ROOT_DIR ../..) +set(MACRO_DIR ${ROOT_DIR}/cmake/macros) + +# setup for find modules +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/") + +set(TARGET_NAME octree) + +find_package(Qt5Widgets REQUIRED) + +include(${MACRO_DIR}/SetupHifiLibrary.cmake) +setup_hifi_library(${TARGET_NAME}) + +qt5_use_modules(${TARGET_NAME} Widgets) + +include(${MACRO_DIR}/IncludeGLM.cmake) +include_glm(${TARGET_NAME} ${ROOT_DIR}) + +include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) + +# link ZLIB +find_package(ZLIB) +include_directories(${ZLIB_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) diff --git a/libraries/voxels/src/AABox.cpp b/libraries/octree/src/AABox.cpp similarity index 100% rename from libraries/voxels/src/AABox.cpp rename to libraries/octree/src/AABox.cpp diff --git a/libraries/voxels/src/AABox.h b/libraries/octree/src/AABox.h similarity index 100% rename from libraries/voxels/src/AABox.h rename to libraries/octree/src/AABox.h diff --git a/libraries/voxels/src/CoverageMap.cpp b/libraries/octree/src/CoverageMap.cpp similarity index 96% rename from libraries/voxels/src/CoverageMap.cpp rename to libraries/octree/src/CoverageMap.cpp index d127bd5804..ace29ccf57 100644 --- a/libraries/voxels/src/CoverageMap.cpp +++ b/libraries/octree/src/CoverageMap.cpp @@ -151,7 +151,7 @@ int CoverageMap::getPolygonCount() const { _remainder.getPolygonCount()); } -VoxelProjectedPolygon* CoverageMap::getPolygon(int index) const { +OctreeProjectedPolygon* CoverageMap::getPolygon(int index) const { int base = 0; if ((index - base) < _topHalf.getPolygonCount()) { return _topHalf.getPolygon((index - base)); @@ -182,7 +182,7 @@ VoxelProjectedPolygon* CoverageMap::getPolygon(int index) const { // possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT -CoverageMapStorageResult CoverageMap::checkMap(VoxelProjectedPolygon* polygon, bool storeIt) { +CoverageMapStorageResult CoverageMap::checkMap(OctreeProjectedPolygon* polygon, bool storeIt) { if (_isRoot) { _checkMapRootCalls++; @@ -371,13 +371,13 @@ void CoverageRegion::erase() { } void CoverageRegion::growPolygonArray() { - VoxelProjectedPolygon** newPolygons = new VoxelProjectedPolygon*[_polygonArraySize + DEFAULT_GROW_SIZE]; + OctreeProjectedPolygon** newPolygons = new OctreeProjectedPolygon*[_polygonArraySize + DEFAULT_GROW_SIZE]; float* newDistances = new float[_polygonArraySize + DEFAULT_GROW_SIZE]; float* newSizes = new float[_polygonArraySize + DEFAULT_GROW_SIZE]; if (_polygons) { - memcpy(newPolygons, _polygons, sizeof(VoxelProjectedPolygon*) * _polygonCount); + memcpy(newPolygons, _polygons, sizeof(OctreeProjectedPolygon*) * _polygonCount); delete[] _polygons; memcpy(newDistances, _polygonDistances, sizeof(float) * _polygonCount); delete[] _polygonDistances; @@ -418,9 +418,9 @@ int CoverageRegion::_outOfOrderPolygon = 0; int CoverageRegion::_clippedPolygons = 0; -bool CoverageRegion::mergeItemsInArray(VoxelProjectedPolygon* seed, bool seedInArray) { +bool CoverageRegion::mergeItemsInArray(OctreeProjectedPolygon* seed, bool seedInArray) { for (int i = 0; i < _polygonCount; i++) { - VoxelProjectedPolygon* otherPolygon = _polygons[i]; + OctreeProjectedPolygon* otherPolygon = _polygons[i]; if (otherPolygon->canMerge(*seed)) { otherPolygon->merge(*seed); @@ -451,7 +451,7 @@ bool CoverageRegion::mergeItemsInArray(VoxelProjectedPolygon* seed, bool seedInA // just handles storage in the array, doesn't test for occlusion or // determining if this is the correct map to store in! -void CoverageRegion::storeInArray(VoxelProjectedPolygon* polygon) { +void CoverageRegion::storeInArray(OctreeProjectedPolygon* polygon) { _currentCoveredBounds.explandToInclude(polygon->getBoundingBox()); @@ -504,7 +504,7 @@ void CoverageRegion::storeInArray(VoxelProjectedPolygon* polygon) { -CoverageMapStorageResult CoverageRegion::checkRegion(VoxelProjectedPolygon* polygon, const BoundingBox& polygonBox, bool storeIt) { +CoverageMapStorageResult CoverageRegion::checkRegion(OctreeProjectedPolygon* polygon, const BoundingBox& polygonBox, bool storeIt) { CoverageMapStorageResult result = DOESNT_FIT; @@ -517,7 +517,7 @@ CoverageMapStorageResult CoverageRegion::checkRegion(VoxelProjectedPolygon* poly } else { // check to make sure this polygon isn't occluded by something at this level for (int i = 0; i < _polygonCount; i++) { - VoxelProjectedPolygon* polygonAtThisLevel = _polygons[i]; + OctreeProjectedPolygon* polygonAtThisLevel = _polygons[i]; // Check to make sure that the polygon in question is "behind" the polygon in the list // otherwise, we don't need to test it's occlusion (although, it means we've potentially diff --git a/libraries/voxels/src/CoverageMap.h b/libraries/octree/src/CoverageMap.h similarity index 84% rename from libraries/voxels/src/CoverageMap.h rename to libraries/octree/src/CoverageMap.h index 46daf646c9..9cdda579e8 100644 --- a/libraries/voxels/src/CoverageMap.h +++ b/libraries/octree/src/CoverageMap.h @@ -1,5 +1,5 @@ // -// CoverageMap.h - 2D CoverageMap Quad tree for storage of VoxelProjectedPolygons +// CoverageMap.h - 2D CoverageMap Quad tree for storage of OctreeProjectedPolygons // hifi // // Added by Brad Hefta-Gaub on 06/11/13. @@ -10,7 +10,7 @@ #define _COVERAGE_MAP_ #include -#include "VoxelProjectedPolygon.h" +#include "OctreeProjectedPolygon.h" typedef enum {STORED, OCCLUDED, DOESNT_FIT, NOT_STORED} CoverageMapStorageResult; typedef enum {TOP_HALF, BOTTOM_HALF, LEFT_HALF, RIGHT_HALF, REMAINDER} RegionName; @@ -22,8 +22,8 @@ public: CoverageRegion(BoundingBox boundingBox, bool isRoot, bool managePolygons = true, RegionName regionName = REMAINDER); ~CoverageRegion(); - CoverageMapStorageResult checkRegion(VoxelProjectedPolygon* polygon, const BoundingBox& polygonBox, bool storeIt); - void storeInArray(VoxelProjectedPolygon* polygon); + CoverageMapStorageResult checkRegion(OctreeProjectedPolygon* polygon, const BoundingBox& polygonBox, bool storeIt); + void storeInArray(OctreeProjectedPolygon* polygon); bool contains(const BoundingBox& box) const { return _myBoundingBox.contains(box); }; void erase(); // erase the coverage region @@ -41,7 +41,7 @@ public: const char* getRegionName() const; int getPolygonCount() const { return _polygonCount; }; - VoxelProjectedPolygon* getPolygon(int index) const { return _polygons[index]; }; + OctreeProjectedPolygon* getPolygon(int index) const { return _polygons[index]; }; private: void init(); @@ -53,7 +53,7 @@ private: RegionName _regionName; int _polygonCount; // how many polygons at this level int _polygonArraySize; // how much room is there to store polygons at this level - VoxelProjectedPolygon** _polygons; + OctreeProjectedPolygon** _polygons; // we will use one or the other of these depending on settings in the code. float* _polygonDistances; @@ -61,7 +61,7 @@ private: void growPolygonArray(); static const int DEFAULT_GROW_SIZE = 100; - bool mergeItemsInArray(VoxelProjectedPolygon* seed, bool seedInArray); + bool mergeItemsInArray(OctreeProjectedPolygon* seed, bool seedInArray); }; @@ -77,7 +77,7 @@ public: CoverageMap(BoundingBox boundingBox = ROOT_BOUNDING_BOX, bool isRoot = IS_ROOT, bool managePolygons = true); ~CoverageMap(); - CoverageMapStorageResult checkMap(VoxelProjectedPolygon* polygon, bool storeIt = true); + CoverageMapStorageResult checkMap(OctreeProjectedPolygon* polygon, bool storeIt = true); BoundingBox getChildBoundingBox(int childIndex); @@ -87,7 +87,7 @@ public: static bool wantDebugging; int getPolygonCount() const; - VoxelProjectedPolygon* getPolygon(int index) const; + OctreeProjectedPolygon* getPolygon(int index) const; CoverageMap* getChild(int childIndex) const { return _childMaps[childIndex]; }; private: diff --git a/libraries/voxels/src/CoverageMapV2.cpp b/libraries/octree/src/CoverageMapV2.cpp similarity index 98% rename from libraries/voxels/src/CoverageMapV2.cpp rename to libraries/octree/src/CoverageMapV2.cpp index 1404dc5d15..8353e02eef 100644 --- a/libraries/voxels/src/CoverageMapV2.cpp +++ b/libraries/octree/src/CoverageMapV2.cpp @@ -114,7 +114,7 @@ BoundingBox CoverageMapV2::getChildBoundingBox(int childIndex) { } // possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT -CoverageMapV2StorageResult CoverageMapV2::checkMap(const VoxelProjectedPolygon* polygon, bool storeIt) { +CoverageMapV2StorageResult CoverageMapV2::checkMap(const OctreeProjectedPolygon* polygon, bool storeIt) { assert(_isRoot); // you can only call this on the root map!!! _checkMapRootCalls++; @@ -152,7 +152,7 @@ CoverageMapV2StorageResult CoverageMapV2::checkMap(const VoxelProjectedPolygon* return V2_NOT_STORED; // unless we weren't asked to store it, then we didn't } -void CoverageMapV2::recurseMap(const VoxelProjectedPolygon* polygon, bool storeIt, +void CoverageMapV2::recurseMap(const OctreeProjectedPolygon* polygon, bool storeIt, bool& seenOccludedMapNodes, bool& allOccludedMapNodesCovered) { // if we are really small, then we act like we don't intersect, this allows us to stop diff --git a/libraries/voxels/src/CoverageMapV2.h b/libraries/octree/src/CoverageMapV2.h similarity index 88% rename from libraries/voxels/src/CoverageMapV2.h rename to libraries/octree/src/CoverageMapV2.h index bce54263c9..e7c2eed4fe 100644 --- a/libraries/voxels/src/CoverageMapV2.h +++ b/libraries/octree/src/CoverageMapV2.h @@ -1,5 +1,5 @@ // -// CoverageMapV2.h - 2D CoverageMapV2 Quad tree for storage of VoxelProjectedPolygons +// CoverageMapV2.h - 2D CoverageMapV2 Quad tree for storage of OctreeProjectedPolygons // hifi // // Added by Brad Hefta-Gaub on 06/11/13. @@ -11,7 +11,7 @@ #include -#include "VoxelProjectedPolygon.h" +#include "OctreeProjectedPolygon.h" typedef enum { V2_DOESNT_FIT, V2_STORED, V2_NOT_STORED, @@ -35,7 +35,7 @@ public: bool isCovered = false, float coverageDistance = NOT_COVERED); ~CoverageMapV2(); - CoverageMapV2StorageResult checkMap(const VoxelProjectedPolygon* polygon, bool storeIt = true); + CoverageMapV2StorageResult checkMap(const OctreeProjectedPolygon* polygon, bool storeIt = true); BoundingBox getChildBoundingBox(int childIndex); const BoundingBox& getBoundingBox() const { return _myBoundingBox; }; @@ -48,7 +48,7 @@ public: private: - void recurseMap(const VoxelProjectedPolygon* polygon, bool storeIt, + void recurseMap(const OctreeProjectedPolygon* polygon, bool storeIt, bool& seenOccludedMapNodes, bool& allOccludedMapNodesCovered); void init(); diff --git a/libraries/voxels/src/JurisdictionListener.cpp b/libraries/octree/src/JurisdictionListener.cpp similarity index 100% rename from libraries/voxels/src/JurisdictionListener.cpp rename to libraries/octree/src/JurisdictionListener.cpp diff --git a/libraries/voxels/src/JurisdictionListener.h b/libraries/octree/src/JurisdictionListener.h similarity index 100% rename from libraries/voxels/src/JurisdictionListener.h rename to libraries/octree/src/JurisdictionListener.h diff --git a/libraries/voxels/src/JurisdictionMap.cpp b/libraries/octree/src/JurisdictionMap.cpp similarity index 99% rename from libraries/voxels/src/JurisdictionMap.cpp rename to libraries/octree/src/JurisdictionMap.cpp index 00e469b03f..bd2f703cdb 100644 --- a/libraries/voxels/src/JurisdictionMap.cpp +++ b/libraries/octree/src/JurisdictionMap.cpp @@ -12,9 +12,9 @@ #include #include +#include #include "JurisdictionMap.h" -#include "VoxelNode.h" // standard assignment diff --git a/libraries/voxels/src/JurisdictionMap.h b/libraries/octree/src/JurisdictionMap.h similarity index 100% rename from libraries/voxels/src/JurisdictionMap.h rename to libraries/octree/src/JurisdictionMap.h diff --git a/libraries/voxels/src/JurisdictionSender.cpp b/libraries/octree/src/JurisdictionSender.cpp similarity index 100% rename from libraries/voxels/src/JurisdictionSender.cpp rename to libraries/octree/src/JurisdictionSender.cpp diff --git a/libraries/voxels/src/JurisdictionSender.h b/libraries/octree/src/JurisdictionSender.h similarity index 98% rename from libraries/voxels/src/JurisdictionSender.h rename to libraries/octree/src/JurisdictionSender.h index f223621b87..1be8128ed0 100644 --- a/libraries/voxels/src/JurisdictionSender.h +++ b/libraries/octree/src/JurisdictionSender.h @@ -5,7 +5,7 @@ // Created by Brad Hefta-Gaub on 8/12/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // -// Voxel Packet Sender +// Jurisdiction Sender // #ifndef __shared__JurisdictionSender__ diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp new file mode 100644 index 0000000000..42dfb06809 --- /dev/null +++ b/libraries/octree/src/Octree.cpp @@ -0,0 +1,1512 @@ +// +// Octree.cpp +// hifi +// +// Created by Stephen Birarda on 3/13/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifdef _WIN32 +#define _USE_MATH_DEFINES +#endif + +#include +#include +#include +#include // to load voxels from file + +#include + +#include +#include +#include + +#include "CoverageMap.h" +#include +#include "OctalCode.h" +#include +#include + +//#include "Tags.h" + +#include "ViewFrustum.h" +#include "OctreeConstants.h" +#include "OctreeElementBag.h" +#include "Octree.h" + +float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) { + return voxelSizeScale / powf(2, renderLevel); +} + +Octree::Octree(bool shouldReaverage) : + _isDirty(true), + _shouldReaverage(shouldReaverage), + _stopImport(false) { + _rootNode = NULL; + + pthread_mutex_init(&_encodeSetLock, NULL); + pthread_mutex_init(&_deleteSetLock, NULL); + pthread_mutex_init(&_deletePendingSetLock, NULL); +} + +Octree::~Octree() { + // delete the children of the root node + // this recursively deletes the tree + delete _rootNode; + + pthread_mutex_destroy(&_encodeSetLock); + pthread_mutex_destroy(&_deleteSetLock); + pthread_mutex_destroy(&_deletePendingSetLock); +} + +// Recurses voxel tree calling the RecurseOctreeOperation function for each node. +// stops recursion if operation function returns false. +void Octree::recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData) { + recurseNodeWithOperation(_rootNode, operation, extraData); +} + +// Recurses voxel node with an operation function +void Octree::recurseNodeWithOperation(OctreeElement* node, RecurseOctreeOperation operation, void* extraData, + int recursionCount) { + if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { + qDebug() << "Octree::recurseNodeWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n"; + return; + } + + if (operation(node, extraData)) { + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + OctreeElement* child = node->getChildAtIndex(i); + if (child) { + recurseNodeWithOperation(child, operation, extraData, recursionCount+1); + } + } + } +} + +// Recurses voxel tree calling the RecurseOctreeOperation function for each node. +// stops recursion if operation function returns false. +void Octree::recurseTreeWithOperationDistanceSorted(RecurseOctreeOperation operation, + const glm::vec3& point, void* extraData) { + + recurseNodeWithOperationDistanceSorted(_rootNode, operation, point, extraData); +} + +// Recurses voxel node with an operation function +void Octree::recurseNodeWithOperationDistanceSorted(OctreeElement* node, RecurseOctreeOperation operation, + const glm::vec3& point, void* extraData, int recursionCount) { + + if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { + qDebug() << "Octree::recurseNodeWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n"; + return; + } + + if (operation(node, extraData)) { + // determine the distance sorted order of our children + OctreeElement* sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + int currentCount = 0; + + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + OctreeElement* childNode = node->getChildAtIndex(i); + if (childNode) { + // chance to optimize, doesn't need to be actual distance!! Could be distance squared + float distanceSquared = childNode->distanceSquareToPoint(point); + //qDebug("recurseNodeWithOperationDistanceSorted() CHECKING child[%d] point=%f,%f center=%f,%f distance=%f...\n", i, point.x, point.y, center.x, center.y, distance); + //childNode->printDebugDetails(""); + currentCount = insertIntoSortedArrays((void*)childNode, distanceSquared, i, + (void**)&sortedChildren, (float*)&distancesToChildren, + (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); + } + } + + for (int i = 0; i < currentCount; i++) { + OctreeElement* childNode = sortedChildren[i]; + if (childNode) { + //qDebug("recurseNodeWithOperationDistanceSorted() PROCESSING child[%d] distance=%f...\n", i, distancesToChildren[i]); + //childNode->printDebugDetails(""); + recurseNodeWithOperationDistanceSorted(childNode, operation, point, extraData); + } + } + } +} + + +OctreeElement* Octree::nodeForOctalCode(OctreeElement* ancestorNode, + const unsigned char* needleCode, OctreeElement** parentOfFoundNode) const { + // special case for NULL octcode + if (needleCode == NULL) { + return _rootNode; + } + + // find the appropriate branch index based on this ancestorNode + if (*needleCode > 0) { + int branchForNeedle = branchIndexWithDescendant(ancestorNode->getOctalCode(), needleCode); + OctreeElement* childNode = ancestorNode->getChildAtIndex(branchForNeedle); + + if (childNode) { + if (*childNode->getOctalCode() == *needleCode) { + + // If the caller asked for the parent, then give them that too... + if (parentOfFoundNode) { + *parentOfFoundNode = ancestorNode; + } + // the fact that the number of sections is equivalent does not always guarantee + // that this is the same node, however due to the recursive traversal + // we know that this is our node + return childNode; + } else { + // we need to go deeper + return nodeForOctalCode(childNode, needleCode, parentOfFoundNode); + } + } + } + + // we've been given a code we don't have a node for + // return this node as the last created parent + return ancestorNode; +} + +// returns the node created! +OctreeElement* Octree::createMissingNode(OctreeElement* lastParentNode, const unsigned char* 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 + // breaking up the leaf first, which will also create a child path + if (lastParentNode->requiresSplit()) { + lastParentNode->splitChildren(); + } 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); + } + + // This works because we know we traversed down the same tree so if the length is the same, then the whole code is the same + if (*lastParentNode->getChildAtIndex(indexOfNewChild)->getOctalCode() == *codeToReach) { + return lastParentNode->getChildAtIndex(indexOfNewChild); + } else { + return createMissingNode(lastParentNode->getChildAtIndex(indexOfNewChild), codeToReach); + } +} + +int Octree::readNodeData(OctreeElement* destinationNode, const unsigned char* nodeData, int bytesLeftToRead, + ReadBitstreamToTreeParams& args) { + // 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 = sizeof(colorInPacketMask); + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + // check the colors mask to see if we have a child to color in + if (oneAtBit(colorInPacketMask, i)) { + // create the child if it doesn't exist + if (!destinationNode->getChildAtIndex(i)) { + destinationNode->addChildAtIndex(i); + if (destinationNode->isDirty()) { + _isDirty = true; + } + } + + OctreeElement* childNodeAt = destinationNode->getChildAtIndex(i); + bool nodeWasDirty = false; + bool nodeIsDirty = false; + if (childNodeAt) { + nodeWasDirty = childNodeAt->isDirty(); + bytesRead += childNodeAt->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead, args); + childNodeAt->setSourceUUID(args.sourceUUID); + + // if we had a local version of the node already, it's possible that we have it in the VBO but + // with the same color data, so this won't count as a change. To address this we check the following + + // XXXBHG - Do something here... + if (!childNodeAt->isDirty() + /**&& !childNodeAt->isKnownBufferIndex() && childNodeAt->getShouldRender() **/ + ) { + childNodeAt->setDirtyBit(); // force dirty! + } + + nodeIsDirty = childNodeAt->isDirty(); + } + if (nodeIsDirty) { + _isDirty = true; + } + } + } + + // give this destination node the child mask from the packet + unsigned char childrenInTreeMask = args.includeExistsBits ? *(nodeData + bytesRead) : ALL_CHILDREN_ASSUMED_TO_EXIST; + unsigned char childMask = *(nodeData + bytesRead + (args.includeExistsBits ? sizeof(childrenInTreeMask) : 0)); + + int childIndex = 0; + bytesRead += args.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 + + if (oneAtBit(childMask, childIndex)) { + if (!destinationNode->getChildAtIndex(childIndex)) { + // add a child at that index, if it doesn't exist + destinationNode->addChildAtIndex(childIndex); + bool nodeIsDirty = destinationNode->isDirty(); + if (nodeIsDirty) { + _isDirty = true; + } + } + + // tell the child to read the subsequent data + bytesRead += readNodeData(destinationNode->getChildAtIndex(childIndex), + nodeData + bytesRead, bytesLeftToRead - bytesRead, args); + } + childIndex++; + } + + if (args.includeExistsBits) { + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + // now also check the childrenInTreeMask, if the mask is missing the bit, then it means we need to delete this child + // subtree/node, because it shouldn't actually exist in the tree. + if (!oneAtBit(childrenInTreeMask, i) && destinationNode->getChildAtIndex(i)) { + destinationNode->safeDeepDeleteChildAtIndex(i); + _isDirty = true; // by definition! + } + } + } + return bytesRead; +} + +void Octree::readBitstreamToTree(const unsigned char * bitstream, unsigned long int bufferSizeBytes, + ReadBitstreamToTreeParams& args) { + int bytesRead = 0; + const unsigned char* bitstreamAt = bitstream; + + // If destination node is not included, set it to root + if (!args.destinationNode) { + args.destinationNode = _rootNode; + } + + // Keep looping through the buffer calling readNodeData() this allows us to pack multiple root-relative Octal codes + // into a single network packet. readNodeData() basically goes down a tree from the root, and fills things in from there + // if there are more bytes after that, it's assumed to be another root relative tree + + while (bitstreamAt < bitstream + bufferSizeBytes) { + OctreeElement* bitstreamRootNode = nodeForOctalCode(args.destinationNode, (unsigned char *)bitstreamAt, NULL); + if (*bitstreamAt != *bitstreamRootNode->getOctalCode()) { + // if the octal code returned is not on the same level as + // the code being searched for, we have OctreeElements to create + + // Note: we need to create this node relative to root, because we're assuming that the bitstream for the initial + // octal code is always relative to root! + bitstreamRootNode = createMissingNode(args.destinationNode, (unsigned char*) bitstreamAt); + if (bitstreamRootNode->isDirty()) { + _isDirty = true; + } + } + + int octalCodeBytes = bytesRequiredForCodeLength(*bitstreamAt); + int theseBytesRead = 0; + theseBytesRead += octalCodeBytes; + + theseBytesRead += readNodeData(bitstreamRootNode, bitstreamAt + octalCodeBytes, + bufferSizeBytes - (bytesRead + octalCodeBytes), args); + + // skip bitstream to new startPoint + bitstreamAt += theseBytesRead; + bytesRead += theseBytesRead; + + if (args.wantImportProgress) { + emit importProgress((100 * (bitstreamAt - bitstream)) / bufferSizeBytes); + } + } +} + +void Octree::deleteOctreeElementAt(float x, float y, float z, float s) { + unsigned char* octalCode = pointToOctalCode(x,y,z,s); + deleteOctalCodeFromTree(octalCode); + delete[] octalCode; // cleanup memory +} + +class DeleteOctalCodeFromTreeArgs { +public: + bool collapseEmptyTrees; + const unsigned char* codeBuffer; + int lengthOfCode; + bool deleteLastChild; + bool pathChanged; +}; + +// Note: uses the codeColorBuffer format, but the color's are ignored, because +// this only finds and deletes the node from the tree. +void Octree::deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees) { + // recurse the tree while decoding the codeBuffer, once you find the node in question, recurse + // back and implement color reaveraging, and marking of lastChanged + DeleteOctalCodeFromTreeArgs args; + args.collapseEmptyTrees = collapseEmptyTrees; + args.codeBuffer = codeBuffer; + args.lengthOfCode = numberOfThreeBitSectionsInCode(codeBuffer); + args.deleteLastChild = false; + args.pathChanged = false; + + OctreeElement* node = _rootNode; + + // We can't encode and delete nodes at the same time, so we guard against deleting any node that is actively + // being encoded. And we stick that code on our pendingDelete list. + if (isEncoding(codeBuffer)) { + queueForLaterDelete(codeBuffer); + } else { + startDeleting(codeBuffer); + deleteOctalCodeFromTreeRecursion(node, &args); + doneDeleting(codeBuffer); + } +} + +void Octree::deleteOctalCodeFromTreeRecursion(OctreeElement* node, void* extraData) { + DeleteOctalCodeFromTreeArgs* args = (DeleteOctalCodeFromTreeArgs*)extraData; + + int lengthOfNodeCode = numberOfThreeBitSectionsInCode(node->getOctalCode()); + + // Since we traverse the tree in code order, we know that if our code + // matches, then we've reached our target node. + if (lengthOfNodeCode == args->lengthOfCode) { + // we've reached our target, depending on how we're called we may be able to operate on it + // it here, we need to recurse up, and delete it there. So we handle these cases the same to keep + // the logic consistent. + args->deleteLastChild = true; + return; + } + + // Ok, we know we haven't reached our target node yet, so keep looking + int childIndex = branchIndexWithDescendant(node->getOctalCode(), args->codeBuffer); + OctreeElement* childNode = node->getChildAtIndex(childIndex); + + // If there is no child at the target location, and the current parent node is a colored leaf, + // then it means we were asked to delete a child out of a larger leaf voxel. + // We support this by breaking up the parent voxel into smaller pieces. + if (!childNode && node->requiresSplit()) { + // we need to break up ancestors until we get to the right level + OctreeElement* ancestorNode = node; + while (true) { + int index = branchIndexWithDescendant(ancestorNode->getOctalCode(), args->codeBuffer); + + // we end up with all the children, even the one we want to delete + ancestorNode->splitChildren(); + + int lengthOfAncestorNode = numberOfThreeBitSectionsInCode(ancestorNode->getOctalCode()); + + // If we've reached the parent of the target, then stop breaking up children + if (lengthOfAncestorNode == (args->lengthOfCode - 1)) { + + // since we created all the children when we split, we need to delete this target one + ancestorNode->deleteChildAtIndex(index); + break; + } + ancestorNode = ancestorNode->getChildAtIndex(index); + } + _isDirty = true; + args->pathChanged = true; + + // ends recursion, unwinds up stack + return; + } + + // if we don't have a child and we reach this point, then we actually know that the parent + // isn't a colored leaf, and the child branch doesn't exist, so there's nothing to do below and + // we can safely return, ending the recursion and unwinding + if (!childNode) { + return; + } + + // If we got this far then we have a child for the branch we're looking for, but we're not there yet + // recurse till we get there + deleteOctalCodeFromTreeRecursion(childNode, args); + + // If the lower level determined it needs to be deleted, then we should delete now. + if (args->deleteLastChild) { + node->deleteChildAtIndex(childIndex); // note: this will track dirtiness and lastChanged for this node + + // track our tree dirtiness + _isDirty = true; + + // track that path has changed + args->pathChanged = true; + + // If we're in collapseEmptyTrees mode, and this was the last child of this node, then we also want + // to delete this node. This will collapse the empty tree above us. + if (args->collapseEmptyTrees && node->getChildCount() == 0) { + // Can't delete the root this way. + if (node == _rootNode) { + args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything + } + } else { + args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything + } + } + + // If the lower level did some work, then we need to let this node know, so it can + // do any bookkeeping it wants to, like color re-averaging, time stamp marking, etc + if (args->pathChanged) { + node->handleSubtreeChanged(this); + } +} + +void Octree::eraseAllOctreeElements() { + // XXXBHG Hack attack - is there a better way to erase the voxel tree? + delete _rootNode; // this will recurse and delete all children + _rootNode = createNewElement(); + _isDirty = true; +} + +void Octree::processRemoveOctreeElementsBitstream(const unsigned char* bitstream, int bufferSizeBytes) { + //unsigned short int itemNumber = (*((unsigned short int*)&bitstream[sizeof(PACKET_HEADER)])); + + int numBytesPacketHeader = numBytesForPacketHeader(bitstream); + unsigned short int sequence = (*((unsigned short int*)(bitstream + numBytesPacketHeader))); + uint64_t sentAt = (*((uint64_t*)(bitstream + numBytesPacketHeader + sizeof(sequence)))); + + int atByte = numBytesPacketHeader + sizeof(sequence) + sizeof(sentAt); + + unsigned char* voxelCode = (unsigned char*)&bitstream[atByte]; + while (atByte < bufferSizeBytes) { + int maxSize = bufferSizeBytes - atByte; + int codeLength = numberOfThreeBitSectionsInCode(voxelCode, maxSize); + + if (codeLength == OVERFLOWED_OCTCODE_BUFFER) { + printf("WARNING! Got remove voxel bitstream that would overflow buffer in numberOfThreeBitSectionsInCode(), "); + printf("bailing processing of packet!\n"); + break; + } + int voxelDataSize = bytesRequiredForCodeLength(codeLength) + SIZE_OF_COLOR_DATA; + + if (atByte + voxelDataSize <= bufferSizeBytes) { + deleteOctalCodeFromTree(voxelCode, COLLAPSE_EMPTY_TREE); + voxelCode += voxelDataSize; + atByte += voxelDataSize; + } else { + printf("WARNING! Got remove voxel bitstream that would overflow buffer, bailing processing!\n"); + break; + } + } +} + +// Note: this is an expensive call. Don't call it unless you really need to reaverage the entire tree (from startNode) +void Octree::reaverageOctreeElements(OctreeElement* startNode) { + if (startNode == NULL) { + startNode = getRoot(); + } + // if our tree is a reaveraging tree, then we do this, otherwise we don't do anything + if (_shouldReaverage) { + static int recursionCount; + if (startNode == _rootNode) { + recursionCount = 0; + } else { + recursionCount++; + } + if (recursionCount > UNREASONABLY_DEEP_RECURSION) { + qDebug("Octree::reaverageOctreeElements()... bailing out of UNREASONABLY_DEEP_RECURSION\n"); + recursionCount--; + return; + } + + bool hasChildren = false; + + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + if (startNode->getChildAtIndex(i)) { + reaverageOctreeElements(startNode->getChildAtIndex(i)); + hasChildren = true; + } + } + + // collapseIdenticalLeaves() returns true if it collapses the leaves + // in which case we don't need to set the average color + if (hasChildren && !startNode->collapseChildren()) { + startNode->calculateAverageFromChildren(); + } + recursionCount--; + } +} + +OctreeElement* Octree::getOctreeElementAt(float x, float y, float z, float s) const { + unsigned char* octalCode = pointToOctalCode(x,y,z,s); + OctreeElement* node = nodeForOctalCode(_rootNode, octalCode, NULL); + if (*node->getOctalCode() != *octalCode) { + node = NULL; + } + delete[] octalCode; // cleanup memory +#ifdef HAS_AUDIT_CHILDREN + if (node) { + node->auditChildren("Octree::getOctreeElementAt()"); + } +#endif // def HAS_AUDIT_CHILDREN + return node; +} + +void Octree::createOctreeElement(float x, float y, float z, float s, + unsigned char red, unsigned char green, unsigned char blue, bool destructive) { + + // XXXBHG Octree cleanup + + //unsigned char* voxelData = pointToVoxel(x,y,z,s,red,green,blue); + //this->readCodeColorBufferToTree(voxelData, destructive); + //delete[] voxelData; +} + +// combines the ray cast arguments into a single object +class RayArgs { +public: + glm::vec3 origin; + glm::vec3 direction; + OctreeElement*& node; + float& distance; + BoxFace& face; + bool found; +}; + +bool findRayIntersectionOp(OctreeElement* node, void* extraData) { + RayArgs* args = static_cast(extraData); + AABox box = node->getAABox(); + float distance; + BoxFace face; + if (!box.findRayIntersection(args->origin, args->direction, distance, face)) { + return false; + } + if (!node->isLeaf()) { + return true; // recurse on children + } + distance *= TREE_SCALE; + // XXXBHG - we used to test node->isColored(), but that no longer exists in the octree + // instead I switched this to isLeaf() which seems more correct. + if (node->isLeaf() && (!args->found || distance < args->distance)) { + args->node = node; + args->distance = distance; + args->face = face; + args->found = true; + } + return false; +} + +bool Octree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + OctreeElement*& node, float& distance, BoxFace& face) { + RayArgs args = { origin / (float)TREE_SCALE, direction, node, distance, face }; + recurseTreeWithOperation(findRayIntersectionOp, &args); + return args.found; +} + +class SphereArgs { +public: + glm::vec3 center; + float radius; + glm::vec3& penetration; + bool found; +}; + +bool findSpherePenetrationOp(OctreeElement* node, void* extraData) { + SphereArgs* args = static_cast(extraData); + + // coarse check against bounds + const AABox& box = node->getAABox(); + if (!box.expandedContains(args->center, args->radius)) { + return false; + } + if (!node->isLeaf()) { + return true; // recurse on children + } + // XXXBHG - used to be isColored() + if (node->isLeaf()) { + glm::vec3 nodePenetration; + if (box.findSpherePenetration(args->center, args->radius, nodePenetration)) { + args->penetration = addPenetrations(args->penetration, nodePenetration * (float)TREE_SCALE); + args->found = true; + } + } + return false; +} + +bool Octree::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) { + SphereArgs args = { center / (float)TREE_SCALE, radius / TREE_SCALE, penetration }; + penetration = glm::vec3(0.0f, 0.0f, 0.0f); + recurseTreeWithOperation(findSpherePenetrationOp, &args); + return args.found; +} + +class CapsuleArgs { +public: + glm::vec3 start; + glm::vec3 end; + float radius; + glm::vec3& penetration; + bool found; +}; + +bool findCapsulePenetrationOp(OctreeElement* node, void* extraData) { + CapsuleArgs* args = static_cast(extraData); + + // coarse check against bounds + const AABox& box = node->getAABox(); + if (!box.expandedIntersectsSegment(args->start, args->end, args->radius)) { + return false; + } + if (!node->isLeaf()) { + return true; // recurse on children + } + // XXXBHG - used to be isColored(), may be an issue. + if (node->isLeaf()) { + glm::vec3 nodePenetration; + if (box.findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) { + args->penetration = addPenetrations(args->penetration, nodePenetration * (float)TREE_SCALE); + args->found = true; + } + } + return false; +} + +bool Octree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) { + CapsuleArgs args = { start / (float)TREE_SCALE, end / (float)TREE_SCALE, radius / TREE_SCALE, penetration }; + penetration = glm::vec3(0.0f, 0.0f, 0.0f); + recurseTreeWithOperation(findCapsulePenetrationOp, &args); + return args.found; +} + +int Octree::encodeTreeBitstream(OctreeElement* node, + OctreePacketData* packetData, OctreeElementBag& bag, + EncodeBitstreamParams& params) { + + // How many bytes have we written so far at this level; + int bytesWritten = 0; + + // you can't call this without a valid node + if (!node) { + qDebug("WARNING! encodeTreeBitstream() called with node=NULL\n"); + params.stopReason = EncodeBitstreamParams::NULL_NODE; + return bytesWritten; + } + + startEncoding(node); + + // If we're at a node that is out of view, then we can return, because no nodes below us will be in view! + if (params.viewFrustum && !node->isInView(*params.viewFrustum)) { + doneEncoding(node); + params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; + return bytesWritten; + } + + // write the octal code + bool roomForOctalCode = false; // assume the worst + int codeLength; + if (params.chopLevels) { + unsigned char* newCode = chopOctalCode(node->getOctalCode(), params.chopLevels); + roomForOctalCode = packetData->startSubTree(newCode); + + if (newCode) { + delete newCode; + } else { + codeLength = 1; + } + } else { + roomForOctalCode = packetData->startSubTree(node->getOctalCode()); + codeLength = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(node->getOctalCode())); + } + + // If the octalcode couldn't fit, then we can return, because no nodes below us will fit... + if (!roomForOctalCode) { + doneEncoding(node); + bag.insert(node); // add the node back to the bag so it will eventually get included + params.stopReason = EncodeBitstreamParams::DIDNT_FIT; + return bytesWritten; + } + + bytesWritten += codeLength; // keep track of byte count + + int currentEncodeLevel = 0; + + // record some stats, this is the one node that we won't record below in the recursion function, so we need to + // track it here + if (params.stats) { + params.stats->traversed(node); + } + + int childBytesWritten = encodeTreeBitstreamRecursion(node, packetData, bag, params, currentEncodeLevel); + + // if childBytesWritten == 1 then something went wrong... that's not possible + assert(childBytesWritten != 1); + + // if includeColor and childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some reason + // couldn't be written... so reset them here... This isn't true for the non-color included case + if (params.includeColor && childBytesWritten == 2) { + childBytesWritten = 0; + //params.stopReason = EncodeBitstreamParams::UNKNOWN; // possibly should be DIDNT_FIT... + } + + // if we wrote child bytes, then return our result of all bytes written + if (childBytesWritten) { + bytesWritten += childBytesWritten; + } else { + // otherwise... if we didn't write any child bytes, then pretend like we also didn't write our octal code + bytesWritten = 0; + //params.stopReason = EncodeBitstreamParams::DIDNT_FIT; + } + + if (bytesWritten == 0) { + packetData->discardSubTree(); + } else { + packetData->endSubTree(); + } + + doneEncoding(node); + + return bytesWritten; +} + +int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, + OctreePacketData* packetData, OctreeElementBag& bag, + EncodeBitstreamParams& params, int& currentEncodeLevel) const { + // How many bytes have we written so far at this level; + int bytesAtThisLevel = 0; + + // you can't call this without a valid node + if (!node) { + qDebug("WARNING! encodeTreeBitstreamRecursion() called with node=NULL\n"); + params.stopReason = EncodeBitstreamParams::NULL_NODE; + return bytesAtThisLevel; + } + + // Keep track of how deep we've encoded. + currentEncodeLevel++; + + params.maxLevelReached = std::max(currentEncodeLevel,params.maxLevelReached); + + // If we've reached our max Search Level, then stop searching. + if (currentEncodeLevel >= params.maxEncodeLevel) { + params.stopReason = EncodeBitstreamParams::TOO_DEEP; + return bytesAtThisLevel; + } + + // If we've been provided a jurisdiction map, then we need to honor it. + if (params.jurisdictionMap) { + // here's how it works... if we're currently above our root jurisdiction, then we proceed normally. + // but once we're in our own jurisdiction, then we need to make sure we're not below it. + if (JurisdictionMap::BELOW == params.jurisdictionMap->isMyJurisdiction(node->getOctalCode(), CHECK_NODE_ONLY)) { + params.stopReason = EncodeBitstreamParams::OUT_OF_JURISDICTION; + return bytesAtThisLevel; + } + } + + // caller can pass NULL as viewFrustum if they want everything + if (params.viewFrustum) { + float distance = node->distanceToCamera(*params.viewFrustum); + float boundaryDistance = boundaryDistanceForRenderLevel(node->getLevel() + params.boundaryLevelAdjust, + params.octreeElementSizeScale); + + // If we're too far away for our render level, then just return + if (distance >= boundaryDistance) { + if (params.stats) { + params.stats->skippedDistance(node); + } + params.stopReason = EncodeBitstreamParams::LOD_SKIP; + return bytesAtThisLevel; + } + + // If we're at a node that is out of view, then we can return, because no nodes below us will be in view! + // although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if + // we're out of view + if (!node->isInView(*params.viewFrustum)) { + if (params.stats) { + params.stats->skippedOutOfView(node); + } + params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; + return bytesAtThisLevel; + } + + // Ok, we are in view, but if we're in delta mode, then we also want to make sure we weren't already in view + // because we don't send nodes from the previously know in view frustum. + bool wasInView = false; + + if (params.deltaViewFrustum && params.lastViewFrustum) { + ViewFrustum::location location = node->inFrustum(*params.lastViewFrustum); + + // If we're a leaf, then either intersect or inside is considered "formerly in view" + if (node->isLeaf()) { + wasInView = location != ViewFrustum::OUTSIDE; + } else { + wasInView = location == ViewFrustum::INSIDE; + } + + // If we were in view, double check that we didn't switch LOD visibility... namely, the was in view doesn't + // tell us if it was so small we wouldn't have rendered it. Which may be the case. And we may have moved closer + // to it, and so therefore it may now be visible from an LOD perspective, in which case we don't consider it + // as "was in view"... + if (wasInView) { + float distance = node->distanceToCamera(*params.lastViewFrustum); + float boundaryDistance = boundaryDistanceForRenderLevel(node->getLevel() + params.boundaryLevelAdjust, + params.octreeElementSizeScale); + if (distance >= boundaryDistance) { + // This would have been invisible... but now should be visible (we wouldn't be here otherwise)... + wasInView = false; + } + } + } + + // If we were previously in the view, then we normally will return out of here and stop recursing. But + // if we're in deltaViewFrustum mode, and this node has changed since it was last sent, then we do + // need to send it. + if (wasInView && !(params.deltaViewFrustum && node->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))) { + if (params.stats) { + params.stats->skippedWasInView(node); + } + params.stopReason = EncodeBitstreamParams::WAS_IN_VIEW; + return bytesAtThisLevel; + } + + // If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed, + // then we can also bail early and save bits + if (!params.forceSendScene && !params.deltaViewFrustum && + !node->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE)) { + if (params.stats) { + params.stats->skippedNoChange(node); + } + params.stopReason = EncodeBitstreamParams::NO_CHANGE; + return bytesAtThisLevel; + } + + // If the user also asked for occlusion culling, check if this node is occluded, but only if it's not a leaf. + // leaf occlusion is handled down below when we check child nodes + if (params.wantOcclusionCulling && !node->isLeaf()) { + AABox voxelBox = node->getAABox(); + voxelBox.scale(TREE_SCALE); + OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon(params.viewFrustum->getProjectedPolygon(voxelBox)); + + // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion + // culling and proceed as normal + if (voxelPolygon->getAllInView()) { + CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, false); + delete voxelPolygon; // cleanup + if (result == OCCLUDED) { + if (params.stats) { + params.stats->skippedOccluded(node); + } + params.stopReason = EncodeBitstreamParams::OCCLUDED; + return bytesAtThisLevel; + } + } else { + // If this shadow wasn't "all in view" then we ignored it for occlusion culling, but + // we do need to clean up memory and proceed as normal... + delete voxelPolygon; + } + } + } + + bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more! + + // At any given point in writing the bitstream, the largest minimum we might need to flesh out the current level + // is 1 byte for child colors + 3*NUMBER_OF_CHILDREN bytes for the actual colors + 1 byte for child trees. There could be sub trees + // below this point, which might take many more bytes, but that's ok, because we can always mark our subtrees as + // not existing and stop the packet at this point, then start up with a new packet for the remaining sub trees. + unsigned char childrenExistInTreeBits = 0; + unsigned char childrenExistInPacketBits = 0; + unsigned char childrenColoredBits = 0; + const int BYTES_PER_COLOR = 3; + + // Make our local buffer large enough to handle writing at this level in case we need to. + LevelDetails thisLevelKey = packetData->startLevel(); + + int inViewCount = 0; + int inViewNotLeafCount = 0; + int inViewWithColorCount = 0; + + OctreeElement* sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + int currentCount = 0; + + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + OctreeElement* childNode = node->getChildAtIndex(i); + + // if the caller wants to include childExistsBits, then include them even if not in view, if however, + // we're in a portion of the tree that's not our responsibility, then we assume the child nodes exist + // even if they don't in our local tree + bool notMyJurisdiction = false; + if (params.jurisdictionMap) { + notMyJurisdiction = (JurisdictionMap::WITHIN != params.jurisdictionMap->isMyJurisdiction(node->getOctalCode(), i)); + } + if (params.includeExistsBits) { + // If the child is known to exist, OR, it's not my jurisdiction, then we mark the bit as existing + if (childNode || notMyJurisdiction) { + childrenExistInTreeBits += (1 << (7 - i)); + } + } + + if (params.wantOcclusionCulling) { + if (childNode) { + float distance = params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; + + currentCount = insertIntoSortedArrays((void*)childNode, distance, i, + (void**)&sortedChildren, (float*)&distancesToChildren, + (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); + } + } else { + sortedChildren[i] = childNode; + indexOfChildren[i] = i; + distancesToChildren[i] = 0.0f; + currentCount++; + } + + // track stats + // must check childNode here, because it could be we got here with no childNode + if (params.stats && childNode) { + params.stats->traversed(childNode); + } + + } + + // for each child node in Distance sorted order..., check to see if they exist, are colored, and in view, and if so + // add them to our distance ordered array of children + for (int i = 0; i < currentCount; i++) { + OctreeElement* childNode = sortedChildren[i]; + int originalIndex = indexOfChildren[i]; + + bool childIsInView = (childNode && (!params.viewFrustum || childNode->isInView(*params.viewFrustum))); + + if (!childIsInView) { + // must check childNode here, because it could be we got here because there was no childNode + if (params.stats && childNode) { + params.stats->skippedOutOfView(childNode); + } + } else { + // Before we determine consider this further, let's see if it's in our LOD scope... + float distance = distancesToChildren[i]; // params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; + float boundaryDistance = !params.viewFrustum ? 1 : + boundaryDistanceForRenderLevel(childNode->getLevel() + params.boundaryLevelAdjust, + params.octreeElementSizeScale); + + if (!(distance < boundaryDistance)) { + // don't need to check childNode here, because we can't get here with no childNode + if (params.stats) { + params.stats->skippedDistance(childNode); + } + } else { + inViewCount++; + + // track children in view as existing and not a leaf, if they're a leaf, + // we don't care about recursing deeper on them, and we don't consider their + // subtree to exist + if (!(childNode && childNode->isLeaf())) { + childrenExistInPacketBits += (1 << (7 - originalIndex)); + inViewNotLeafCount++; + } + + bool childIsOccluded = false; // assume it's not occluded + + // If the user also asked for occlusion culling, check if this node is occluded + if (params.wantOcclusionCulling && childNode->isLeaf()) { + // Don't check occlusion here, just add them to our distance ordered array... + + AABox voxelBox = childNode->getAABox(); + voxelBox.scale(TREE_SCALE); + OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon( + params.viewFrustum->getProjectedPolygon(voxelBox)); + + // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion + // culling and proceed as normal + if (voxelPolygon->getAllInView()) { + CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, true); + + // In all cases where the shadow wasn't stored, we need to free our own memory. + // In the case where it is stored, the CoverageMap will free memory for us later. + if (result != STORED) { + delete voxelPolygon; + } + + // If while attempting to add this voxel's shadow, we determined it was occluded, then + // we don't need to process it further and we can exit early. + if (result == OCCLUDED) { + childIsOccluded = true; + } + } else { + delete voxelPolygon; + } + } // wants occlusion culling & isLeaf() + + + bool shouldRender = !params.viewFrustum + ? true + : childNode->calculateShouldRender(params.viewFrustum, + params.octreeElementSizeScale, params.boundaryLevelAdjust); + + // track some stats + if (params.stats) { + // don't need to check childNode here, because we can't get here with no childNode + if (!shouldRender && childNode->isLeaf()) { + params.stats->skippedDistance(childNode); + } + // don't need to check childNode here, because we can't get here with no childNode + if (childIsOccluded) { + params.stats->skippedOccluded(childNode); + } + } + + // track children with actual color, only if the child wasn't previously in view! + if (shouldRender && !childIsOccluded) { + bool childWasInView = false; + + if (childNode && params.deltaViewFrustum && params.lastViewFrustum) { + ViewFrustum::location location = childNode->inFrustum(*params.lastViewFrustum); + + // If we're a leaf, then either intersect or inside is considered "formerly in view" + if (childNode->isLeaf()) { + childWasInView = location != ViewFrustum::OUTSIDE; + } else { + childWasInView = location == ViewFrustum::INSIDE; + } + } + + // If our child wasn't in view (or we're ignoring wasInView) then we add it to our sending items. + // Or if we were previously in the view, but this node has changed since it was last sent, then we do + // need to send it. + if (!childWasInView || + (params.deltaViewFrustum && + childNode->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))){ + + childrenColoredBits += (1 << (7 - originalIndex)); + inViewWithColorCount++; + } else { + // otherwise just track stats of the items we discarded + // don't need to check childNode here, because we can't get here with no childNode + if (params.stats) { + if (childWasInView) { + params.stats->skippedWasInView(childNode); + } else { + params.stats->skippedNoChange(childNode); + } + } + + } + } + } + } + } + + bool continueThisLevel = true; + continueThisLevel = packetData->appendBitMask(childrenColoredBits); + + if (continueThisLevel) { + bytesAtThisLevel += sizeof(childrenColoredBits); // keep track of byte count + if (params.stats) { + params.stats->colorBitsWritten(); + } + } + + // write the color data... + if (continueThisLevel && params.includeColor) { + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + if (oneAtBit(childrenColoredBits, i)) { + OctreeElement* childNode = node->getChildAtIndex(i); + continueThisLevel = childNode->appendElementData(packetData); + + if (!continueThisLevel) { + break; // no point in continuing + } + + bytesAtThisLevel += BYTES_PER_COLOR; // keep track of byte count for color + + // don't need to check childNode here, because we can't get here with no childNode + if (params.stats) { + params.stats->colorSent(childNode); + } + + } + } + } + + // if the caller wants to include childExistsBits, then include them even if not in view, put them before the + // childrenExistInPacketBits, so that the lower code can properly repair the packet exists bits + if (continueThisLevel && params.includeExistsBits) { + continueThisLevel = packetData->appendBitMask(childrenExistInTreeBits); + if (continueThisLevel) { + bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count + if (params.stats) { + params.stats->existsBitsWritten(); + } + } + } + + // write the child exist bits + if (continueThisLevel) { + continueThisLevel = packetData->appendBitMask(childrenExistInPacketBits); + if (continueThisLevel) { + bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count + if (params.stats) { + params.stats->existsInPacketBitsWritten(); + } + } + } + + // We only need to keep digging, if there is at least one child that is inView, and not a leaf. + keepDiggingDeeper = (inViewNotLeafCount > 0); + + if (continueThisLevel && keepDiggingDeeper) { + // at this point, we need to iterate the children who are in view, even if not colored + // and we need to determine if there's a deeper tree below them that we care about. + // + // Since this recursive function assumes we're already writing, we know we've already written our + // childrenExistInPacketBits. But... we don't really know how big the child tree will be. And we don't know if + // we'll have room in our buffer to actually write all these child trees. What we kinda would like to do is + // write our childExistsBits as a place holder. Then let each potential tree have a go at it. If they + // write something, we keep them in the bits, if they don't, we take them out. + // + // we know the last thing we wrote to the packet was our childrenExistInPacketBits. Let's remember where that was! + int childExistsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenExistInPacketBits)); + + // we are also going to recurse these child trees in "distance" sorted order, but we need to pack them in the + // final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes, + // and then later reshuffle these sections of our output buffer back into normal order. This allows us to make + // a single recursive pass in distance sorted order, but retain standard order in our encoded packet + int recursiveSliceSizes[NUMBER_OF_CHILDREN]; + const unsigned char* recursiveSliceStarts[NUMBER_OF_CHILDREN]; + int firstRecursiveSliceOffset = packetData->getUncompressedByteOffset(); + int allSlicesSize = 0; + + // for each child node in Distance sorted order..., check to see if they exist, are colored, and in view, and if so + // add them to our distance ordered array of children + for (int indexByDistance = 0; indexByDistance < currentCount; indexByDistance++) { + OctreeElement* childNode = sortedChildren[indexByDistance]; + int originalIndex = indexOfChildren[indexByDistance]; + + if (oneAtBit(childrenExistInPacketBits, originalIndex)) { + + int thisLevel = currentEncodeLevel; + // remember this for reshuffling + recursiveSliceStarts[originalIndex] = packetData->getUncompressedData() + packetData->getUncompressedSize(); + + int childTreeBytesOut = 0; + + // XXXBHG - Note, this seems like the correct logic here, if we included the color in this packet, then + // the LOD logic determined that the child nodes would not be visible... and if so, we shouldn't recurse + // them further. But... for some time now the code has included and recursed into these child nodes, which + // would likely still send the child content, even though the client wouldn't render it. This change is + // a major savings (~30%) and it seems to work correctly. But I want us to discuss as a group when we do + // a voxel protocol review. + // + // This only applies in the view frustum case, in other cases, like file save and copy/past where + // no viewFrustum was requested, we still want to recurse the child tree. + if (!params.viewFrustum || !oneAtBit(childrenColoredBits, originalIndex)) { + childTreeBytesOut = encodeTreeBitstreamRecursion(childNode, packetData, bag, params, thisLevel); + } + + // remember this for reshuffling + recursiveSliceSizes[originalIndex] = childTreeBytesOut; + allSlicesSize += childTreeBytesOut; + + // if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space, + // basically, the children below don't contain any info. + + // if the child tree wrote 1 byte??? something must have gone wrong... because it must have at least the color + // byte and the child exist byte. + // + assert(childTreeBytesOut != 1); + + // if the child tree wrote just 2 bytes, then it means: it had no colors and no child nodes, because... + // if it had colors it would write 1 byte for the color mask, + // and at least a color's worth of bytes for the node of colors. + // if it had child trees (with something in them) then it would have the 1 byte for child mask + // and some number of bytes of lower children... + // so, if the child returns 2 bytes out, we can actually consider that an empty tree also!! + // + // we can make this act like no bytes out, by just resetting the bytes out in this case + if (params.includeColor && !params.includeExistsBits && childTreeBytesOut == 2) { + childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees + } + // We used to try to collapse trees that didn't contain any data, but this does appear to create a problem + // in detecting node deletion. So, I've commented this out but left it in here as a warning to anyone else + // about not attempting to add this optimization back in, without solving the node deletion case. + // We need to send these bitMasks in case the exists in tree bitmask is indicating the deletion of a tree + //if (params.includeColor && params.includeExistsBits && childTreeBytesOut == 3) { + // childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees + //} + + bytesAtThisLevel += childTreeBytesOut; + + // If we had previously started writing, and if the child DIDN'T write any bytes, + // then we want to remove their bit from the childExistsPlaceHolder bitmask + if (childTreeBytesOut == 0) { + // remove this child's bit... + childrenExistInPacketBits -= (1 << (7 - originalIndex)); + + // repair the child exists mask + continueThisLevel = packetData->updatePriorBitMask(childExistsPlaceHolder, childrenExistInPacketBits); + + // If this is the last of the child exists bits, then we're actually be rolling out the entire tree + if (params.stats && childrenExistInPacketBits == 0) { + params.stats->childBitsRemoved(params.includeExistsBits, params.includeColor); + } + + if (!continueThisLevel) { + break; // can't continue... + } + + // Note: no need to move the pointer, cause we already stored this + } // end if (childTreeBytesOut == 0) + } // end if (oneAtBit(childrenExistInPacketBits, originalIndex)) + } // end for + + // reshuffle here... + if (continueThisLevel && params.wantOcclusionCulling) { + unsigned char tempReshuffleBuffer[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE]; + + unsigned char* tempBufferTo = &tempReshuffleBuffer[0]; // this is our temporary destination + + // iterate through our childrenExistInPacketBits, these will be the sections of the packet that we copied subTree + // details into. Unfortunately, they're in distance sorted order, not original index order. we need to put them + // back into original distance order + for (int originalIndex = 0; originalIndex < NUMBER_OF_CHILDREN; originalIndex++) { + if (oneAtBit(childrenExistInPacketBits, originalIndex)) { + int thisSliceSize = recursiveSliceSizes[originalIndex]; + const unsigned char* thisSliceStarts = recursiveSliceStarts[originalIndex]; + + memcpy(tempBufferTo, thisSliceStarts, thisSliceSize); + tempBufferTo += thisSliceSize; + } + } + + // now that all slices are back in the correct order, copy them to the correct output buffer + continueThisLevel = packetData->updatePriorBytes(firstRecursiveSliceOffset, &tempReshuffleBuffer[0], allSlicesSize); + } + } // end keepDiggingDeeper + + // At this point all our BitMasks are complete... so let's output them to see how they compare... + /** + printf("This Level's BitMasks: childInTree:"); + outputBits(childrenExistInTreeBits, false, true); + printf(" childInPacket:"); + outputBits(childrenExistInPacketBits, false, true); + printf(" childrenColored:"); + outputBits(childrenColoredBits, false, true); + printf("\n"); + **/ + + // if we were unable to fit this level in our packet, then rewind and add it to the node bag for + // sending later... + if (continueThisLevel) { + continueThisLevel = packetData->endLevel(thisLevelKey); + } else { + packetData->discardLevel(thisLevelKey); + } + + if (!continueThisLevel) { + bag.insert(node); + + // don't need to check node here, because we can't get here with no node + if (params.stats) { + params.stats->didntFit(node); + } + + params.stopReason = EncodeBitstreamParams::DIDNT_FIT; + bytesAtThisLevel = 0; // didn't fit + } + + return bytesAtThisLevel; +} + +bool Octree::readFromSVOFile(const char* fileName) { + std::ifstream file(fileName, std::ios::in|std::ios::binary|std::ios::ate); + if(file.is_open()) { + emit importSize(1.0f, 1.0f, 1.0f); + emit importProgress(0); + + qDebug("loading file %s...\n", fileName); + + // get file length.... + unsigned long fileLength = file.tellg(); + file.seekg( 0, std::ios::beg ); + + // read the entire file into a buffer, WHAT!? Why not. + unsigned char* entireFile = new unsigned char[fileLength]; + file.read((char*)entireFile, fileLength); + bool wantImportProgress = true; + ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0, wantImportProgress); + readBitstreamToTree(entireFile, fileLength, args); + delete[] entireFile; + + emit importProgress(100); + + file.close(); + return true; + } + return false; +} + +void Octree::writeToSVOFile(const char* fileName, OctreeElement* node) { + + std::ofstream file(fileName, std::ios::out|std::ios::binary); + + if(file.is_open()) { + qDebug("saving to file %s...\n", fileName); + + OctreeElementBag nodeBag; + // If we were given a specific node, start from there, otherwise start from root + if (node) { + nodeBag.insert(node); + } else { + nodeBag.insert(_rootNode); + } + + static OctreePacketData packetData; + int bytesWritten = 0; + bool lastPacketWritten = false; + + while (!nodeBag.isEmpty()) { + OctreeElement* subTree = nodeBag.extract(); + + lockForRead(); // do tree locking down here so that we have shorter slices and less thread contention + EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS); + bytesWritten = encodeTreeBitstream(subTree, &packetData, nodeBag, params); + unlock(); + + // if bytesWritten == 0, then it means that the subTree couldn't fit, and so we should reset the packet + // and reinsert the node in our bag and try again... + if (bytesWritten == 0) { + if (packetData.hasContent()) { + file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize()); + lastPacketWritten = true; + } + packetData.reset(); // is there a better way to do this? could we fit more? + nodeBag.insert(subTree); + } else { + lastPacketWritten = false; + } + } + + if (!lastPacketWritten) { + file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize()); + } + + } + file.close(); +} + +unsigned long Octree::getOctreeElementsCount() { + unsigned long nodeCount = 0; + recurseTreeWithOperation(countOctreeElementsOperation, &nodeCount); + return nodeCount; +} + +bool Octree::countOctreeElementsOperation(OctreeElement* node, void* extraData) { + (*(unsigned long*)extraData)++; + return true; // keep going +} + +void Octree::copySubTreeIntoNewTree(OctreeElement* startNode, Octree* destinationTree, bool rebaseToRoot) { + OctreeElementBag nodeBag; + nodeBag.insert(startNode); + int chopLevels = 0; + if (rebaseToRoot) { + chopLevels = numberOfThreeBitSectionsInCode(startNode->getOctalCode()); + } + + static OctreePacketData packetData; + int bytesWritten = 0; + + while (!nodeBag.isEmpty()) { + OctreeElement* subTree = nodeBag.extract(); + packetData.reset(); // reset the packet between usage + // ask our tree to write a bitsteam + EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS, chopLevels); + bytesWritten = encodeTreeBitstream(subTree, &packetData, nodeBag, params); + // ask destination tree to read the bitstream + ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS); + destinationTree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); + } + + // XXXBHG - what is this trying to do? + // + //OctreeElement* destinationStartNode; + //if (rebaseToRoot) { + // destinationStartNode = destinationTree->_rootNode; + //} else { + // destinationStartNode = nodeForOctalCode(destinationTree->_rootNode, startNode->getOctalCode(), NULL); + //} + //destinationStartNode->setColor(startNode->getColor()); +} + +void Octree::copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinationNode) { + OctreeElementBag nodeBag; + // If we were given a specific node, start from there, otherwise start from root + nodeBag.insert(sourceTree->_rootNode); + + static OctreePacketData packetData; + int bytesWritten = 0; + + while (!nodeBag.isEmpty()) { + OctreeElement* subTree = nodeBag.extract(); + + packetData.reset(); // reset between usage + + // ask our tree to write a bitsteam + EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS); + bytesWritten = sourceTree->encodeTreeBitstream(subTree, &packetData, nodeBag, params); + + // ask destination tree to read the bitstream + bool wantImportProgress = true; + ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, destinationNode, 0, wantImportProgress); + readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); + } +} + +void dumpSetContents(const char* name, std::set set) { + printf("set %s has %ld elements\n", name, set.size()); + /* + for (std::set::iterator i = set.begin(); i != set.end(); ++i) { + printOctalCode(*i); + } + */ +} + +void Octree::startEncoding(OctreeElement* node) { + pthread_mutex_lock(&_encodeSetLock); + _codesBeingEncoded.insert(node->getOctalCode()); + pthread_mutex_unlock(&_encodeSetLock); +} + +void Octree::doneEncoding(OctreeElement* node) { + pthread_mutex_lock(&_encodeSetLock); + _codesBeingEncoded.erase(node->getOctalCode()); + pthread_mutex_unlock(&_encodeSetLock); + + // if we have any pending delete codes, then delete them now. + emptyDeleteQueue(); +} + +void Octree::startDeleting(const unsigned char* code) { + pthread_mutex_lock(&_deleteSetLock); + _codesBeingDeleted.insert(code); + pthread_mutex_unlock(&_deleteSetLock); +} + +void Octree::doneDeleting(const unsigned char* code) { + pthread_mutex_lock(&_deleteSetLock); + _codesBeingDeleted.erase(code); + pthread_mutex_unlock(&_deleteSetLock); +} + +bool Octree::isEncoding(const unsigned char* codeBuffer) { + pthread_mutex_lock(&_encodeSetLock); + bool isEncoding = (_codesBeingEncoded.find(codeBuffer) != _codesBeingEncoded.end()); + pthread_mutex_unlock(&_encodeSetLock); + return isEncoding; +} + +void Octree::queueForLaterDelete(const unsigned char* codeBuffer) { + pthread_mutex_lock(&_deletePendingSetLock); + _codesPendingDelete.insert(codeBuffer); + pthread_mutex_unlock(&_deletePendingSetLock); +} + +void Octree::emptyDeleteQueue() { + pthread_mutex_lock(&_deletePendingSetLock); + for (std::set::iterator i = _codesPendingDelete.begin(); i != _codesPendingDelete.end(); ++i) { + const unsigned char* codeToDelete = *i; + _codesBeingDeleted.erase(codeToDelete); + deleteOctalCodeFromTree(codeToDelete, COLLAPSE_EMPTY_TREE); + } + pthread_mutex_unlock(&_deletePendingSetLock); +} + +void Octree::cancelImport() { + _stopImport = true; +} + diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h new file mode 100644 index 0000000000..8f572bace8 --- /dev/null +++ b/libraries/octree/src/Octree.h @@ -0,0 +1,313 @@ +// +// Octree.h +// hifi +// +// Created by Stephen Birarda on 3/13/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__Octree__ +#define __hifi__Octree__ + +#include +#include + +//#include "CoverageMap.h" +class CoverageMap; +class ReadBitstreamToTreeParams; +class Octree; +class OctreeElement; +class OctreeElementBag; +class OctreePacketData; + + +#include "JurisdictionMap.h" +#include "ViewFrustum.h" +#include "OctreeElement.h" +#include "OctreeElementBag.h" +#include "OctreePacketData.h" +#include "OctreeSceneStats.h" + +#include +#include + +// Callback function, for recuseTreeWithOperation +typedef bool (*RecurseOctreeOperation)(OctreeElement* node, void* extraData); +typedef enum {GRADIENT, RANDOM, NATURAL} creationMode; + +const bool NO_EXISTS_BITS = false; +const bool WANT_EXISTS_BITS = true; +const bool NO_COLOR = false; +const bool WANT_COLOR = true; +const bool COLLAPSE_EMPTY_TREE = true; +const bool DONT_COLLAPSE = false; +const bool NO_OCCLUSION_CULLING = false; +const bool WANT_OCCLUSION_CULLING = true; + +const int DONT_CHOP = 0; +const int NO_BOUNDARY_ADJUST = 0; +const int LOW_RES_MOVING_ADJUST = 1; +const uint64_t IGNORE_LAST_SENT = 0; + +#define IGNORE_SCENE_STATS NULL +#define IGNORE_VIEW_FRUSTUM NULL +#define IGNORE_COVERAGE_MAP NULL +#define IGNORE_JURISDICTION_MAP NULL + +class EncodeBitstreamParams { +public: + int maxEncodeLevel; + int maxLevelReached; + const ViewFrustum* viewFrustum; + bool includeColor; + bool includeExistsBits; + int chopLevels; + bool deltaViewFrustum; + const ViewFrustum* lastViewFrustum; + bool wantOcclusionCulling; + int boundaryLevelAdjust; + float octreeElementSizeScale; + uint64_t lastViewFrustumSent; + bool forceSendScene; + OctreeSceneStats* stats; + CoverageMap* map; + JurisdictionMap* jurisdictionMap; + + // output hints from the encode process + typedef enum { + UNKNOWN, + DIDNT_FIT, + NULL_NODE, + TOO_DEEP, + OUT_OF_JURISDICTION, + LOD_SKIP, + OUT_OF_VIEW, + WAS_IN_VIEW, + NO_CHANGE, + OCCLUDED + } reason; + reason stopReason; + + EncodeBitstreamParams( + int maxEncodeLevel = INT_MAX, + const ViewFrustum* viewFrustum = IGNORE_VIEW_FRUSTUM, + bool includeColor = WANT_COLOR, + bool includeExistsBits = WANT_EXISTS_BITS, + int chopLevels = 0, + bool deltaViewFrustum = false, + const ViewFrustum* lastViewFrustum = IGNORE_VIEW_FRUSTUM, + bool wantOcclusionCulling = NO_OCCLUSION_CULLING, + CoverageMap* map = IGNORE_COVERAGE_MAP, + int boundaryLevelAdjust = NO_BOUNDARY_ADJUST, + float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE, + uint64_t lastViewFrustumSent = IGNORE_LAST_SENT, + bool forceSendScene = true, + OctreeSceneStats* stats = IGNORE_SCENE_STATS, + JurisdictionMap* jurisdictionMap = IGNORE_JURISDICTION_MAP) : + maxEncodeLevel(maxEncodeLevel), + maxLevelReached(0), + viewFrustum(viewFrustum), + includeColor(includeColor), + includeExistsBits(includeExistsBits), + chopLevels(chopLevels), + deltaViewFrustum(deltaViewFrustum), + lastViewFrustum(lastViewFrustum), + wantOcclusionCulling(wantOcclusionCulling), + boundaryLevelAdjust(boundaryLevelAdjust), + octreeElementSizeScale(octreeElementSizeScale), + lastViewFrustumSent(lastViewFrustumSent), + forceSendScene(forceSendScene), + stats(stats), + map(map), + jurisdictionMap(jurisdictionMap), + stopReason(UNKNOWN) + {} + + void displayStopReason() { + printf("StopReason: "); + switch (stopReason) { + default: + case UNKNOWN: printf("UNKNOWN\n"); break; + + case DIDNT_FIT: printf("DIDNT_FIT\n"); break; + case NULL_NODE: printf("NULL_NODE\n"); break; + case TOO_DEEP: printf("TOO_DEEP\n"); break; + case OUT_OF_JURISDICTION: printf("OUT_OF_JURISDICTION\n"); break; + case LOD_SKIP: printf("LOD_SKIP\n"); break; + case OUT_OF_VIEW: printf("OUT_OF_VIEW\n"); break; + case WAS_IN_VIEW: printf("WAS_IN_VIEW\n"); break; + case NO_CHANGE: printf("NO_CHANGE\n"); break; + case OCCLUDED: printf("OCCLUDED\n"); break; + } + } +}; + +class ReadElementBufferToTreeArgs { +public: + const unsigned char* buffer; + int length; + bool destructive; + bool pathChanged; +}; + +class ReadBitstreamToTreeParams { +public: + bool includeColor; + bool includeExistsBits; + OctreeElement* destinationNode; + QUuid sourceUUID; + bool wantImportProgress; + + ReadBitstreamToTreeParams( + bool includeColor = WANT_COLOR, + bool includeExistsBits = WANT_EXISTS_BITS, + OctreeElement* destinationNode = NULL, + QUuid sourceUUID = QUuid(), + bool wantImportProgress = false) : + includeColor(includeColor), + includeExistsBits(includeExistsBits), + destinationNode(destinationNode), + sourceUUID(sourceUUID), + wantImportProgress(wantImportProgress) + {} +}; + +class Octree : public QObject { + Q_OBJECT +public: + Octree(bool shouldReaverage = false); + ~Octree(); + + virtual OctreeElement* createNewElement(unsigned char * octalCode = NULL) const = 0; + + OctreeElement* getRoot() { return _rootNode; } + + void eraseAllOctreeElements(); + + void processRemoveOctreeElementsBitstream(const unsigned char* bitstream, int bufferSizeBytes); + void readBitstreamToTree(const unsigned char* bitstream, unsigned long int bufferSizeBytes, ReadBitstreamToTreeParams& args); + void deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE); + void reaverageOctreeElements(OctreeElement* startNode = NULL); + + void deleteOctreeElementAt(float x, float y, float z, float s); + OctreeElement* getOctreeElementAt(float x, float y, float z, float s) const; + void createOctreeElement(float x, float y, float z, float s, + unsigned char red, unsigned char green, unsigned char blue, bool destructive = false); + + void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData=NULL); + + void recurseTreeWithOperationDistanceSorted(RecurseOctreeOperation operation, + const glm::vec3& point, void* extraData=NULL); + + int encodeTreeBitstream(OctreeElement* node, OctreePacketData* packetData, OctreeElementBag& bag, + EncodeBitstreamParams& params) ; + + bool isDirty() const { return _isDirty; } + void clearDirtyBit() { _isDirty = false; } + void setDirtyBit() { _isDirty = true; } + + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + OctreeElement*& node, float& distance, BoxFace& face); + + bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration); + bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration); + + // Note: this assumes the fileFormat is the HIO individual voxels code files + void loadOctreeFile(const char* fileName, bool wantColorRandomizer); + + // these will read/write files that match the wireformat, excluding the 'V' leading + void writeToSVOFile(const char* filename, OctreeElement* node = NULL); + bool readFromSVOFile(const char* filename); + // reads voxels from square image with alpha as a Y-axis + bool readFromSquareARGB32Pixels(const char *filename); + bool readFromSchematicFile(const char* filename); + + // Octree does not currently handle its own locking, caller must use these to lock/unlock + void lockForRead() { lock.lockForRead(); } + void tryLockForRead() { lock.tryLockForRead(); } + void lockForWrite() { lock.lockForWrite(); } + void tryLockForWrite() { lock.tryLockForWrite(); } + void unlock() { lock.unlock(); } + + unsigned long getOctreeElementsCount(); + + void copySubTreeIntoNewTree(OctreeElement* startNode, Octree* destinationTree, bool rebaseToRoot); + void copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinationNode); + + bool getShouldReaverage() const { return _shouldReaverage; } + + void recurseNodeWithOperation(OctreeElement* node, RecurseOctreeOperation operation, + void* extraData, int recursionCount = 0); + + void recurseNodeWithOperationDistanceSorted(OctreeElement* node, RecurseOctreeOperation operation, + const glm::vec3& point, void* extraData, int recursionCount = 0); + +signals: + void importSize(float x, float y, float z); + void importProgress(int progress); + +public slots: + void cancelImport(); + + +protected: + void deleteOctalCodeFromTreeRecursion(OctreeElement* node, void* extraData); + void readCodeColorBufferToTreeRecursion(OctreeElement* node, void* extraData); + + int encodeTreeBitstreamRecursion(OctreeElement* node, + OctreePacketData* packetData, OctreeElementBag& bag, + EncodeBitstreamParams& params, int& currentEncodeLevel) const; + + static bool countOctreeElementsOperation(OctreeElement* node, void* extraData); + + OctreeElement* nodeForOctalCode(OctreeElement* ancestorNode, const unsigned char* needleCode, OctreeElement** parentOfFoundNode) const; + OctreeElement* createMissingNode(OctreeElement* lastParentNode, const unsigned char* codeToReach); + int readNodeData(OctreeElement *destinationNode, const unsigned char* nodeData, + int bufferSizeBytes, ReadBitstreamToTreeParams& args); + + OctreeElement* _rootNode; + + bool _isDirty; + bool _shouldReaverage; + bool _stopImport; + + /// Octal Codes of any subtrees currently being encoded. While any of these codes is being encoded, ancestors and + /// descendants of them can not be deleted. + std::set _codesBeingEncoded; + /// mutex lock to protect the encoding set + pthread_mutex_t _encodeSetLock; + + /// Called to indicate that a OctreeElement is in the process of being encoded. + void startEncoding(OctreeElement* node); + /// Called to indicate that a OctreeElement is done being encoded. + void doneEncoding(OctreeElement* node); + /// Is the Octal Code currently being deleted? + bool isEncoding(const unsigned char* codeBuffer); + + /// Octal Codes of any subtrees currently being deleted. While any of these codes is being deleted, ancestors and + /// descendants of them can not be encoded. + std::set _codesBeingDeleted; + /// mutex lock to protect the deleting set + pthread_mutex_t _deleteSetLock; + + /// Called to indicate that an octal code is in the process of being deleted. + void startDeleting(const unsigned char* code); + /// Called to indicate that an octal code is done being deleted. + void doneDeleting(const unsigned char* code); + /// Octal Codes that were attempted to be deleted but couldn't be because they were actively being encoded, and were + /// instead queued for later delete + std::set _codesPendingDelete; + /// mutex lock to protect the deleting set + pthread_mutex_t _deletePendingSetLock; + + /// Adds an Octal Code to the set of codes that needs to be deleted + void queueForLaterDelete(const unsigned char* codeBuffer); + /// flushes out any Octal Codes that had to be queued + void emptyDeleteQueue(); + + QReadWriteLock lock; +}; + +float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale); + +#endif /* defined(__hifi__Octree__) */ diff --git a/libraries/octree/src/OctreeConstants.h b/libraries/octree/src/OctreeConstants.h new file mode 100644 index 0000000000..31820b6fbc --- /dev/null +++ b/libraries/octree/src/OctreeConstants.h @@ -0,0 +1,47 @@ +// +// OctreeConstants.h +// hifi +// +// Created by Brad Hefta-Gaub on 4/29/13. +// +// +// Various important constants used throughout the system related to voxels +// +// + +#ifndef __hifi_OctreeConstants_h__ +#define __hifi_OctreeConstants_h__ + +#include +#include +#include +#include +#include +#include + +// this is where the coordinate system is represented +const glm::vec3 IDENTITY_RIGHT = glm::vec3( 1.0f, 0.0f, 0.0f); +const glm::vec3 IDENTITY_UP = glm::vec3( 0.0f, 1.0f, 0.0f); +const glm::vec3 IDENTITY_FRONT = glm::vec3( 0.0f, 0.0f,-1.0f); + +const uint64_t CHANGE_FUDGE = 1000 * 200; // useconds of fudge in determining if we want to resend changed voxels + +const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe + +// This controls the LOD. Larger number will make smaller voxels visible at greater distance. +const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * 400.0f; +const float MAX_LOD_SIZE_MULTIPLIER = 2000.0f; + +const int NUMBER_OF_CHILDREN = 8; + +const int MAX_TREE_SLICE_BYTES = 26; + +const float VIEW_FRUSTUM_FOV_OVERSEND = 60.0f; + +// These are guards to prevent our voxel tree recursive routines from spinning out of control +const int UNREASONABLY_DEEP_RECURSION = 20; // use this for something that you want to be shallow, but not spin out +const int DANGEROUSLY_DEEP_RECURSION = 200; // use this for something that needs to go deeper + +const int DEFAULT_MAX_OCTREE_PPS = 600; // the default maximum PPS we think any octree based server should send to a client + +#endif \ No newline at end of file diff --git a/libraries/voxels/src/VoxelNode.cpp b/libraries/octree/src/OctreeElement.cpp similarity index 70% rename from libraries/voxels/src/VoxelNode.cpp rename to libraries/octree/src/OctreeElement.cpp index 8e225dc2ed..51b9de1a2a 100644 --- a/libraries/voxels/src/VoxelNode.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -1,5 +1,5 @@ // -// VoxelNode.cpp +// OctreeElement.cpp // hifi // // Created by Stephen Birarda on 3/13/13. @@ -18,32 +18,27 @@ #include "AABox.h" #include "OctalCode.h" #include "SharedUtil.h" -#include "VoxelConstants.h" -#include "VoxelNode.h" -#include "VoxelTree.h" +#include "OctreeConstants.h" +#include "OctreeElement.h" +#include "Octree.h" -uint64_t VoxelNode::_voxelMemoryUsage = 0; -uint64_t VoxelNode::_octcodeMemoryUsage = 0; -uint64_t VoxelNode::_externalChildrenMemoryUsage = 0; -uint64_t VoxelNode::_voxelNodeCount = 0; -uint64_t VoxelNode::_voxelNodeLeafCount = 0; +uint64_t OctreeElement::_voxelMemoryUsage = 0; +uint64_t OctreeElement::_octcodeMemoryUsage = 0; +uint64_t OctreeElement::_externalChildrenMemoryUsage = 0; +uint64_t OctreeElement::_voxelNodeCount = 0; +uint64_t OctreeElement::_voxelNodeLeafCount = 0; -VoxelNode::VoxelNode() { - unsigned char* rootCode = new unsigned char[1]; - *rootCode = 0; - init(rootCode); - - _voxelNodeCount++; - _voxelNodeLeafCount++; // all nodes start as leaf nodes -} - -VoxelNode::VoxelNode(unsigned char * octalCode) { +OctreeElement::OctreeElement(unsigned char * octalCode) { + if (!octalCode) { + octalCode = new unsigned char[1]; + *octalCode = 0; + } init(octalCode); _voxelNodeCount++; _voxelNodeLeafCount++; // all nodes start as leaf nodes } -void VoxelNode::init(unsigned char * octalCode) { +void OctreeElement::init(unsigned char * octalCode) { int octalCodeLength = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(octalCode)); if (octalCodeLength > sizeof(_octalCode)) { _octalCode.pointer = octalCode; @@ -55,13 +50,6 @@ void VoxelNode::init(unsigned char * octalCode) { delete[] octalCode; } -#ifndef NO_FALSE_COLOR // !NO_FALSE_COLOR means, does have false color - _falseColored = false; // assume true color - _currentColor[0] = _currentColor[1] = _currentColor[2] = _currentColor[3] = 0; -#endif - _trueColor[0] = _trueColor[1] = _trueColor[2] = _trueColor[3] = 0; - _density = 0.0f; - // set up the _children union _childBitmask = 0; _childrenExternal = false; @@ -89,23 +77,19 @@ void VoxelNode::init(unsigned char * octalCode) { _children.single = NULL; #endif - _unknownBufferIndex = true; - setBufferIndex(GLBUFFER_INDEX_UNKNOWN); - - setVoxelSystem(NULL); _isDirty = true; _shouldRender = false; _sourceUUIDKey = 0; calculateAABox(); markWithChangedTime(); - _voxelMemoryUsage += sizeof(VoxelNode); + _voxelMemoryUsage += sizeof(OctreeElement); } -VoxelNode::~VoxelNode() { +OctreeElement::~OctreeElement() { notifyDeleteHooks(); - _voxelMemoryUsage -= sizeof(VoxelNode); + _voxelMemoryUsage -= sizeof(OctreeElement); _voxelNodeCount--; if (isLeaf()) { @@ -121,65 +105,32 @@ VoxelNode::~VoxelNode() { deleteAllChildren(); } -void VoxelNode::markWithChangedTime() { +void OctreeElement::markWithChangedTime() { _lastChanged = usecTimestampNow(); notifyUpdateHooks(); // if the node has changed, notify our hooks } -// This method is called by VoxelTree when the subtree below this node +// This method is called by Octree when the subtree below this node // is known to have changed. It's intended to be used as a place to do // bookkeeping that a node may need to do when the subtree below it has // changed. However, you should hopefully make your bookkeeping relatively // localized, because this method will get called for every node in an // recursive unwinding case like delete or add voxel -void VoxelNode::handleSubtreeChanged(VoxelTree* myTree) { +void OctreeElement::handleSubtreeChanged(Octree* myTree) { // here's a good place to do color re-averaging... if (myTree->getShouldReaverage()) { - setColorFromAverageOfChildren(); + calculateAverageFromChildren(); } markWithChangedTime(); } -const uint8_t INDEX_FOR_NULL = 0; -uint8_t VoxelNode::_nextIndex = INDEX_FOR_NULL + 1; // start at 1, 0 is reserved for NULL -std::map VoxelNode::_mapVoxelSystemPointersToIndex; -std::map VoxelNode::_mapIndexToVoxelSystemPointers; - -VoxelSystem* VoxelNode::getVoxelSystem() const { - if (_voxelSystemIndex > INDEX_FOR_NULL) { - if (_mapIndexToVoxelSystemPointers.end() != _mapIndexToVoxelSystemPointers.find(_voxelSystemIndex)) { - - VoxelSystem* voxelSystem = _mapIndexToVoxelSystemPointers[_voxelSystemIndex]; - return voxelSystem; - } - } - return NULL; -} - -void VoxelNode::setVoxelSystem(VoxelSystem* voxelSystem) { - if (voxelSystem == NULL) { - _voxelSystemIndex = INDEX_FOR_NULL; - } else { - uint8_t index; - if (_mapVoxelSystemPointersToIndex.end() != _mapVoxelSystemPointersToIndex.find(voxelSystem)) { - index = _mapVoxelSystemPointersToIndex[voxelSystem]; - } else { - index = _nextIndex; - _nextIndex++; - _mapVoxelSystemPointersToIndex[voxelSystem] = index; - _mapIndexToVoxelSystemPointers[index] = voxelSystem; - } - _voxelSystemIndex = index; - } -} - const uint16_t KEY_FOR_NULL = 0; -uint16_t VoxelNode::_nextUUIDKey = KEY_FOR_NULL + 1; // start at 1, 0 is reserved for NULL -std::map VoxelNode::_mapSourceUUIDsToKeys; -std::map VoxelNode::_mapKeysToSourceUUIDs; +uint16_t OctreeElement::_nextUUIDKey = KEY_FOR_NULL + 1; // start at 1, 0 is reserved for NULL +std::map OctreeElement::_mapSourceUUIDsToKeys; +std::map OctreeElement::_mapKeysToSourceUUIDs; -void VoxelNode::setSourceUUID(const QUuid& sourceUUID) { +void OctreeElement::setSourceUUID(const QUuid& sourceUUID) { uint16_t key; QString sourceUUIDString = sourceUUID.toString(); if (_mapSourceUUIDsToKeys.end() != _mapSourceUUIDsToKeys.find(sourceUUIDString)) { @@ -193,7 +144,7 @@ void VoxelNode::setSourceUUID(const QUuid& sourceUUID) { _sourceUUIDKey = key; } -QUuid VoxelNode::getSourceUUID() const { +QUuid OctreeElement::getSourceUUID() const { if (_sourceUUIDKey > KEY_FOR_NULL) { if (_mapKeysToSourceUUIDs.end() != _mapKeysToSourceUUIDs.find(_sourceUUIDKey)) { return QUuid(_mapKeysToSourceUUIDs[_sourceUUIDKey]); @@ -202,7 +153,7 @@ QUuid VoxelNode::getSourceUUID() const { return QUuid(); } -bool VoxelNode::matchesSourceUUID(const QUuid& sourceUUID) const { +bool OctreeElement::matchesSourceUUID(const QUuid& sourceUUID) const { if (_sourceUUIDKey > KEY_FOR_NULL) { if (_mapKeysToSourceUUIDs.end() != _mapKeysToSourceUUIDs.find(_sourceUUIDKey)) { return QUuid(_mapKeysToSourceUUIDs[_sourceUUIDKey]) == sourceUUID; @@ -211,7 +162,7 @@ bool VoxelNode::matchesSourceUUID(const QUuid& sourceUUID) const { return sourceUUID.isNull(); } -uint16_t VoxelNode::getSourceNodeUUIDKey(const QUuid& sourceUUID) { +uint16_t OctreeElement::getSourceNodeUUIDKey(const QUuid& sourceUUID) { uint16_t key = KEY_FOR_NULL; QString sourceUUIDString = sourceUUID.toString(); if (_mapSourceUUIDsToKeys.end() != _mapSourceUUIDsToKeys.find(sourceUUIDString)) { @@ -222,7 +173,7 @@ uint16_t VoxelNode::getSourceNodeUUIDKey(const QUuid& sourceUUID) { -void VoxelNode::setShouldRender(bool shouldRender) { +void OctreeElement::setShouldRender(bool shouldRender) { // if shouldRender is changing, then consider ourselves dirty if (shouldRender != _shouldRender) { _shouldRender = shouldRender; @@ -231,7 +182,7 @@ void VoxelNode::setShouldRender(bool shouldRender) { } } -void VoxelNode::calculateAABox() { +void OctreeElement::calculateAABox() { glm::vec3 corner; // copy corner into box @@ -242,8 +193,8 @@ void VoxelNode::calculateAABox() { _box.setBox(corner,voxelScale); } -void VoxelNode::deleteChildAtIndex(int childIndex) { - VoxelNode* childAt = getChildAtIndex(childIndex); +void OctreeElement::deleteChildAtIndex(int childIndex) { + OctreeElement* childAt = getChildAtIndex(childIndex); if (childAt) { delete childAt; setChildAtIndex(childIndex, NULL); @@ -261,8 +212,8 @@ void VoxelNode::deleteChildAtIndex(int childIndex) { } // does not delete the node! -VoxelNode* VoxelNode::removeChildAtIndex(int childIndex) { - VoxelNode* returnedChild = getChildAtIndex(childIndex); +OctreeElement* OctreeElement::removeChildAtIndex(int childIndex) { + OctreeElement* returnedChild = getChildAtIndex(childIndex); if (returnedChild) { setChildAtIndex(childIndex, NULL); _isDirty = true; @@ -281,11 +232,11 @@ VoxelNode* VoxelNode::removeChildAtIndex(int childIndex) { } #ifdef HAS_AUDIT_CHILDREN -void VoxelNode::auditChildren(const char* label) const { +void OctreeElement::auditChildren(const char* label) const { bool auditFailed = false; for (int childIndex = 0; childIndex < NUMBER_OF_CHILDREN; childIndex++) { - VoxelNode* testChildNew = getChildAtIndex(childIndex); - VoxelNode* testChildOld = _childrenArray[childIndex]; + OctreeElement* testChildNew = getChildAtIndex(childIndex); + OctreeElement* testChildOld = _childrenArray[childIndex]; if (testChildNew != testChildOld) { auditFailed = true; @@ -302,8 +253,8 @@ void VoxelNode::auditChildren(const char* label) const { for (int childIndex = 0; childIndex < NUMBER_OF_CHILDREN; childIndex++) { - VoxelNode* testChildNew = getChildAtIndex(childIndex); - VoxelNode* testChildOld = _childrenArray[childIndex]; + OctreeElement* testChildNew = getChildAtIndex(childIndex); + OctreeElement* testChildOld = _childrenArray[childIndex]; qDebug("child at index %d... testChildOld=%p testChildNew=%p %s \n", childIndex, testChildOld, testChildNew , @@ -316,25 +267,25 @@ void VoxelNode::auditChildren(const char* label) const { #endif // def HAS_AUDIT_CHILDREN -uint64_t VoxelNode::_getChildAtIndexTime = 0; -uint64_t VoxelNode::_getChildAtIndexCalls = 0; -uint64_t VoxelNode::_setChildAtIndexTime = 0; -uint64_t VoxelNode::_setChildAtIndexCalls = 0; +uint64_t OctreeElement::_getChildAtIndexTime = 0; +uint64_t OctreeElement::_getChildAtIndexCalls = 0; +uint64_t OctreeElement::_setChildAtIndexTime = 0; +uint64_t OctreeElement::_setChildAtIndexCalls = 0; #ifdef BLENDED_UNION_CHILDREN -uint64_t VoxelNode::_singleChildrenCount = 0; -uint64_t VoxelNode::_twoChildrenOffsetCount = 0; -uint64_t VoxelNode::_twoChildrenExternalCount = 0; -uint64_t VoxelNode::_threeChildrenOffsetCount = 0; -uint64_t VoxelNode::_threeChildrenExternalCount = 0; -uint64_t VoxelNode::_couldStoreFourChildrenInternally = 0; -uint64_t VoxelNode::_couldNotStoreFourChildrenInternally = 0; +uint64_t OctreeElement::_singleChildrenCount = 0; +uint64_t OctreeElement::_twoChildrenOffsetCount = 0; +uint64_t OctreeElement::_twoChildrenExternalCount = 0; +uint64_t OctreeElement::_threeChildrenOffsetCount = 0; +uint64_t OctreeElement::_threeChildrenExternalCount = 0; +uint64_t OctreeElement::_couldStoreFourChildrenInternally = 0; +uint64_t OctreeElement::_couldNotStoreFourChildrenInternally = 0; #endif -uint64_t VoxelNode::_externalChildrenCount = 0; -uint64_t VoxelNode::_childrenCount[NUMBER_OF_CHILDREN + 1] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +uint64_t OctreeElement::_externalChildrenCount = 0; +uint64_t OctreeElement::_childrenCount[NUMBER_OF_CHILDREN + 1] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; -VoxelNode* VoxelNode::getChildAtIndex(int childIndex) const { +OctreeElement* OctreeElement::getChildAtIndex(int childIndex) const { #ifdef SIMPLE_CHILD_ARRAY return _simpleChildArray[childIndex]; #endif // SIMPLE_CHILD_ARRAY @@ -366,7 +317,7 @@ VoxelNode* VoxelNode::getChildAtIndex(int childIndex) const { #ifdef BLENDED_UNION_CHILDREN PerformanceWarning warn(false,"getChildAtIndex",false,&_getChildAtIndexTime,&_getChildAtIndexCalls); - VoxelNode* result = NULL; + OctreeElement* result = NULL; int childCount = getChildCount(); #ifdef HAS_AUDIT_CHILDREN @@ -405,10 +356,10 @@ VoxelNode* VoxelNode::getChildAtIndex(int childIndex) const { } else { if (indexOne == childIndex) { int32_t offset = _children.offsetsTwoChildren[0]; - result = (VoxelNode*)((uint8_t*)this + offset); + result = (OctreeElement*)((uint8_t*)this + offset); } else if (indexTwo == childIndex) { int32_t offset = _children.offsetsTwoChildren[1]; - result = (VoxelNode*)((uint8_t*)this + offset); + result = (OctreeElement*)((uint8_t*)this + offset); } } } break; @@ -435,11 +386,11 @@ VoxelNode* VoxelNode::getChildAtIndex(int childIndex) const { decodeThreeOffsets(offsetOne, offsetTwo, offsetThree); if (indexOne == childIndex) { - result = (VoxelNode*)((uint8_t*)this + offsetOne); + result = (OctreeElement*)((uint8_t*)this + offsetOne); } else if (indexTwo == childIndex) { - result = (VoxelNode*)((uint8_t*)this + offsetTwo); + result = (OctreeElement*)((uint8_t*)this + offsetTwo); } else if (indexThree == childIndex) { - result = (VoxelNode*)((uint8_t*)this + offsetThree); + result = (OctreeElement*)((uint8_t*)this + offsetThree); } } } break; @@ -477,7 +428,7 @@ VoxelNode* VoxelNode::getChildAtIndex(int childIndex) const { } #ifdef BLENDED_UNION_CHILDREN -void VoxelNode::storeTwoChildren(VoxelNode* childOne, VoxelNode* childTwo) { +void OctreeElement::storeTwoChildren(OctreeElement* childOne, OctreeElement* childTwo) { int64_t offsetOne = (uint8_t*)childOne - (uint8_t*)this; int64_t offsetTwo = (uint8_t*)childTwo - (uint8_t*)this; @@ -490,7 +441,7 @@ void VoxelNode::storeTwoChildren(VoxelNode* childOne, VoxelNode* childTwo) { if (_childrenExternal) { //assert(_children.external); const int previousChildCount = 2; - _externalChildrenMemoryUsage -= previousChildCount * sizeof(VoxelNode*); + _externalChildrenMemoryUsage -= previousChildCount * sizeof(OctreeElement*); delete[] _children.external; _children.external = NULL; // probably not needed! _childrenExternal = false; @@ -508,9 +459,9 @@ void VoxelNode::storeTwoChildren(VoxelNode* childOne, VoxelNode* childTwo) { if (!_childrenExternal) { _childrenExternal = true; const int newChildCount = 2; - _externalChildrenMemoryUsage += newChildCount * sizeof(VoxelNode*); - _children.external = new VoxelNode*[newChildCount]; - memset(_children.external, 0, sizeof(VoxelNode*) * newChildCount); + _externalChildrenMemoryUsage += newChildCount * sizeof(OctreeElement*); + _children.external = new OctreeElement*[newChildCount]; + memset(_children.external, 0, sizeof(OctreeElement*) * newChildCount); } _children.external[0] = childOne; _children.external[1] = childTwo; @@ -518,7 +469,7 @@ void VoxelNode::storeTwoChildren(VoxelNode* childOne, VoxelNode* childTwo) { } } -void VoxelNode::retrieveTwoChildren(VoxelNode*& childOne, VoxelNode*& childTwo) { +void OctreeElement::retrieveTwoChildren(OctreeElement*& childOne, OctreeElement*& childTwo) { // If we previously had an external array, then get the if (_childrenExternal) { childOne = _children.external[0]; @@ -528,17 +479,17 @@ void VoxelNode::retrieveTwoChildren(VoxelNode*& childOne, VoxelNode*& childTwo) _childrenExternal = false; _twoChildrenExternalCount--; const int newChildCount = 2; - _externalChildrenMemoryUsage -= newChildCount * sizeof(VoxelNode*); + _externalChildrenMemoryUsage -= newChildCount * sizeof(OctreeElement*); } else { int64_t offsetOne = _children.offsetsTwoChildren[0]; int64_t offsetTwo = _children.offsetsTwoChildren[1]; - childOne = (VoxelNode*)((uint8_t*)this + offsetOne); - childTwo = (VoxelNode*)((uint8_t*)this + offsetTwo); + childOne = (OctreeElement*)((uint8_t*)this + offsetOne); + childTwo = (OctreeElement*)((uint8_t*)this + offsetTwo); _twoChildrenOffsetCount--; } } -void VoxelNode::decodeThreeOffsets(int64_t& offsetOne, int64_t& offsetTwo, int64_t& offsetThree) const { +void OctreeElement::decodeThreeOffsets(int64_t& offsetOne, int64_t& offsetTwo, int64_t& offsetThree) const { const uint64_t ENCODE_BITS = 21; const uint64_t ENCODE_MASK = 0xFFFFF; const uint64_t ENCODE_MASK_SIGN = 0x100000; @@ -560,7 +511,7 @@ void VoxelNode::decodeThreeOffsets(int64_t& offsetOne, int64_t& offsetTwo, int64 offsetThree = threeNegative ? -offsetEncodedThree : offsetEncodedThree; } -void VoxelNode::encodeThreeOffsets(int64_t offsetOne, int64_t offsetTwo, int64_t offsetThree) { +void OctreeElement::encodeThreeOffsets(int64_t offsetOne, int64_t offsetTwo, int64_t offsetThree) { const uint64_t ENCODE_BITS = 21; const uint64_t ENCODE_MASK = 0xFFFFF; const uint64_t ENCODE_MASK_SIGN = 0x100000; @@ -588,7 +539,7 @@ void VoxelNode::encodeThreeOffsets(int64_t offsetOne, int64_t offsetTwo, int64_t _children.offsetsThreeChildrenEncoded = offsetEncodedOne | offsetEncodedTwo | offsetEncodedThree; } -void VoxelNode::storeThreeChildren(VoxelNode* childOne, VoxelNode* childTwo, VoxelNode* childThree) { +void OctreeElement::storeThreeChildren(OctreeElement* childOne, OctreeElement* childTwo, OctreeElement* childThree) { int64_t offsetOne = (uint8_t*)childOne - (uint8_t*)this; int64_t offsetTwo = (uint8_t*)childTwo - (uint8_t*)this; int64_t offsetThree = (uint8_t*)childThree - (uint8_t*)this; @@ -607,7 +558,7 @@ void VoxelNode::storeThreeChildren(VoxelNode* childOne, VoxelNode* childTwo, Vox _children.external = NULL; // probably not needed! _childrenExternal = false; const int previousChildCount = 3; - _externalChildrenMemoryUsage -= previousChildCount * sizeof(VoxelNode*); + _externalChildrenMemoryUsage -= previousChildCount * sizeof(OctreeElement*); } // encode in union encodeThreeOffsets(offsetOne, offsetTwo, offsetThree); @@ -619,9 +570,9 @@ void VoxelNode::storeThreeChildren(VoxelNode* childOne, VoxelNode* childTwo, Vox if (!_childrenExternal) { _childrenExternal = true; const int newChildCount = 3; - _externalChildrenMemoryUsage += newChildCount * sizeof(VoxelNode*); - _children.external = new VoxelNode*[newChildCount]; - memset(_children.external, 0, sizeof(VoxelNode*) * newChildCount); + _externalChildrenMemoryUsage += newChildCount * sizeof(OctreeElement*); + _children.external = new OctreeElement*[newChildCount]; + memset(_children.external, 0, sizeof(OctreeElement*) * newChildCount); } _children.external[0] = childOne; _children.external[1] = childTwo; @@ -630,7 +581,7 @@ void VoxelNode::storeThreeChildren(VoxelNode* childOne, VoxelNode* childTwo, Vox } } -void VoxelNode::retrieveThreeChildren(VoxelNode*& childOne, VoxelNode*& childTwo, VoxelNode*& childThree) { +void OctreeElement::retrieveThreeChildren(OctreeElement*& childOne, OctreeElement*& childTwo, OctreeElement*& childThree) { // If we previously had an external array, then get the if (_childrenExternal) { childOne = _children.external[0]; @@ -640,19 +591,19 @@ void VoxelNode::retrieveThreeChildren(VoxelNode*& childOne, VoxelNode*& childTwo _children.external = NULL; // probably not needed! _childrenExternal = false; _threeChildrenExternalCount--; - _externalChildrenMemoryUsage -= 3 * sizeof(VoxelNode*); + _externalChildrenMemoryUsage -= 3 * sizeof(OctreeElement*); } else { int64_t offsetOne, offsetTwo, offsetThree; decodeThreeOffsets(offsetOne, offsetTwo, offsetThree); - childOne = (VoxelNode*)((uint8_t*)this + offsetOne); - childTwo = (VoxelNode*)((uint8_t*)this + offsetTwo); - childThree = (VoxelNode*)((uint8_t*)this + offsetThree); + childOne = (OctreeElement*)((uint8_t*)this + offsetOne); + childTwo = (OctreeElement*)((uint8_t*)this + offsetTwo); + childThree = (OctreeElement*)((uint8_t*)this + offsetThree); _threeChildrenOffsetCount--; } } -void VoxelNode::checkStoreFourChildren(VoxelNode* childOne, VoxelNode* childTwo, VoxelNode* childThree, VoxelNode* childFour) { +void OctreeElement::checkStoreFourChildren(OctreeElement* childOne, OctreeElement* childTwo, OctreeElement* childThree, OctreeElement* childFour) { int64_t offsetOne = (uint8_t*)childOne - (uint8_t*)this; int64_t offsetTwo = (uint8_t*)childTwo - (uint8_t*)this; int64_t offsetThree = (uint8_t*)childThree - (uint8_t*)this; @@ -675,10 +626,10 @@ void VoxelNode::checkStoreFourChildren(VoxelNode* childOne, VoxelNode* childTwo, } #endif -void VoxelNode::deleteAllChildren() { - // first delete all the VoxelNode objects... +void OctreeElement::deleteAllChildren() { + // first delete all the OctreeElement objects... for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* childAt = getChildAtIndex(i); + OctreeElement* childAt = getChildAtIndex(i); if (childAt) { delete childAt; } @@ -731,7 +682,7 @@ void VoxelNode::deleteAllChildren() { #endif // BLENDED_UNION_CHILDREN } -void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { +void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) { #ifdef SIMPLE_CHILD_ARRAY int previousChildCount = getChildCount(); if (child) { @@ -775,20 +726,20 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { } else if (previousChildCount == 0 && newChildCount == 1) { _children.single = child; } else if (previousChildCount == 1 && newChildCount == 2) { - VoxelNode* previousChild = _children.single; - _children.external = new VoxelNode*[NUMBER_OF_CHILDREN]; - memset(_children.external, 0, sizeof(VoxelNode*) * NUMBER_OF_CHILDREN); + OctreeElement* previousChild = _children.single; + _children.external = new OctreeElement*[NUMBER_OF_CHILDREN]; + memset(_children.external, 0, sizeof(OctreeElement*) * NUMBER_OF_CHILDREN); _children.external[firstIndex] = previousChild; _children.external[childIndex] = child; - _externalChildrenMemoryUsage += NUMBER_OF_CHILDREN * sizeof(VoxelNode*); + _externalChildrenMemoryUsage += NUMBER_OF_CHILDREN * sizeof(OctreeElement*); } else if (previousChildCount == 2 && newChildCount == 1) { assert(child == NULL); // we are removing a child, so this must be true! - VoxelNode* previousFirstChild = _children.external[firstIndex]; - VoxelNode* previousSecondChild = _children.external[secondIndex]; + OctreeElement* previousFirstChild = _children.external[firstIndex]; + OctreeElement* previousSecondChild = _children.external[secondIndex]; delete[] _children.external; - _externalChildrenMemoryUsage -= NUMBER_OF_CHILDREN * sizeof(VoxelNode*); + _externalChildrenMemoryUsage -= NUMBER_OF_CHILDREN * sizeof(OctreeElement*); if (childIndex == firstIndex) { _children.single = previousSecondChild; } else { @@ -839,8 +790,8 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { // If we had 1 child, and we're adding a second child, then we need to determine // if we can use offsets to store them - VoxelNode* childOne; - VoxelNode* childTwo; + OctreeElement* childOne; + OctreeElement* childTwo; if (getNthBit(previousChildMask, 1) < childIndex) { childOne = _children.single; @@ -859,8 +810,8 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { int indexTwo = getNthBit(previousChildMask, 2); bool keepChildOne = indexTwo == childIndex; - VoxelNode* childOne; - VoxelNode* childTwo; + OctreeElement* childOne; + OctreeElement* childTwo; retrieveTwoChildren(childOne, childTwo); @@ -878,8 +829,8 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { bool replaceChildOne = indexOne == childIndex; // Get the existing two children out of their encoding... - VoxelNode* childOne; - VoxelNode* childTwo; + OctreeElement* childOne; + OctreeElement* childTwo; retrieveTwoChildren(childOne, childTwo); if (replaceChildOne) { @@ -894,9 +845,9 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { // If we had 2 children, and now have 3, then we know we are going to an external case... // First, decode the children... - VoxelNode* childOne; - VoxelNode* childTwo; - VoxelNode* childThree; + OctreeElement* childOne; + OctreeElement* childTwo; + OctreeElement* childThree; // Get the existing two children out of their encoding... retrieveTwoChildren(childOne, childTwo); @@ -926,9 +877,9 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { bool removeChildOne = indexOne == childIndex; bool removeChildTwo = indexTwo == childIndex; - VoxelNode* childOne; - VoxelNode* childTwo; - VoxelNode* childThree; + OctreeElement* childOne; + OctreeElement* childTwo; + OctreeElement* childThree; // Get the existing two children out of their encoding... retrieveThreeChildren(childOne, childTwo, childThree); @@ -953,9 +904,9 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { bool replaceChildOne = indexOne == childIndex; bool replaceChildTwo = indexTwo == childIndex; - VoxelNode* childOne; - VoxelNode* childTwo; - VoxelNode* childThree; + OctreeElement* childOne; + OctreeElement* childTwo; + OctreeElement* childThree; // Get the existing two children out of their encoding... retrieveThreeChildren(childOne, childTwo, childThree); @@ -973,10 +924,10 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { // If we had 3 children, and now have 4, then we know we are going to an external case... // First, decode the children... - VoxelNode* childOne; - VoxelNode* childTwo; - VoxelNode* childThree; - VoxelNode* childFour; + OctreeElement* childOne; + OctreeElement* childTwo; + OctreeElement* childThree; + OctreeElement* childFour; // Get the existing two children out of their encoding... retrieveThreeChildren(childOne, childTwo, childThree); @@ -1005,10 +956,10 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { // now, allocate the external... _childrenExternal = true; const int newChildCount = 4; - _children.external = new VoxelNode*[newChildCount]; - memset(_children.external, 0, sizeof(VoxelNode*) * newChildCount); + _children.external = new OctreeElement*[newChildCount]; + memset(_children.external, 0, sizeof(OctreeElement*) * newChildCount); - _externalChildrenMemoryUsage += newChildCount * sizeof(VoxelNode*); + _externalChildrenMemoryUsage += newChildCount * sizeof(OctreeElement*); _children.external[0] = childOne; _children.external[1] = childTwo; @@ -1028,10 +979,10 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { bool removeChildTwo = indexTwo == childIndex; bool removeChildThree = indexThree == childIndex; - VoxelNode* childOne = _children.external[0]; - VoxelNode* childTwo = _children.external[1]; - VoxelNode* childThree = _children.external[2]; - VoxelNode* childFour = _children.external[3]; + OctreeElement* childOne = _children.external[0]; + OctreeElement* childTwo = _children.external[1]; + OctreeElement* childThree = _children.external[2]; + OctreeElement* childFour = _children.external[3]; if (removeChildOne) { childOne = childTwo; @@ -1051,7 +1002,7 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { delete[] _children.external; _children.external = NULL; _externalChildrenCount--; - _externalChildrenMemoryUsage -= previousChildCount * sizeof(VoxelNode*); + _externalChildrenMemoryUsage -= previousChildCount * sizeof(OctreeElement*); storeThreeChildren(childOne, childTwo, childThree); } else if (previousChildCount == newChildCount) { //assert(_children.external && _childrenExternal && previousChildCount >= 4); @@ -1075,8 +1026,8 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { // 4 or more children, one item being added, we know we're stored externally, we just figure out where to insert // this child pointer into our external list - VoxelNode** newExternalList = new VoxelNode*[newChildCount]; - memset(newExternalList, 0, sizeof(VoxelNode*) * newChildCount); + OctreeElement** newExternalList = new OctreeElement*[newChildCount]; + memset(newExternalList, 0, sizeof(OctreeElement*) * newChildCount); int copiedCount = 0; for (int ordinal = 1; ordinal <= newChildCount; ordinal++) { @@ -1101,8 +1052,8 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { } delete[] _children.external; _children.external = newExternalList; - _externalChildrenMemoryUsage -= previousChildCount * sizeof(VoxelNode*); - _externalChildrenMemoryUsage += newChildCount * sizeof(VoxelNode*); + _externalChildrenMemoryUsage -= previousChildCount * sizeof(OctreeElement*); + _externalChildrenMemoryUsage += newChildCount * sizeof(OctreeElement*); } else if (previousChildCount > newChildCount) { //assert(_children.external && _childrenExternal && previousChildCount >= 4); @@ -1110,7 +1061,7 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { // 4 or more children, one item being removed, we know we're stored externally, we just figure out which // item to remove from our external list - VoxelNode** newExternalList = new VoxelNode*[newChildCount]; + OctreeElement** newExternalList = new OctreeElement*[newChildCount]; for (int ordinal = 1; ordinal <= previousChildCount; ordinal++) { int index = getNthBit(previousChildMask, ordinal); @@ -1127,8 +1078,8 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { } delete[] _children.external; _children.external = newExternalList; - _externalChildrenMemoryUsage -= previousChildCount * sizeof(VoxelNode*); - _externalChildrenMemoryUsage += newChildCount * sizeof(VoxelNode*); + _externalChildrenMemoryUsage -= previousChildCount * sizeof(OctreeElement*); + _externalChildrenMemoryUsage += newChildCount * sizeof(OctreeElement*); } else { //assert(false); qDebug("THIS SHOULD NOT HAPPEN previousChildCount == %d && newChildCount == %d\n",previousChildCount, newChildCount); @@ -1149,16 +1100,16 @@ void VoxelNode::setChildAtIndex(int childIndex, VoxelNode* child) { } -VoxelNode* VoxelNode::addChildAtIndex(int childIndex) { - VoxelNode* childAt = getChildAtIndex(childIndex); +OctreeElement* OctreeElement::addChildAtIndex(int childIndex) { + OctreeElement* childAt = getChildAtIndex(childIndex); if (!childAt) { // before adding a child, see if we're currently a leaf if (isLeaf()) { _voxelNodeLeafCount--; } - childAt = new VoxelNode(childOctalCode(getOctalCode(), childIndex)); - childAt->setVoxelSystem(getVoxelSystem()); // our child is always part of our voxel system NULL ok + childAt = new OctreeElement(childOctalCode(getOctalCode(), childIndex)); + //childAt->setVoxelSystem(getVoxelSystem()); // our child is always part of our voxel system NULL ok setChildAtIndex(childIndex, childAt); _isDirty = true; @@ -1168,12 +1119,12 @@ VoxelNode* VoxelNode::addChildAtIndex(int childIndex) { } // handles staging or deletion of all deep children -void VoxelNode::safeDeepDeleteChildAtIndex(int childIndex, int recursionCount) { +void OctreeElement::safeDeepDeleteChildAtIndex(int childIndex, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - qDebug() << "VoxelNode::safeDeepDeleteChildAtIndex() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n"; + qDebug() << "OctreeElement::safeDeepDeleteChildAtIndex() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n"; return; } - VoxelNode* childToDelete = getChildAtIndex(childIndex); + OctreeElement* childToDelete = getChildAtIndex(childIndex); if (childToDelete) { // If the child is not a leaf, then call ourselves recursively on all the children if (!childToDelete->isLeaf()) { @@ -1188,180 +1139,37 @@ void VoxelNode::safeDeepDeleteChildAtIndex(int childIndex, int recursionCount) { } } -// will average the child colors... -void VoxelNode::setColorFromAverageOfChildren() { - int colorArray[4] = {0,0,0,0}; - float density = 0.0f; - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* childAt = getChildAtIndex(i); - if (childAt && childAt->isColored()) { - for (int j = 0; j < 3; j++) { - colorArray[j] += childAt->getTrueColor()[j]; // color averaging should always be based on true colors - } - colorArray[3]++; - } - if (childAt) { - density += childAt->getDensity(); - } - } - density /= (float) NUMBER_OF_CHILDREN; - // - // The VISIBLE_ABOVE_DENSITY sets the density of matter above which an averaged color voxel will - // be set. It is an important physical constant in our universe. A number below 0.5 will cause - // things to get 'fatter' at a distance, because upward averaging will make larger voxels out of - // less data, which is (probably) going to be preferable because it gives a sense that there is - // something out there to go investigate. A number above 0.5 would cause the world to become - // more 'empty' at a distance. Exactly 0.5 would match the physical world, at least for materials - // that are not shiny and have equivalent ambient reflectance. - // - const float VISIBLE_ABOVE_DENSITY = 0.10f; - nodeColor newColor = { 0, 0, 0, 0}; - if (density > VISIBLE_ABOVE_DENSITY) { - // The density of material in the space of the voxel sets whether it is actually colored - for (int c = 0; c < 3; c++) { - // set the average color value - newColor[c] = colorArray[c] / colorArray[3]; - } - // set the alpha to 1 to indicate that this isn't transparent - newColor[3] = 1; - } - // Set the color from the average of the child colors, and update the density - setColor(newColor); - setDensity(density); -} -// Note: !NO_FALSE_COLOR implementations of setFalseColor(), setFalseColored(), and setColor() here. -// the actual NO_FALSE_COLOR version are inline in the VoxelNode.h -#ifndef NO_FALSE_COLOR // !NO_FALSE_COLOR means, does have false color -void VoxelNode::setFalseColor(colorPart red, colorPart green, colorPart blue) { - if (_falseColored != true || _currentColor[0] != red || _currentColor[1] != green || _currentColor[2] != blue) { - _falseColored=true; - _currentColor[0] = red; - _currentColor[1] = green; - _currentColor[2] = blue; - _currentColor[3] = 1; // XXXBHG - False colors are always considered set - _isDirty = true; - markWithChangedTime(); - } -} - -void VoxelNode::setFalseColored(bool isFalseColored) { - if (_falseColored != isFalseColored) { - // if we were false colored, and are no longer false colored, then swap back - if (_falseColored && !isFalseColored) { - memcpy(&_currentColor,&_trueColor,sizeof(nodeColor)); - } - _falseColored = isFalseColored; - _isDirty = true; - _density = 1.0f; // If color set, assume leaf, re-averaging will update density if needed. - markWithChangedTime(); - } -}; - - -void VoxelNode::setColor(const nodeColor& color) { - if (_trueColor[0] != color[0] || _trueColor[1] != color[1] || _trueColor[2] != color[2]) { - memcpy(&_trueColor,&color,sizeof(nodeColor)); - if (!_falseColored) { - memcpy(&_currentColor,&color,sizeof(nodeColor)); - } - _isDirty = true; - _density = 1.0f; // If color set, assume leaf, re-averaging will update density if needed. - markWithChangedTime(); - } -} -#endif - - - - -// will detect if children are leaves AND the same color -// and in that case will delete the children and make this node -// a leaf, returns TRUE if all the leaves are collapsed into a -// single node -bool VoxelNode::collapseIdenticalLeaves() { - // scan children, verify that they are ALL present and accounted for - bool allChildrenMatch = true; // assume the best (ottimista) - int red,green,blue; - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* childAt = getChildAtIndex(i); - // if no child, child isn't a leaf, or child doesn't have a color - if (!childAt || !childAt->isLeaf() || !childAt->isColored()) { - allChildrenMatch=false; - //qDebug("SADNESS child missing or not colored! i=%d\n",i); - break; - } else { - if (i==0) { - red = childAt->getColor()[0]; - green = childAt->getColor()[1]; - blue = childAt->getColor()[2]; - } else if (red != childAt->getColor()[0] || - green != childAt->getColor()[1] || blue != childAt->getColor()[2]) { - allChildrenMatch=false; - break; - } - } - } - - - if (allChildrenMatch) { - //qDebug("allChildrenMatch: pruning tree\n"); - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* childAt = getChildAtIndex(i); - delete childAt; // delete all the child nodes - setChildAtIndex(i, NULL); // set it to NULL - } - nodeColor collapsedColor; - collapsedColor[0]=red; - collapsedColor[1]=green; - collapsedColor[2]=blue; - collapsedColor[3]=1; // color is set - setColor(collapsedColor); - } - return allChildrenMatch; -} - -void VoxelNode::setRandomColor(int minimumBrightness) { - nodeColor newColor; - for (int c = 0; c < 3; c++) { - newColor[c] = randomColorValue(minimumBrightness); - } - - newColor[3] = 1; - setColor(newColor); -} - -void VoxelNode::printDebugDetails(const char* label) const { +void OctreeElement::printDebugDetails(const char* label) const { unsigned char childBits = 0; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* childAt = getChildAtIndex(i); + OctreeElement* childAt = getChildAtIndex(i); if (childAt) { setAtBit(childBits,i); } } - qDebug("%s - Voxel at corner=(%f,%f,%f) size=%f\n isLeaf=%s isColored=%s (%d,%d,%d,%d) isDirty=%s shouldRender=%s\n children=", label, + qDebug("%s - Voxel at corner=(%f,%f,%f) size=%f\n isLeaf=%s isDirty=%s shouldRender=%s\n children=", label, _box.getCorner().x, _box.getCorner().y, _box.getCorner().z, _box.getScale(), - debug::valueOf(isLeaf()), debug::valueOf(isColored()), getColor()[0], getColor()[1], getColor()[2], getColor()[3], - debug::valueOf(isDirty()), debug::valueOf(getShouldRender())); + debug::valueOf(isLeaf()), debug::valueOf(isDirty()), debug::valueOf(getShouldRender())); outputBits(childBits, false); qDebug("\n octalCode="); printOctalCode(getOctalCode()); } -float VoxelNode::getEnclosingRadius() const { +float OctreeElement::getEnclosingRadius() const { return getScale() * sqrtf(3.0f) / 2.0f; } -bool VoxelNode::isInView(const ViewFrustum& viewFrustum) const { +bool OctreeElement::isInView(const ViewFrustum& viewFrustum) const { AABox box = _box; // use temporary box so we can scale it box.scale(TREE_SCALE); bool inView = (ViewFrustum::OUTSIDE != viewFrustum.boxInFrustum(box)); return inView; } -ViewFrustum::location VoxelNode::inFrustum(const ViewFrustum& viewFrustum) const { +ViewFrustum::location OctreeElement::inFrustum(const ViewFrustum& viewFrustum) const { AABox box = _box; // use temporary box so we can scale it box.scale(TREE_SCALE); return viewFrustum.boxInFrustum(box); @@ -1376,9 +1184,9 @@ ViewFrustum::location VoxelNode::inFrustum(const ViewFrustum& viewFrustum) const // Since, if we know the camera position and orientation, we can know which of the corners is the "furthest" // corner. We can use we can use this corner as our "voxel position" to do our distance calculations off of. // By doing this, we don't need to test each child voxel's position vs the LOD boundary -bool VoxelNode::calculateShouldRender(const ViewFrustum* viewFrustum, float voxelScaleSize, int boundaryLevelAdjust) const { +bool OctreeElement::calculateShouldRender(const ViewFrustum* viewFrustum, float voxelScaleSize, int boundaryLevelAdjust) const { bool shouldRender = false; - if (isColored()) { + if (hasContent()) { float furthestDistance = furthestDistanceToCamera(*viewFrustum); float boundary = boundaryDistanceForRenderLevel(getLevel() + boundaryLevelAdjust, voxelScaleSize); float childBoundary = boundaryDistanceForRenderLevel(getLevel() + 1 + boundaryLevelAdjust, voxelScaleSize); @@ -1390,7 +1198,7 @@ bool VoxelNode::calculateShouldRender(const ViewFrustum* viewFrustum, float voxe } // Calculates the distance to the furthest point of the voxel to the camera -float VoxelNode::furthestDistanceToCamera(const ViewFrustum& viewFrustum) const { +float OctreeElement::furthestDistanceToCamera(const ViewFrustum& viewFrustum) const { AABox box = getAABox(); box.scale(TREE_SCALE); glm::vec3 furthestPoint = viewFrustum.getFurthestPointFromCamera(box); @@ -1399,35 +1207,35 @@ float VoxelNode::furthestDistanceToCamera(const ViewFrustum& viewFrustum) const return distanceToVoxelCenter; } -float VoxelNode::distanceToCamera(const ViewFrustum& viewFrustum) const { +float OctreeElement::distanceToCamera(const ViewFrustum& viewFrustum) const { glm::vec3 center = _box.calcCenter() * (float)TREE_SCALE; glm::vec3 temp = viewFrustum.getPosition() - center; float distanceToVoxelCenter = sqrtf(glm::dot(temp, temp)); return distanceToVoxelCenter; } -float VoxelNode::distanceSquareToPoint(const glm::vec3& point) const { +float OctreeElement::distanceSquareToPoint(const glm::vec3& point) const { glm::vec3 temp = point - _box.calcCenter(); float distanceSquare = glm::dot(temp, temp); return distanceSquare; } -float VoxelNode::distanceToPoint(const glm::vec3& point) const { +float OctreeElement::distanceToPoint(const glm::vec3& point) const { glm::vec3 temp = point - _box.calcCenter(); float distance = sqrtf(glm::dot(temp, temp)); return distance; } -QReadWriteLock VoxelNode::_deleteHooksLock; -std::vector VoxelNode::_deleteHooks; +QReadWriteLock OctreeElement::_deleteHooksLock; +std::vector OctreeElement::_deleteHooks; -void VoxelNode::addDeleteHook(VoxelNodeDeleteHook* hook) { +void OctreeElement::addDeleteHook(OctreeElementDeleteHook* hook) { _deleteHooksLock.lockForWrite(); _deleteHooks.push_back(hook); _deleteHooksLock.unlock(); } -void VoxelNode::removeDeleteHook(VoxelNodeDeleteHook* hook) { +void OctreeElement::removeDeleteHook(OctreeElementDeleteHook* hook) { _deleteHooksLock.lockForWrite(); for (int i = 0; i < _deleteHooks.size(); i++) { if (_deleteHooks[i] == hook) { @@ -1438,21 +1246,21 @@ void VoxelNode::removeDeleteHook(VoxelNodeDeleteHook* hook) { _deleteHooksLock.unlock(); } -void VoxelNode::notifyDeleteHooks() { +void OctreeElement::notifyDeleteHooks() { _deleteHooksLock.lockForRead(); for (int i = 0; i < _deleteHooks.size(); i++) { - _deleteHooks[i]->voxelDeleted(this); + _deleteHooks[i]->elementDeleted(this); } _deleteHooksLock.unlock(); } -std::vector VoxelNode::_updateHooks; +std::vector OctreeElement::_updateHooks; -void VoxelNode::addUpdateHook(VoxelNodeUpdateHook* hook) { +void OctreeElement::addUpdateHook(OctreeElementUpdateHook* hook) { _updateHooks.push_back(hook); } -void VoxelNode::removeUpdateHook(VoxelNodeUpdateHook* hook) { +void OctreeElement::removeUpdateHook(OctreeElementUpdateHook* hook) { for (int i = 0; i < _updateHooks.size(); i++) { if (_updateHooks[i] == hook) { _updateHooks.erase(_updateHooks.begin() + i); @@ -1461,8 +1269,8 @@ void VoxelNode::removeUpdateHook(VoxelNodeUpdateHook* hook) { } } -void VoxelNode::notifyUpdateHooks() { +void OctreeElement::notifyUpdateHooks() { for (int i = 0; i < _updateHooks.size(); i++) { - _updateHooks[i]->voxelUpdated(this); + _updateHooks[i]->elementUpdated(this); } } \ No newline at end of file diff --git a/libraries/voxels/src/VoxelNode.h b/libraries/octree/src/OctreeElement.h similarity index 65% rename from libraries/voxels/src/VoxelNode.h rename to libraries/octree/src/OctreeElement.h index 3e10a2fc7b..0682ce7f74 100644 --- a/libraries/voxels/src/VoxelNode.h +++ b/libraries/octree/src/OctreeElement.h @@ -1,13 +1,13 @@ // -// VoxelNode.h +// OctreeElement.h // hifi // // Created by Stephen Birarda on 3/13/13. // // -#ifndef __hifi__VoxelNode__ -#define __hifi__VoxelNode__ +#ifndef __hifi__OctreeElement__ +#define __hifi__OctreeElement__ //#define HAS_AUDIT_CHILDREN //#define SIMPLE_CHILD_ARRAY @@ -18,45 +18,47 @@ #include #include "AABox.h" #include "ViewFrustum.h" -#include "VoxelConstants.h" +#include "OctreeConstants.h" +//#include "Octree.h" -class VoxelTree; // forward declaration -class VoxelNode; // forward declaration -class VoxelSystem; // forward declaration - -typedef unsigned char colorPart; -typedef unsigned char nodeColor[4]; -typedef unsigned char rgbColor[3]; +class Octree; +class OctreeElement; +class OctreeElementDeleteHook; +class OctreePacketData; +class VoxelSystem; +class ReadBitstreamToTreeParams; // Callers who want delete hook callbacks should implement this class -class VoxelNodeDeleteHook { +class OctreeElementDeleteHook { public: - virtual void voxelDeleted(VoxelNode* node) = 0; + virtual void elementDeleted(OctreeElement* element) = 0; }; // Callers who want update hook callbacks should implement this class -class VoxelNodeUpdateHook { +class OctreeElementUpdateHook { public: - virtual void voxelUpdated(VoxelNode* node) = 0; + virtual void elementUpdated(OctreeElement* element) = 0; }; -class VoxelNode { +class OctreeElement { + +protected: + // can only be constructed by derived implementation + OctreeElement(unsigned char * octalCode = NULL); + public: - VoxelNode(); // root node constructor - VoxelNode(unsigned char * octalCode); // regular constructor - ~VoxelNode(); + virtual ~OctreeElement(); const unsigned char* getOctalCode() const { return (_octcodePointer) ? _octalCode.pointer : &_octalCode.buffer[0]; } - VoxelNode* getChildAtIndex(int childIndex) const; + OctreeElement* getChildAtIndex(int childIndex) const; void deleteChildAtIndex(int childIndex); - VoxelNode* removeChildAtIndex(int childIndex); - VoxelNode* addChildAtIndex(int childIndex); + OctreeElement* removeChildAtIndex(int childIndex); + virtual OctreeElement* addChildAtIndex(int childIndex); void safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0); // handles deletion of all descendents - void setColorFromAverageOfChildren(); - void setRandomColor(int minimumBrightness); - bool collapseIdenticalLeaves(); + virtual void calculateAverageFromChildren() { }; + virtual bool collapseChildren() { return false; }; const AABox& getAABox() const { return _box; } const glm::vec3& getCorner() const { return _box.getCorner(); } @@ -64,15 +66,21 @@ public: int getLevel() const { return numberOfThreeBitSectionsInCode(getOctalCode()) + 1; } float getEnclosingRadius() const; - - bool isColored() const { return _trueColor[3] == 1; } + + virtual bool hasContent() const { return isLeaf(); } + virtual void splitChildren() { } + virtual bool requiresSplit() const { return false; } + virtual bool appendElementData(OctreePacketData* packetData) const { return true; } + virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) + { return 0; } + bool isInView(const ViewFrustum& viewFrustum) const; ViewFrustum::location inFrustum(const ViewFrustum& viewFrustum) const; float distanceToCamera(const ViewFrustum& viewFrustum) const; float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const; bool calculateShouldRender(const ViewFrustum* viewFrustum, - float voxelSizeScale = DEFAULT_VOXEL_SIZE_SCALE, int boundaryLevelAdjust = 0) const; + float voxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const; // points are assumed to be in Voxel Coordinates (not TREE_SCALE'd) float distanceSquareToPoint(const glm::vec3& point) const; // when you don't need the actual distance, use this. @@ -87,27 +95,11 @@ public: bool hasChangedSince(uint64_t time) const { return (_lastChanged > time); } void markWithChangedTime(); uint64_t getLastChanged() const { return _lastChanged; } - void handleSubtreeChanged(VoxelTree* myTree); + void handleSubtreeChanged(Octree* myTree); - glBufferIndex getBufferIndex() const { return _glBufferIndex; } - bool isKnownBufferIndex() const { return !_unknownBufferIndex; } - void setBufferIndex(glBufferIndex index) { _glBufferIndex = index; _unknownBufferIndex =(index == GLBUFFER_INDEX_UNKNOWN);} - VoxelSystem* getVoxelSystem() const; - void setVoxelSystem(VoxelSystem* voxelSystem); - // Used by VoxelSystem for rendering in/out of view and LOD void setShouldRender(bool shouldRender); bool getShouldRender() const { return _shouldRender; } - - void setFalseColor(colorPart red, colorPart green, colorPart blue); - void setFalseColored(bool isFalseColored); - bool getFalseColored() { return _falseColored; } - void setColor(const nodeColor& color); - const nodeColor& getTrueColor() const { return _trueColor; } - const nodeColor& getColor() const { return _currentColor; } - - void setDensity(float density) { _density = density; } - float getDensity() const { return _density; } void setSourceUUID(const QUuid& sourceID); QUuid getSourceUUID() const; @@ -115,11 +107,11 @@ public: bool matchesSourceUUID(const QUuid& sourceUUID) const; static uint16_t getSourceNodeUUIDKey(const QUuid& sourceUUID); - static void addDeleteHook(VoxelNodeDeleteHook* hook); - static void removeDeleteHook(VoxelNodeDeleteHook* hook); + static void addDeleteHook(OctreeElementDeleteHook* hook); + static void removeDeleteHook(OctreeElementDeleteHook* hook); - static void addUpdateHook(VoxelNodeUpdateHook* hook); - static void removeUpdateHook(VoxelNodeUpdateHook* hook); + static void addUpdateHook(OctreeElementUpdateHook* hook); + static void removeUpdateHook(OctreeElementUpdateHook* hook); static unsigned long getNodeCount() { return _voxelNodeCount; } static unsigned long getInternalNodeCount() { return _voxelNodeCount - _voxelNodeLeafCount; } @@ -154,21 +146,21 @@ public: #endif // def HAS_AUDIT_CHILDREN #endif // def BLENDED_UNION_CHILDREN -private: +protected: void deleteAllChildren(); - void setChildAtIndex(int childIndex, VoxelNode* child); + void setChildAtIndex(int childIndex, OctreeElement* child); #ifdef BLENDED_UNION_CHILDREN - void storeTwoChildren(VoxelNode* childOne, VoxelNode* childTwo); - void retrieveTwoChildren(VoxelNode*& childOne, VoxelNode*& childTwo); - void storeThreeChildren(VoxelNode* childOne, VoxelNode* childTwo, VoxelNode* childThree); - void retrieveThreeChildren(VoxelNode*& childOne, VoxelNode*& childTwo, VoxelNode*& childThree); + void storeTwoChildren(OctreeElement* childOne, OctreeElement* childTwo); + void retrieveTwoChildren(OctreeElement*& childOne, OctreeElement*& childTwo); + void storeThreeChildren(OctreeElement* childOne, OctreeElement* childTwo, OctreeElement* childThree); + void retrieveThreeChildren(OctreeElement*& childOne, OctreeElement*& childTwo, OctreeElement*& childThree); void decodeThreeOffsets(int64_t& offsetOne, int64_t& offsetTwo, int64_t& offsetThree) const; void encodeThreeOffsets(int64_t offsetOne, int64_t offsetTwo, int64_t offsetThree); - void checkStoreFourChildren(VoxelNode* childOne, VoxelNode* childTwo, VoxelNode* childThree, VoxelNode* childFour); + void checkStoreFourChildren(OctreeElement* childOne, OctreeElement* childTwo, OctreeElement* childThree, OctreeElement* childFour); #endif void calculateAABox(); - void init(unsigned char * octalCode); + virtual void init(unsigned char * octalCode); void notifyDeleteHooks(); void notifyUpdateHooks(); @@ -184,45 +176,29 @@ private: /// Client and server, pointers to child nodes, various encodings #ifdef SIMPLE_CHILD_ARRAY - VoxelNode* _simpleChildArray[8]; /// Only used when SIMPLE_CHILD_ARRAY is enabled + OctreeElement* _simpleChildArray[8]; /// Only used when SIMPLE_CHILD_ARRAY is enabled #endif #ifdef SIMPLE_EXTERNAL_CHILDREN union children_t { - VoxelNode* single; - VoxelNode** external; + OctreeElement* single; + OctreeElement** external; } _children; #endif #ifdef BLENDED_UNION_CHILDREN union children_t { - VoxelNode* single; + OctreeElement* single; int32_t offsetsTwoChildren[2]; uint64_t offsetsThreeChildrenEncoded; - VoxelNode** external; + OctreeElement** external; } _children; #ifdef HAS_AUDIT_CHILDREN - VoxelNode* _childrenArray[8]; /// Only used when HAS_AUDIT_CHILDREN is enabled to help debug children encoding + OctreeElement* _childrenArray[8]; /// Only used when HAS_AUDIT_CHILDREN is enabled to help debug children encoding #endif // def HAS_AUDIT_CHILDREN #endif //def BLENDED_UNION_CHILDREN - uint32_t _glBufferIndex : 24, /// Client only, vbo index for this voxel if being rendered, 3 bytes - _voxelSystemIndex : 8; /// Client only, index to the VoxelSystem rendering this voxel, 1 bytes - - // Support for _voxelSystemIndex, we use these static member variables to track the VoxelSystems that are - // in use by various voxel nodes. We map the VoxelSystem pointers into an 1 byte key, this limits us to at - // most 255 voxel systems in use at a time within the client. Which is far more than we need. - static uint8_t _nextIndex; - static std::map _mapVoxelSystemPointersToIndex; - static std::map _mapIndexToVoxelSystemPointers; - - float _density; /// Client and server, If leaf: density = 1, if internal node: 0-1 density of voxels inside, 4 bytes - - nodeColor _trueColor; /// Client and server, true color of this voxel, 4 bytes - nodeColor _currentColor; /// Client only, false color of this voxel, 4 bytes - - uint16_t _sourceUUIDKey; /// Client only, stores node id of voxel server that sent his voxel, 2 bytes // Support for _sourceUUID, we use these static member variables to track the UUIDs that are @@ -242,10 +218,10 @@ private: _childrenExternal : 1; /// Client only, is this voxel's VBO buffer the unknown buffer index, 1 bit static QReadWriteLock _deleteHooksLock; - static std::vector _deleteHooks; + static std::vector _deleteHooks; //static QReadWriteLock _updateHooksLock; - static std::vector _updateHooks; + static std::vector _updateHooks; static uint64_t _voxelNodeCount; static uint64_t _voxelNodeLeafCount; @@ -272,4 +248,4 @@ private: static uint64_t _childrenCount[NUMBER_OF_CHILDREN + 1]; }; -#endif /* defined(__hifi__VoxelNode__) */ \ No newline at end of file +#endif /* defined(__hifi__OctreeElement__) */ \ No newline at end of file diff --git a/libraries/voxels/src/VoxelNodeBag.cpp b/libraries/octree/src/OctreeElementBag.cpp similarity index 65% rename from libraries/voxels/src/VoxelNodeBag.cpp rename to libraries/octree/src/OctreeElementBag.cpp index 60bf41d939..c3ad44a604 100644 --- a/libraries/voxels/src/VoxelNodeBag.cpp +++ b/libraries/octree/src/OctreeElementBag.cpp @@ -1,27 +1,27 @@ // -// VoxelNodeBag.cpp +// OctreeElementBag.cpp // hifi // // Created by Brad Hefta-Gaub on 4/25/2013 // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // -#include "VoxelNodeBag.h" +#include "OctreeElementBag.h" #include -VoxelNodeBag::VoxelNodeBag() : +OctreeElementBag::OctreeElementBag() : _bagElements(NULL), _elementsInUse(0), _sizeOfElementsArray(0) { - VoxelNode::addDeleteHook(this); + OctreeElement::addDeleteHook(this); }; -VoxelNodeBag::~VoxelNodeBag() { - VoxelNode::removeDeleteHook(this); +OctreeElementBag::~OctreeElementBag() { + OctreeElement::removeDeleteHook(this); deleteAll(); } -void VoxelNodeBag::deleteAll() { +void OctreeElementBag::deleteAll() { if (_bagElements) { delete[] _bagElements; } @@ -34,18 +34,18 @@ void VoxelNodeBag::deleteAll() { const int GROW_BAG_BY = 100; // put a node into the bag -void VoxelNodeBag::insert(VoxelNode* node) { +void OctreeElementBag::insert(OctreeElement* element) { // Search for where we should live in the bag (sorted) // Note: change this to binary search... instead of linear! int insertAt = _elementsInUse; for (int i = 0; i < _elementsInUse; i++) { // just compare the pointers... that's good enough - if (_bagElements[i] == node) { + if (_bagElements[i] == element) { return; // exit early!! } - if (_bagElements[i] > node) { + if (_bagElements[i] > element) { insertAt = i; break; } @@ -54,51 +54,51 @@ void VoxelNodeBag::insert(VoxelNode* node) { // If we don't have room in our bag, then grow the bag if (_sizeOfElementsArray < _elementsInUse + 1) { - VoxelNode** oldBag = _bagElements; - _bagElements = new VoxelNode * [_sizeOfElementsArray + GROW_BAG_BY]; + OctreeElement** oldBag = _bagElements; + _bagElements = new OctreeElement*[_sizeOfElementsArray + GROW_BAG_BY]; _sizeOfElementsArray += GROW_BAG_BY; // If we had an old bag... if (oldBag) { // copy old elements into the new bag, but leave a space where we need to // insert the new node - memcpy(_bagElements, oldBag, insertAt * sizeof(VoxelNode*)); - memcpy(&_bagElements[insertAt + 1], &oldBag[insertAt], (_elementsInUse - insertAt) * sizeof(VoxelNode*)); + memcpy(_bagElements, oldBag, insertAt * sizeof(OctreeElement*)); + memcpy(&_bagElements[insertAt + 1], &oldBag[insertAt], (_elementsInUse - insertAt) * sizeof(OctreeElement*)); delete[] oldBag; } } else { // move existing elements further back in the bag array, leave a space where we need to // insert the new node - memmove(&_bagElements[insertAt + 1], &_bagElements[insertAt], (_elementsInUse - insertAt) * sizeof(VoxelNode*)); + memmove(&_bagElements[insertAt + 1], &_bagElements[insertAt], (_elementsInUse - insertAt) * sizeof(OctreeElement*)); } - _bagElements[insertAt] = node; + _bagElements[insertAt] = element; _elementsInUse++; } // pull a node out of the bag (could come in any order) -VoxelNode* VoxelNodeBag::extract() { +OctreeElement* OctreeElementBag::extract() { // pull the last node out, and shrink our list... if (_elementsInUse) { // get the last element - VoxelNode* node = _bagElements[_elementsInUse - 1]; + OctreeElement* element = _bagElements[_elementsInUse - 1]; // reduce the count _elementsInUse--; - return node; + return element; } return NULL; } -bool VoxelNodeBag::contains(VoxelNode* node) { +bool OctreeElementBag::contains(OctreeElement* element) { for (int i = 0; i < _elementsInUse; i++) { // just compare the pointers... that's good enough - if (_bagElements[i] == node) { + if (_bagElements[i] == element) { return true; // exit early!! } // if we're past where it should be, then it's not here! - if (_bagElements[i] > node) { + if (_bagElements[i] > element) { return false; } } @@ -106,29 +106,29 @@ bool VoxelNodeBag::contains(VoxelNode* node) { return false; } -void VoxelNodeBag::remove(VoxelNode* node) { +void OctreeElementBag::remove(OctreeElement* element) { int foundAt = -1; for (int i = 0; i < _elementsInUse; i++) { // just compare the pointers... that's good enough - if (_bagElements[i] == node) { + if (_bagElements[i] == element) { foundAt = i; break; } // if we're past where it should be, then it's not here! - if (_bagElements[i] > node) { + if (_bagElements[i] > element) { break; } } // if we found it, then we need to remove it.... if (foundAt != -1) { - memmove(&_bagElements[foundAt], &_bagElements[foundAt + 1], (_elementsInUse - foundAt) * sizeof(VoxelNode*)); + memmove(&_bagElements[foundAt], &_bagElements[foundAt + 1], (_elementsInUse - foundAt) * sizeof(OctreeElement*)); _elementsInUse--; } } -void VoxelNodeBag::voxelDeleted(VoxelNode* node) { - remove(node); // note: remove can safely handle nodes that aren't in it, so we don't need to check contains() +void OctreeElementBag::elementDeleted(OctreeElement* element) { + remove(element); // note: remove can safely handle nodes that aren't in it, so we don't need to check contains() } diff --git a/libraries/octree/src/OctreeElementBag.h b/libraries/octree/src/OctreeElementBag.h new file mode 100644 index 0000000000..49587d04a0 --- /dev/null +++ b/libraries/octree/src/OctreeElementBag.h @@ -0,0 +1,47 @@ +// +// OctreeElementBag.h +// hifi +// +// Created by Brad Hefta-Gaub on 4/25/2013 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// This class is used by the VoxelTree:encodeTreeBitstream() functions to store extra nodes that need to be sent +// it's a generic bag style storage mechanism. But It has the property that you can't put the same node into the bag +// more than once (in other words, it de-dupes automatically), also, it supports collapsing it's several peer nodes +// into a parent node in cases where you add enough peers that it makes more sense to just add the parent. +// + +#ifndef __hifi__OctreeElementBag__ +#define __hifi__OctreeElementBag__ + +#include "OctreeElement.h" + +class OctreeElementBag : public OctreeElementDeleteHook { + +public: + OctreeElementBag(); + ~OctreeElementBag(); + + void insert(OctreeElement* element); // put a element into the bag + OctreeElement* extract(); // pull a element out of the bag (could come in any order) + bool contains(OctreeElement* element); // is this element in the bag? + void remove(OctreeElement* element); // remove a specific element from the bag + + bool isEmpty() const { return (_elementsInUse == 0); } + int count() const { return _elementsInUse; } + + void deleteAll(); + + //static void voxelNodeDeleteHook(OctreeElement* element, void* extraData); + + virtual void elementDeleted(OctreeElement* element); + +private: + + OctreeElement** _bagElements; + int _elementsInUse; + int _sizeOfElementsArray; + //int _hookID; +}; + +#endif /* defined(__hifi__OctreeElementBag__) */ diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp new file mode 100644 index 0000000000..9b119e611c --- /dev/null +++ b/libraries/octree/src/OctreePacketData.cpp @@ -0,0 +1,325 @@ +// +// OctreePacketData.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 11/19/2013. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#include +#include "OctreePacketData.h" + +bool OctreePacketData::_debug = false; +uint64_t OctreePacketData::_totalBytesOfOctalCodes = 0; +uint64_t OctreePacketData::_totalBytesOfBitMasks = 0; +uint64_t OctreePacketData::_totalBytesOfColor = 0; + + + +OctreePacketData::OctreePacketData(bool enableCompression, int targetSize) { + changeSettings(enableCompression, targetSize); // does reset... +} + +void OctreePacketData::changeSettings(bool enableCompression, int targetSize) { + _enableCompression = enableCompression; + _targetSize = std::min(MAX_OCTREE_UNCOMRESSED_PACKET_SIZE, targetSize); + reset(); +} + +void OctreePacketData::reset() { + _bytesInUse = 0; + _bytesAvailable = _targetSize; + _subTreeAt = 0; + _compressedBytes = 0; + _bytesInUseLastCheck = 0; + _dirty = false; + + _bytesOfOctalCodes = 0; + _bytesOfBitMasks = 0; + _bytesOfColor = 0; + _bytesOfOctalCodesCurrentSubTree = 0; +} + +OctreePacketData::~OctreePacketData() { +} + +bool OctreePacketData::append(const unsigned char* data, int length) { + bool success = false; + + if (length <= _bytesAvailable) { + memcpy(&_uncompressed[_bytesInUse], data, length); + _bytesInUse += length; + _bytesAvailable -= length; + success = true; + _dirty = true; + } + return success; +} + +bool OctreePacketData::append(unsigned char byte) { + bool success = false; + if (_bytesAvailable > 0) { + _uncompressed[_bytesInUse] = byte; + _bytesInUse++; + _bytesAvailable--; + success = true; + _dirty = true; + } + return success; +} + +bool OctreePacketData::updatePriorBitMask(int offset, unsigned char bitmask) { + bool success = false; + if (offset >= 0 && offset < _bytesInUse) { + _uncompressed[offset] = bitmask; + success = true; + _dirty = true; + } + return success; +} + +bool OctreePacketData::updatePriorBytes(int offset, const unsigned char* replacementBytes, int length) { + bool success = false; + if (length >= 0 && offset >= 0 && ((offset + length) <= _bytesInUse)) { + memcpy(&_uncompressed[offset], replacementBytes, length); // copy new content + success = true; + _dirty = true; + } + return success; +} + +bool OctreePacketData::startSubTree(const unsigned char* octcode) { + _bytesOfOctalCodesCurrentSubTree = _bytesOfOctalCodes; + bool success = false; + int possibleStartAt = _bytesInUse; + int length = 0; + if (octcode) { + length = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(octcode)); + success = append(octcode, length); // handles checking compression + } else { + // NULL case, means root node, which is 0 + unsigned char byte = 0; + length = 1; + success = append(byte); // handles checking compression + } + if (success) { + _subTreeAt = possibleStartAt; + } + if (success) { + _bytesOfOctalCodes += length; + _totalBytesOfOctalCodes += length; + } + return success; +} + +const unsigned char* OctreePacketData::getFinalizedData() { + if (!_enableCompression) { + return &_uncompressed[0]; + } + + if (_dirty) { + if (_debug) { + printf("getFinalizedData() _compressedBytes=%d _bytesInUse=%d\n",_compressedBytes, _bytesInUse); + } + compressContent(); + } + return &_compressed[0]; +} + +int OctreePacketData::getFinalizedSize() { + if (!_enableCompression) { + return _bytesInUse; + } + + if (_dirty) { + if (_debug) { + printf("getFinalizedSize() _compressedBytes=%d _bytesInUse=%d\n",_compressedBytes, _bytesInUse); + } + compressContent(); + } + + return _compressedBytes; +} + + +void OctreePacketData::endSubTree() { + _subTreeAt = _bytesInUse; +} + +void OctreePacketData::discardSubTree() { + int bytesInSubTree = _bytesInUse - _subTreeAt; + _bytesInUse -= bytesInSubTree; + _bytesAvailable += bytesInSubTree; + _subTreeAt = _bytesInUse; // should be the same actually... + _dirty = true; + + // rewind to start of this subtree, other items rewound by endLevel() + int reduceBytesOfOctalCodes = _bytesOfOctalCodes - _bytesOfOctalCodesCurrentSubTree; + _bytesOfOctalCodes = _bytesOfOctalCodesCurrentSubTree; + _totalBytesOfOctalCodes -= reduceBytesOfOctalCodes; +} + +LevelDetails OctreePacketData::startLevel() { + LevelDetails key(_bytesInUse, _bytesOfOctalCodes, _bytesOfBitMasks, _bytesOfColor); + return key; +} + +void OctreePacketData::discardLevel(LevelDetails key) { + int bytesInLevel = _bytesInUse - key._startIndex; + + // reset statistics... + int reduceBytesOfOctalCodes = _bytesOfOctalCodes - key._bytesOfOctalCodes; + int reduceBytesOfBitMasks = _bytesOfBitMasks - key._bytesOfBitmasks; + int reduceBytesOfColor = _bytesOfColor - key._bytesOfColor; + + _bytesOfOctalCodes = key._bytesOfOctalCodes; + _bytesOfBitMasks = key._bytesOfBitmasks; + _bytesOfColor = key._bytesOfColor; + + _totalBytesOfOctalCodes -= reduceBytesOfOctalCodes; + _totalBytesOfBitMasks -= reduceBytesOfBitMasks; + _totalBytesOfColor -= reduceBytesOfColor; + + if (_debug) { + printf("discardLevel() BEFORE _dirty=%s bytesInLevel=%d _compressedBytes=%d _bytesInUse=%d\n", + debug::valueOf(_dirty), bytesInLevel, _compressedBytes, _bytesInUse); + } + + _bytesInUse -= bytesInLevel; + _bytesAvailable += bytesInLevel; + _dirty = true; + + if (_debug) { + printf("discardLevel() AFTER _dirty=%s bytesInLevel=%d _compressedBytes=%d _bytesInUse=%d\n", + debug::valueOf(_dirty), bytesInLevel, _compressedBytes, _bytesInUse); + } +} + +bool OctreePacketData::endLevel(LevelDetails key) { + bool success = true; + return success; +} + +bool OctreePacketData::appendBitMask(unsigned char bitmask) { + bool success = append(bitmask); // handles checking compression + if (success) { + _bytesOfBitMasks++; + _totalBytesOfBitMasks++; + } + return success; +} + +bool OctreePacketData::appendColor(const nodeColor& color) { + // eventually we can make this use a dictionary... + bool success = false; + const int BYTES_PER_COLOR = 3; + if (_bytesAvailable > BYTES_PER_COLOR) { + // handles checking compression... + if (append(color[RED_INDEX])) { + if (append(color[GREEN_INDEX])) { + if (append(color[BLUE_INDEX])) { + success = true; + } + } + } + } + if (success) { + _bytesOfColor += BYTES_PER_COLOR; + _totalBytesOfColor += BYTES_PER_COLOR; + } + return success; +} + +uint64_t OctreePacketData::_compressContentTime = 0; +uint64_t OctreePacketData::_compressContentCalls = 0; + +bool OctreePacketData::compressContent() { + PerformanceWarning warn(false, "OctreePacketData::compressContent()", false, &_compressContentTime, &_compressContentCalls); + + // without compression, we always pass... + if (!_enableCompression) { + return true; + } + + _bytesInUseLastCheck = _bytesInUse; + + bool success = false; + const int MAX_COMPRESSION = 9; + + // we only want to compress the data payload, not the message header + const uchar* uncompressedData = &_uncompressed[0]; + int uncompressedSize = _bytesInUse; + + QByteArray compressedData = qCompress(uncompressedData, uncompressedSize, MAX_COMPRESSION); + + if (compressedData.size() < MAX_OCTREE_PACKET_DATA_SIZE) { + _compressedBytes = compressedData.size(); + for (int i = 0; i < _compressedBytes; i++) { + _compressed[i] = compressedData[i]; + } + _dirty = false; + success = true; + } + return success; +} + + +void OctreePacketData::loadFinalizedContent(const unsigned char* data, int length) { + reset(); + + if (data && length > 0) { + + if (_enableCompression) { + QByteArray compressedData; + for (int i = 0; i < length; i++) { + compressedData[i] = data[i]; + _compressed[i] = compressedData[i]; + } + _compressedBytes = length; + QByteArray uncompressedData = qUncompress(compressedData); + if (uncompressedData.size() <= _bytesAvailable) { + _bytesInUse = uncompressedData.size(); + _bytesAvailable -= uncompressedData.size(); + + for (int i = 0; i < _bytesInUse; i++) { + _uncompressed[i] = uncompressedData[i]; + } + } + } else { + for (int i = 0; i < length; i++) { + _uncompressed[i] = _compressed[i] = data[i]; + } + _bytesInUse = _compressedBytes = length; + } + } else { + if (_debug) { + printf("OctreePacketData::loadCompressedContent()... length = 0, nothing to do...\n"); + } + } +} + +void OctreePacketData::debugContent() { + printf("OctreePacketData::debugContent()... COMPRESSED DATA.... size=%d\n",_compressedBytes); + int perline=0; + for (int i = 0; i < _compressedBytes; i++) { + printf("%.2x ",_compressed[i]); + perline++; + if (perline >= 30) { + printf("\n"); + perline=0; + } + } + printf("\n"); + + printf("OctreePacketData::debugContent()... UNCOMPRESSED DATA.... size=%d\n",_bytesInUse); + perline=0; + for (int i = 0; i < _bytesInUse; i++) { + printf("%.2x ",_uncompressed[i]); + perline++; + if (perline >= 30) { + printf("\n"); + perline=0; + } + } + printf("\n"); +} diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h new file mode 100644 index 0000000000..f53e854179 --- /dev/null +++ b/libraries/octree/src/OctreePacketData.h @@ -0,0 +1,182 @@ +// +// OctreePacketData.h +// hifi +// +// Created by Brad Hefta-Gaub on 11/19/2013 +// +// TO DO: +// +// * add stats tracking for number of unique colors and consecutive identical colors. +// (as research for color dictionaries and RLE) +// +// * further testing of compression to determine optimal configuration for performance and compression +// +// * improve semantics for "reshuffle" - current approach will work for now and with compression +// but wouldn't work with RLE because the colors in the levels would get reordered and RLE would need +// to be recalculated +// + +#ifndef __hifi__OctreePacketData__ +#define __hifi__OctreePacketData__ + +#include +#include "OctreeConstants.h" +#include "OctreeElement.h" + +typedef unsigned char OCTREE_PACKET_FLAGS; +typedef uint16_t OCTREE_PACKET_SEQUENCE; +typedef uint64_t OCTREE_PACKET_SENT_TIME; +typedef uint16_t OCTREE_PACKET_INTERNAL_SECTION_SIZE; +const int MAX_OCTREE_PACKET_SIZE = MAX_PACKET_SIZE; +const int OCTREE_PACKET_HEADER_SIZE = (sizeof(PACKET_TYPE) + sizeof(PACKET_VERSION) + sizeof(OCTREE_PACKET_FLAGS) + + sizeof(OCTREE_PACKET_SEQUENCE) + sizeof(OCTREE_PACKET_SENT_TIME)); + +const int MAX_OCTREE_PACKET_DATA_SIZE = MAX_PACKET_SIZE - OCTREE_PACKET_HEADER_SIZE; + +const int MAX_OCTREE_UNCOMRESSED_PACKET_SIZE = MAX_OCTREE_PACKET_DATA_SIZE; + +const int MINIMUM_ATTEMPT_MORE_PACKING = sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) + 40; +const int COMPRESS_PADDING = 15; +const int REASONABLE_NUMBER_OF_PACKING_ATTEMPTS = 5; + +const int PACKET_IS_COLOR_BIT = 0; +const int PACKET_IS_COMPRESSED_BIT = 1; + +/// An opaque key used when starting, ending, and discarding encoding/packing levels of OctreePacketData +class LevelDetails { + LevelDetails(int startIndex, int bytesOfOctalCodes, int bytesOfBitmasks, int bytesOfColor) : + _startIndex(startIndex), + _bytesOfOctalCodes(bytesOfOctalCodes), + _bytesOfBitmasks(bytesOfBitmasks), + _bytesOfColor(bytesOfColor) { + } + + friend class OctreePacketData; + +private: + int _startIndex; + int _bytesOfOctalCodes; + int _bytesOfBitmasks; + int _bytesOfColor; +}; + +/// Handles packing of the data portion of PACKET_TYPE_OCTREE_DATA messages. +class OctreePacketData { +public: + OctreePacketData(bool enableCompression = false, int maxFinalizedSize = MAX_OCTREE_PACKET_DATA_SIZE); + ~OctreePacketData(); + + /// change compression and target size settings + void changeSettings(bool enableCompression = false, int targetSize = MAX_OCTREE_PACKET_DATA_SIZE); + + /// reset completely, all data is discarded + void reset(); + + /// call to begin encoding a subtree starting at this point, this will append the octcode to the uncompressed stream + /// at this point. May fail if new datastream is too long. In failure case the stream remains in it's previous state. + bool startSubTree(const unsigned char* octcode = NULL); + + // call to indicate that the current subtree is complete and changes should be committed. + void endSubTree(); + + // call rollback the current subtree and restore the stream to the state prior to starting the subtree encoding + void discardSubTree(); + + /// starts a level marker. returns an opaque key which can be used to discard the level + LevelDetails startLevel(); + + /// discards all content back to a previous marker key + void discardLevel(LevelDetails key); + + /// ends a level, and performs any expensive finalization. may fail if finalization creates a stream which is too large + /// if the finalization would fail, the packet will automatically discard the previous level. + bool endLevel(LevelDetails key); + + /// appends a bitmask to the end of the stream, may fail if new data stream is too long to fit in packet + bool appendBitMask(unsigned char bitmask); + + /// updates the value of a bitmask from a previously appended portion of the uncompressed stream, might fail if the new + /// bitmask would cause packet to be less compressed, or if offset was out of range. + bool updatePriorBitMask(int offset, unsigned char bitmask); + + /// updates the uncompressed content of the stream starting at byte offset with replacementBytes for length. + /// Might fail if the new bytes would cause packet to be less compressed, or if offset and length was out of range. + bool updatePriorBytes(int offset, const unsigned char* replacementBytes, int length); + + /// appends a color to the end of the stream, may fail if new data stream is too long to fit in packet + bool appendColor(const nodeColor& color); + + /// returns a byte offset from beginning of the uncompressed stream based on offset from end. + /// Positive offsetFromEnd returns that many bytes before the end of uncompressed stream + int getUncompressedByteOffset(int offsetFromEnd = 0) const { return _bytesInUse - offsetFromEnd; } + + /// get access to the finalized data (it may be compressed or rewritten into optimal form) + const unsigned char* getFinalizedData(); + /// get size of the finalized data (it may be compressed or rewritten into optimal form) + int getFinalizedSize(); + + /// get pointer to the start of uncompressed stream buffer + const unsigned char* getUncompressedData() { return &_uncompressed[0]; } + /// the size of the packet in uncompressed form + int getUncompressedSize() { return _bytesInUse; } + + /// has some content been written to the packet + bool hasContent() const { return (_bytesInUse > 0); } + + /// load finalized content to allow access to decoded content for parsing + void loadFinalizedContent(const unsigned char* data, int length); + + /// returns whether or not zlib compression enabled on finalization + bool isCompressed() const { return _enableCompression; } + + /// returns the target uncompressed size + int getTargetSize() const { return _targetSize; } + + /// displays contents for debugging + void debugContent(); + + static uint64_t getCompressContentTime() { return _compressContentTime; } /// total time spent compressing content + static uint64_t getCompressContentCalls() { return _compressContentCalls; } /// total calls to compress content + static uint64_t getTotalBytesOfOctalCodes() { return _totalBytesOfOctalCodes; } /// total bytes for octal codes + static uint64_t getTotalBytesOfBitMasks() { return _totalBytesOfBitMasks; } /// total bytes of bitmasks + static uint64_t getTotalBytesOfColor() { return _totalBytesOfColor; } /// total bytes of color + +private: + /// appends raw bytes, might fail if byte would cause packet to be too large + bool append(const unsigned char* data, int length); + + /// append a single byte, might fail if byte would cause packet to be too large + bool append(unsigned char byte); + + int _targetSize; + bool _enableCompression; + + unsigned char _uncompressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE]; + int _bytesInUse; + int _bytesAvailable; + int _subTreeAt; + + bool compressContent(); + + unsigned char _compressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE]; + int _compressedBytes; + int _bytesInUseLastCheck; + bool _dirty; + + // statistics... + int _bytesOfOctalCodes; + int _bytesOfBitMasks; + int _bytesOfColor; + int _bytesOfOctalCodesCurrentSubTree; + + static bool _debug; + + static uint64_t _compressContentTime; + static uint64_t _compressContentCalls; + + static uint64_t _totalBytesOfOctalCodes; + static uint64_t _totalBytesOfBitMasks; + static uint64_t _totalBytesOfColor; +}; + +#endif /* defined(__hifi__OctreePacketData__) */ \ No newline at end of file diff --git a/libraries/voxels/src/VoxelProjectedPolygon.cpp b/libraries/octree/src/OctreeProjectedPolygon.cpp similarity index 96% rename from libraries/voxels/src/VoxelProjectedPolygon.cpp rename to libraries/octree/src/OctreeProjectedPolygon.cpp index a6336665ad..0f9e66c42a 100644 --- a/libraries/voxels/src/VoxelProjectedPolygon.cpp +++ b/libraries/octree/src/OctreeProjectedPolygon.cpp @@ -1,5 +1,5 @@ // -// VoxelProjectedPolygon.cpp - The projected shadow (on the 2D view plane) for a voxel +// OctreeProjectedPolygon.cpp - The projected shadow (on the 2D view plane) for a voxel // hifi // // Added by Brad Hefta-Gaub on 06/11/13. @@ -11,7 +11,7 @@ #include "GeometryUtil.h" #include "SharedUtil.h" -#include "VoxelProjectedPolygon.h" +#include "OctreeProjectedPolygon.h" glm::vec2 BoundingBox::getVertex(int vertexNumber) const { @@ -100,12 +100,12 @@ void BoundingBox::printDebugDetails(const char* label) const { } -long VoxelProjectedPolygon::pointInside_calls = 0; -long VoxelProjectedPolygon::occludes_calls = 0; -long VoxelProjectedPolygon::intersects_calls = 0; +long OctreeProjectedPolygon::pointInside_calls = 0; +long OctreeProjectedPolygon::occludes_calls = 0; +long OctreeProjectedPolygon::intersects_calls = 0; -VoxelProjectedPolygon::VoxelProjectedPolygon(const BoundingBox& box) : +OctreeProjectedPolygon::OctreeProjectedPolygon(const BoundingBox& box) : _vertexCount(4), _maxX(-FLT_MAX), _maxY(-FLT_MAX), _minX(FLT_MAX), _minY(FLT_MAX), _distance(0) @@ -116,7 +116,7 @@ VoxelProjectedPolygon::VoxelProjectedPolygon(const BoundingBox& box) : } -void VoxelProjectedPolygon::setVertex(int vertex, const glm::vec2& point) { +void OctreeProjectedPolygon::setVertex(int vertex, const glm::vec2& point) { _vertices[vertex] = point; // keep track of our bounding box @@ -136,9 +136,9 @@ void VoxelProjectedPolygon::setVertex(int vertex, const glm::vec2& point) { }; // can be optimized with new pointInside() -bool VoxelProjectedPolygon::occludes(const VoxelProjectedPolygon& occludee, bool checkAllInView) const { +bool OctreeProjectedPolygon::occludes(const OctreeProjectedPolygon& occludee, bool checkAllInView) const { - VoxelProjectedPolygon::occludes_calls++; + OctreeProjectedPolygon::occludes_calls++; // if we are completely out of view, then we definitely don't occlude! // if the occludee is completely out of view, then we also don't occlude it @@ -195,12 +195,12 @@ bool VoxelProjectedPolygon::occludes(const VoxelProjectedPolygon& occludee, bool return false; // if we got this far, then we're not occluded } -bool VoxelProjectedPolygon::occludes(const BoundingBox& boxOccludee) const { - VoxelProjectedPolygon testee(boxOccludee); +bool OctreeProjectedPolygon::occludes(const BoundingBox& boxOccludee) const { + OctreeProjectedPolygon testee(boxOccludee); return occludes(testee); } -bool VoxelProjectedPolygon::matches(const VoxelProjectedPolygon& testee) const { +bool OctreeProjectedPolygon::matches(const OctreeProjectedPolygon& testee) const { if (testee.getVertexCount() != getVertexCount()) { return false; } @@ -229,14 +229,14 @@ bool VoxelProjectedPolygon::matches(const VoxelProjectedPolygon& testee) const { return true; // all of our vertices match, therefore we're the same } -bool VoxelProjectedPolygon::matches(const BoundingBox& box) const { - VoxelProjectedPolygon testee(box); +bool OctreeProjectedPolygon::matches(const BoundingBox& box) const { + OctreeProjectedPolygon testee(box); return matches(testee); } -bool VoxelProjectedPolygon::pointInside(const glm::vec2& point, bool* matchesVertex) const { +bool OctreeProjectedPolygon::pointInside(const glm::vec2& point, bool* matchesVertex) const { - VoxelProjectedPolygon::pointInside_calls++; + OctreeProjectedPolygon::pointInside_calls++; // first check the bounding boxes, the point must be fully within the boounding box of this polygon if ((point.x > getMaxX()) || @@ -262,8 +262,8 @@ bool VoxelProjectedPolygon::pointInside(const glm::vec2& point, bool* matchesVer return true; } -void VoxelProjectedPolygon::printDebugDetails() const { - printf("VoxelProjectedPolygon..."); +void OctreeProjectedPolygon::printDebugDetails() const { + printf("OctreeProjectedPolygon..."); printf(" minX=%f maxX=%f minY=%f maxY=%f\n", getMinX(), getMaxX(), getMinY(), getMaxY()); printf(" vertex count=%d distance=%f\n", getVertexCount(), getDistance()); for (int i = 0; i < getVertexCount(); i++) { @@ -272,13 +272,13 @@ void VoxelProjectedPolygon::printDebugDetails() const { } } -bool VoxelProjectedPolygon::intersects(const BoundingBox& box) const { - VoxelProjectedPolygon testee(box); +bool OctreeProjectedPolygon::intersects(const BoundingBox& box) const { + OctreeProjectedPolygon testee(box); return intersects(testee); } -bool VoxelProjectedPolygon::intersects(const VoxelProjectedPolygon& testee) const { - VoxelProjectedPolygon::intersects_calls++; +bool OctreeProjectedPolygon::intersects(const OctreeProjectedPolygon& testee) const { + OctreeProjectedPolygon::intersects_calls++; return intersectsOnAxes(testee) && testee.intersectsOnAxes(*this); } @@ -292,7 +292,7 @@ bool VoxelProjectedPolygon::intersects(const VoxelProjectedPolygon& testee) cons // Note: this only works on convex polygons // // -bool VoxelProjectedPolygon::intersectsOnAxes(const VoxelProjectedPolygon& testee) const { +bool OctreeProjectedPolygon::intersectsOnAxes(const OctreeProjectedPolygon& testee) const { // consider each edge of this polygon as a potential separating axis for (int i = 0; i < getVertexCount(); i++) { @@ -322,7 +322,7 @@ bool VoxelProjectedPolygon::intersectsOnAxes(const VoxelProjectedPolygon& testee return true; } -bool VoxelProjectedPolygon::canMerge(const VoxelProjectedPolygon& that) const { +bool OctreeProjectedPolygon::canMerge(const OctreeProjectedPolygon& that) const { // RIGHT/NEAR // LEFT/NEAR @@ -640,7 +640,7 @@ bool VoxelProjectedPolygon::canMerge(const VoxelProjectedPolygon& that) const { } -void VoxelProjectedPolygon::merge(const VoxelProjectedPolygon& that) { +void OctreeProjectedPolygon::merge(const OctreeProjectedPolygon& that) { // RIGHT/NEAR // LEFT/NEAR diff --git a/libraries/voxels/src/VoxelProjectedPolygon.h b/libraries/octree/src/OctreeProjectedPolygon.h similarity index 84% rename from libraries/voxels/src/VoxelProjectedPolygon.h rename to libraries/octree/src/OctreeProjectedPolygon.h index 1d7ac9713c..94e55a11d5 100644 --- a/libraries/voxels/src/VoxelProjectedPolygon.h +++ b/libraries/octree/src/OctreeProjectedPolygon.h @@ -1,5 +1,5 @@ // -// VoxelProjectedPolygon.h - The projected shadow (on the 2D view plane) for a voxel +// OctreeProjectedPolygon.h - The projected shadow (on the 2D view plane) for a voxel // hifi // // Added by Brad Hefta-Gaub on 06/11/13. @@ -57,18 +57,18 @@ const int PROJECTION_NEAR = 16; const int PROJECTION_FAR = 32; const int PROJECTION_CLIPPED = 64; -class VoxelProjectedPolygon { +class OctreeProjectedPolygon { public: - VoxelProjectedPolygon(const BoundingBox& box); + OctreeProjectedPolygon(const BoundingBox& box); - VoxelProjectedPolygon(int vertexCount = 0) : + OctreeProjectedPolygon(int vertexCount = 0) : _vertexCount(vertexCount), _maxX(-FLT_MAX), _maxY(-FLT_MAX), _minX(FLT_MAX), _minY(FLT_MAX), _distance(0) { } - ~VoxelProjectedPolygon() { } + ~OctreeProjectedPolygon() { } const ProjectedVertices& getVertices() const { return _vertices; } const glm::vec2& getVertex(int i) const { return _vertices[i]; } void setVertex(int vertex, const glm::vec2& point); @@ -86,16 +86,16 @@ public: bool pointInside(const glm::vec2& point, bool* matchesVertex = NULL) const; - bool occludes(const VoxelProjectedPolygon& occludee, bool checkAllInView = false) const; + bool occludes(const OctreeProjectedPolygon& occludee, bool checkAllInView = false) const; bool occludes(const BoundingBox& occludee) const; - bool intersects(const VoxelProjectedPolygon& testee) const; + bool intersects(const OctreeProjectedPolygon& testee) const; bool intersects(const BoundingBox& box) const; - bool matches(const VoxelProjectedPolygon& testee) const; + bool matches(const OctreeProjectedPolygon& testee) const; bool matches(const BoundingBox& testee) const; - bool intersectsOnAxes(const VoxelProjectedPolygon& testee) const; + bool intersectsOnAxes(const OctreeProjectedPolygon& testee) const; - bool canMerge(const VoxelProjectedPolygon& that) const; - void merge(const VoxelProjectedPolygon& that); // replaces vertices of this with new merged version + bool canMerge(const OctreeProjectedPolygon& that) const; + void merge(const OctreeProjectedPolygon& that); // replaces vertices of this with new merged version float getMaxX() const { return _maxX; } float getMaxY() const { return _maxY; } diff --git a/libraries/octree/src/OctreeQuery.cpp b/libraries/octree/src/OctreeQuery.cpp new file mode 100644 index 0000000000..dd65fe5246 --- /dev/null +++ b/libraries/octree/src/OctreeQuery.cpp @@ -0,0 +1,152 @@ +// +// OctreeQuery.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 10/24/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include +#include +#include + +#include +#include +#include +#include +#include "OctreeConstants.h" + +#include "OctreeQuery.h" + +using namespace std; + +static const float fingerVectorRadix = 4; // bits of precision when converting from float<->fixed + +OctreeQuery::OctreeQuery(Node* owningNode) : + NodeData(owningNode), + _uuid(), + _cameraPosition(0,0,0), + _cameraOrientation(), + _cameraFov(0.0f), + _cameraAspectRatio(0.0f), + _cameraNearClip(0.0f), + _cameraFarClip(0.0f), + _wantColor(true), + _wantDelta(true), + _wantLowResMoving(true), + _wantOcclusionCulling(false), // disabled by default + _wantCompression(false), // disabled by default + _maxOctreePPS(DEFAULT_MAX_OCTREE_PPS), + _octreeElementSizeScale(DEFAULT_OCTREE_SIZE_SCALE) +{ + +} + +OctreeQuery::~OctreeQuery() { + // nothing to do +} + +int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) { + unsigned char* bufferStart = destinationBuffer; + + // TODO: DRY this up to a shared method + // that can pack any type given the number of bytes + // and return the number of bytes to push the pointer + + // UUID + QByteArray uuidByteArray = _uuid.toRfc4122(); + memcpy(destinationBuffer, uuidByteArray.constData(), uuidByteArray.size()); + destinationBuffer += uuidByteArray.size(); + + // camera details + memcpy(destinationBuffer, &_cameraPosition, sizeof(_cameraPosition)); + destinationBuffer += sizeof(_cameraPosition); + destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _cameraOrientation); + destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _cameraFov); + destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _cameraAspectRatio); + destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraNearClip); + destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraFarClip); + memcpy(destinationBuffer, &_cameraEyeOffsetPosition, sizeof(_cameraEyeOffsetPosition)); + destinationBuffer += sizeof(_cameraEyeOffsetPosition); + + // bitMask of less than byte wide items + unsigned char bitItems = 0; + if (_wantLowResMoving) { setAtBit(bitItems, WANT_LOW_RES_MOVING_BIT); } + if (_wantColor) { setAtBit(bitItems, WANT_COLOR_AT_BIT); } + if (_wantDelta) { setAtBit(bitItems, WANT_DELTA_AT_BIT); } + if (_wantOcclusionCulling) { setAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); } + if (_wantCompression) { setAtBit(bitItems, WANT_COMPRESSION); } + + *destinationBuffer++ = bitItems; + + // desired Max Octree PPS + memcpy(destinationBuffer, &_maxOctreePPS, sizeof(_maxOctreePPS)); + destinationBuffer += sizeof(_maxOctreePPS); + + // desired voxelSizeScale + memcpy(destinationBuffer, &_octreeElementSizeScale, sizeof(_octreeElementSizeScale)); + destinationBuffer += sizeof(_octreeElementSizeScale); + + // desired boundaryLevelAdjust + memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust)); + destinationBuffer += sizeof(_boundaryLevelAdjust); + + return destinationBuffer - bufferStart; +} + +// called on the other nodes - assigns it to my views of the others +int OctreeQuery::parseData(unsigned char* sourceBuffer, int numBytes) { + + // increment to push past the packet header + int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer); + sourceBuffer += numBytesPacketHeader; + + unsigned char* startPosition = sourceBuffer; + + // push past the node session UUID + sourceBuffer += NUM_BYTES_RFC4122_UUID; + + // user UUID + _uuid = QUuid::fromRfc4122(QByteArray((char*) sourceBuffer, NUM_BYTES_RFC4122_UUID)); + sourceBuffer += NUM_BYTES_RFC4122_UUID; + + // camera details + memcpy(&_cameraPosition, sourceBuffer, sizeof(_cameraPosition)); + sourceBuffer += sizeof(_cameraPosition); + sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _cameraOrientation); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_cameraFov); + sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer,_cameraAspectRatio); + sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraNearClip); + sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraFarClip); + memcpy(&_cameraEyeOffsetPosition, sourceBuffer, sizeof(_cameraEyeOffsetPosition)); + sourceBuffer += sizeof(_cameraEyeOffsetPosition); + + // voxel sending features... + unsigned char bitItems = 0; + bitItems = (unsigned char)*sourceBuffer++; + _wantLowResMoving = oneAtBit(bitItems, WANT_LOW_RES_MOVING_BIT); + _wantColor = oneAtBit(bitItems, WANT_COLOR_AT_BIT); + _wantDelta = oneAtBit(bitItems, WANT_DELTA_AT_BIT); + _wantOcclusionCulling = oneAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); + _wantCompression = oneAtBit(bitItems, WANT_COMPRESSION); + + // desired Max Octree PPS + memcpy(&_maxOctreePPS, sourceBuffer, sizeof(_maxOctreePPS)); + sourceBuffer += sizeof(_maxOctreePPS); + + // desired _octreeElementSizeScale + memcpy(&_octreeElementSizeScale, sourceBuffer, sizeof(_octreeElementSizeScale)); + sourceBuffer += sizeof(_octreeElementSizeScale); + + // desired boundaryLevelAdjust + memcpy(&_boundaryLevelAdjust, sourceBuffer, sizeof(_boundaryLevelAdjust)); + sourceBuffer += sizeof(_boundaryLevelAdjust); + + return sourceBuffer - startPosition; +} + +glm::vec3 OctreeQuery::calculateCameraDirection() const { + glm::vec3 direction = glm::vec3(_cameraOrientation * glm::vec4(IDENTITY_FRONT, 0.0f)); + return direction; +} + diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h new file mode 100644 index 0000000000..bcad22f4d0 --- /dev/null +++ b/libraries/octree/src/OctreeQuery.h @@ -0,0 +1,115 @@ +// +// OctreeQuery.h +// hifi +// +// Created by Stephen Birarda on 4/9/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__OctreeQuery__ +#define __hifi__OctreeQuery__ + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +// First bitset +const int WANT_LOW_RES_MOVING_BIT = 0; +const int WANT_COLOR_AT_BIT = 1; +const int WANT_DELTA_AT_BIT = 2; +const int WANT_OCCLUSION_CULLING_BIT = 3; +const int WANT_COMPRESSION = 4; // 5th bit + +class OctreeQuery : public NodeData { + Q_OBJECT + +public: + OctreeQuery(Node* owningNode = NULL); + virtual ~OctreeQuery(); + + int getBroadcastData(unsigned char* destinationBuffer); + int parseData(unsigned char* sourceBuffer, int numBytes); + + QUuid& getUUID() { return _uuid; } + void setUUID(const QUuid& uuid) { _uuid = uuid; } + + // getters for camera details + const glm::vec3& getCameraPosition() const { return _cameraPosition; } + const glm::quat& getCameraOrientation() const { return _cameraOrientation; } + float getCameraFov() const { return _cameraFov; } + float getCameraAspectRatio() const { return _cameraAspectRatio; } + float getCameraNearClip() const { return _cameraNearClip; } + float getCameraFarClip() const { return _cameraFarClip; } + const glm::vec3& getCameraEyeOffsetPosition() const { return _cameraEyeOffsetPosition; } + + glm::vec3 calculateCameraDirection() const; + + // setters for camera details + void setCameraPosition(const glm::vec3& position) { _cameraPosition = position; } + void setCameraOrientation(const glm::quat& orientation) { _cameraOrientation = orientation; } + void setCameraFov(float fov) { _cameraFov = fov; } + void setCameraAspectRatio(float aspectRatio) { _cameraAspectRatio = aspectRatio; } + void setCameraNearClip(float nearClip) { _cameraNearClip = nearClip; } + void setCameraFarClip(float farClip) { _cameraFarClip = farClip; } + void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; } + + // related to Octree Sending strategies + bool getWantColor() const { return _wantColor; } + bool getWantDelta() const { return _wantDelta; } + bool getWantLowResMoving() const { return _wantLowResMoving; } + bool getWantOcclusionCulling() const { return _wantOcclusionCulling; } + bool getWantCompression() const { return _wantCompression; } + int getMaxOctreePacketsPerSecond() const { return _maxOctreePPS; } + float getOctreeSizeScale() const { return _octreeElementSizeScale; } + int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } + +public slots: + void setWantLowResMoving(bool wantLowResMoving) { _wantLowResMoving = wantLowResMoving; } + void setWantColor(bool wantColor) { _wantColor = wantColor; } + void setWantDelta(bool wantDelta) { _wantDelta = wantDelta; } + void setWantOcclusionCulling(bool wantOcclusionCulling) { _wantOcclusionCulling = wantOcclusionCulling; } + void setWantCompression(bool wantCompression) { _wantCompression = wantCompression; } + void setMaxOctreePacketsPerSecond(int maxOctreePPS) { _maxOctreePPS = maxOctreePPS; } + void setOctreeSizeScale(float octreeSizeScale) { _octreeElementSizeScale = octreeSizeScale; } + void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; } + +protected: + QUuid _uuid; + + // camera details for the avatar + glm::vec3 _cameraPosition; + glm::quat _cameraOrientation; + float _cameraFov; + float _cameraAspectRatio; + float _cameraNearClip; + float _cameraFarClip; + glm::vec3 _cameraEyeOffsetPosition; + + // octree server sending items + bool _wantColor; + bool _wantDelta; + bool _wantLowResMoving; + bool _wantOcclusionCulling; + bool _wantCompression; + int _maxOctreePPS; + float _octreeElementSizeScale; /// used for LOD calculations + int _boundaryLevelAdjust; /// used for LOD calculations + +private: + // privatize the copy constructor and assignment operator so they cannot be called + OctreeQuery(const OctreeQuery&); + OctreeQuery& operator= (const OctreeQuery&); +}; + +#endif /* defined(__hifi__OctreeQuery__) */ diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp new file mode 100644 index 0000000000..646cc3a05c --- /dev/null +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -0,0 +1,842 @@ +// +// OctreeSceneStats.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 7/18/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// + +#include +#include + +#include +#include + +#include "OctreePacketData.h" +#include "OctreeElement.h" +#include "OctreeSceneStats.h" + + +const int samples = 100; +OctreeSceneStats::OctreeSceneStats() : + _elapsedAverage(samples), + _bitsPerOctreeAverage(samples), + _incomingFlightTimeAverage(samples), + _jurisdictionRoot(NULL) +{ + reset(); + _isReadyToSend = false; + _isStarted = false; + _lastFullTotalEncodeTime = 0; + _lastFullElapsed = 0; + _incomingPacket = 0; + _incomingBytes = 0; + _incomingWastedBytes = 0; + _incomingLastSequence = 0; + _incomingOutOfOrder = 0; + _incomingLikelyLost = 0; + +} + +// copy constructor +OctreeSceneStats::OctreeSceneStats(const OctreeSceneStats& other) : +_jurisdictionRoot(NULL) { + copyFromOther(other); +} + +// copy assignment +OctreeSceneStats& OctreeSceneStats::operator=(const OctreeSceneStats& other) { + copyFromOther(other); + return *this; +} + +void OctreeSceneStats::copyFromOther(const OctreeSceneStats& other) { + _totalEncodeTime = other._totalEncodeTime; + _elapsed = other._elapsed; + _lastFullTotalEncodeTime = other._lastFullTotalEncodeTime; + _lastFullElapsed = other._lastFullElapsed; + _encodeStart = other._encodeStart; + + _packets = other._packets; + _bytes = other._bytes; + _passes = other._passes; + + _totalElements = other._totalElements; + _totalInternal = other._totalInternal; + _totalLeaves = other._totalLeaves; + + _traversed = other._traversed; + _internal = other._internal; + _leaves = other._leaves; + + _skippedDistance = other._skippedDistance; + _internalSkippedDistance = other._internalSkippedDistance; + _leavesSkippedDistance = other._leavesSkippedDistance; + + _skippedOutOfView = other._skippedOutOfView; + _internalSkippedOutOfView = other._internalSkippedOutOfView; + _leavesSkippedOutOfView = other._leavesSkippedOutOfView; + + _skippedWasInView = other._skippedWasInView; + _internalSkippedWasInView = other._internalSkippedWasInView; + _leavesSkippedWasInView = other._leavesSkippedWasInView; + + _skippedNoChange = other._skippedNoChange; + _internalSkippedNoChange = other._internalSkippedNoChange; + _leavesSkippedNoChange = other._leavesSkippedNoChange; + + _skippedOccluded = other._skippedOccluded; + _internalSkippedOccluded = other._internalSkippedOccluded; + _leavesSkippedOccluded = other._leavesSkippedOccluded; + + _colorSent = other._colorSent; + _internalColorSent = other._internalColorSent; + _leavesColorSent = other._leavesColorSent; + + _didntFit = other._didntFit; + _internalDidntFit = other._internalDidntFit; + _leavesDidntFit = other._leavesDidntFit; + + _colorBitsWritten = other._colorBitsWritten; + _existsBitsWritten = other._existsBitsWritten; + _existsInPacketBitsWritten = other._existsInPacketBitsWritten; + _treesRemoved = other._treesRemoved; + + // before copying the jurisdictions, delete any current values... + if (_jurisdictionRoot) { + delete[] _jurisdictionRoot; + _jurisdictionRoot = NULL; + } + for (int i=0; i < _jurisdictionEndNodes.size(); i++) { + if (_jurisdictionEndNodes[i]) { + delete[] _jurisdictionEndNodes[i]; + } + } + _jurisdictionEndNodes.clear(); + + // Now copy the values from the other + if (other._jurisdictionRoot) { + int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(other._jurisdictionRoot)); + _jurisdictionRoot = new unsigned char[bytes]; + memcpy(_jurisdictionRoot, other._jurisdictionRoot, bytes); + } + for (int i=0; i < other._jurisdictionEndNodes.size(); i++) { + unsigned char* endNodeCode = other._jurisdictionEndNodes[i]; + if (endNodeCode) { + int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); + unsigned char* endNodeCodeCopy = new unsigned char[bytes]; + memcpy(endNodeCodeCopy, endNodeCode, bytes); + _jurisdictionEndNodes.push_back(endNodeCodeCopy); + } + } + + _incomingPacket = other._incomingPacket; + _incomingBytes = other._incomingBytes; + _incomingWastedBytes = other._incomingWastedBytes; + _incomingLastSequence = other._incomingLastSequence; + _incomingOutOfOrder = other._incomingOutOfOrder; + _incomingLikelyLost = other._incomingLikelyLost; +} + + +OctreeSceneStats::~OctreeSceneStats() { + reset(); +} + +void OctreeSceneStats::sceneStarted(bool isFullScene, bool isMoving, OctreeElement* root, JurisdictionMap* jurisdictionMap) { + reset(); // resets packet and octree stats + _isStarted = true; + _start = usecTimestampNow(); + + _totalElements = OctreeElement::getNodeCount(); + _totalInternal = OctreeElement::getInternalNodeCount(); + _totalLeaves = OctreeElement::getLeafNodeCount(); + + _isFullScene = isFullScene; + _isMoving = isMoving; + + if (_jurisdictionRoot) { + delete[] _jurisdictionRoot; + _jurisdictionRoot = NULL; + } + // clear existing endNodes before copying new ones... + for (int i=0; i < _jurisdictionEndNodes.size(); i++) { + if (_jurisdictionEndNodes[i]) { + delete[] _jurisdictionEndNodes[i]; + } + } + _jurisdictionEndNodes.clear(); + + // setup jurisdictions + if (jurisdictionMap) { + unsigned char* jurisdictionRoot = jurisdictionMap->getRootOctalCode(); + if (jurisdictionRoot) { + int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(jurisdictionRoot)); + _jurisdictionRoot = new unsigned char[bytes]; + memcpy(_jurisdictionRoot, jurisdictionRoot, bytes); + } + + // copy new endNodes... + for (int i=0; i < jurisdictionMap->getEndNodeCount(); i++) { + unsigned char* endNodeCode = jurisdictionMap->getEndNodeOctalCode(i); + if (endNodeCode) { + int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); + unsigned char* endNodeCodeCopy = new unsigned char[bytes]; + memcpy(endNodeCodeCopy, endNodeCode, bytes); + _jurisdictionEndNodes.push_back(endNodeCodeCopy); + } + } + } +} + +void OctreeSceneStats::sceneCompleted() { + if (_isStarted) { + _end = usecTimestampNow(); + _elapsed = _end - _start; + _elapsedAverage.updateAverage((float)_elapsed); + + if (_isFullScene) { + _lastFullElapsed = _elapsed; + _lastFullTotalEncodeTime = _totalEncodeTime; + } + + _statsMessageLength = packIntoMessage(_statsMessage, sizeof(_statsMessage)); + _isReadyToSend = true; + _isStarted = false; + } +} + +void OctreeSceneStats::encodeStarted() { + _encodeStart = usecTimestampNow(); +} + +void OctreeSceneStats::encodeStopped() { + _totalEncodeTime += (usecTimestampNow() - _encodeStart); +} + +void OctreeSceneStats::reset() { + _totalEncodeTime = 0; + _encodeStart = 0; + + _packets = 0; + _bytes = 0; + _passes = 0; + + _totalElements = 0; + _totalInternal = 0; + _totalLeaves = 0; + + _traversed = 0; + _internal = 0; + _leaves = 0; + + _skippedDistance = 0; + _internalSkippedDistance = 0; + _leavesSkippedDistance = 0; + + _skippedOutOfView = 0; + _internalSkippedOutOfView = 0; + _leavesSkippedOutOfView = 0; + + _skippedWasInView = 0; + _internalSkippedWasInView = 0; + _leavesSkippedWasInView = 0; + + _skippedNoChange = 0; + _internalSkippedNoChange = 0; + _leavesSkippedNoChange = 0; + + _skippedOccluded = 0; + _internalSkippedOccluded = 0; + _leavesSkippedOccluded = 0; + + _colorSent = 0; + _internalColorSent = 0; + _leavesColorSent = 0; + + _didntFit = 0; + _internalDidntFit = 0; + _leavesDidntFit = 0; + + _colorBitsWritten = 0; + _existsBitsWritten = 0; + _existsInPacketBitsWritten = 0; + _treesRemoved = 0; + + if (_jurisdictionRoot) { + delete[] _jurisdictionRoot; + _jurisdictionRoot = NULL; + } + for (int i=0; i < _jurisdictionEndNodes.size(); i++) { + if (_jurisdictionEndNodes[i]) { + delete[] _jurisdictionEndNodes[i]; + } + } + _jurisdictionEndNodes.clear(); +} + +void OctreeSceneStats::packetSent(int bytes) { + _packets++; + _bytes += bytes; +} + +void OctreeSceneStats::traversed(const OctreeElement* element) { + _traversed++; + if (element->isLeaf()) { + _leaves++; + } else { + _internal++; + } +} + +void OctreeSceneStats::skippedDistance(const OctreeElement* element) { + _skippedDistance++; + if (element->isLeaf()) { + _leavesSkippedDistance++; + } else { + _internalSkippedDistance++; + } +} + +void OctreeSceneStats::skippedOutOfView(const OctreeElement* element) { + _skippedOutOfView++; + if (element->isLeaf()) { + _leavesSkippedOutOfView++; + } else { + _internalSkippedOutOfView++; + } +} + +void OctreeSceneStats::skippedWasInView(const OctreeElement* element) { + _skippedWasInView++; + if (element->isLeaf()) { + _leavesSkippedWasInView++; + } else { + _internalSkippedWasInView++; + } +} + +void OctreeSceneStats::skippedNoChange(const OctreeElement* element) { + _skippedNoChange++; + if (element->isLeaf()) { + _leavesSkippedNoChange++; + } else { + _internalSkippedNoChange++; + } +} + +void OctreeSceneStats::skippedOccluded(const OctreeElement* element) { + _skippedOccluded++; + if (element->isLeaf()) { + _leavesSkippedOccluded++; + } else { + _internalSkippedOccluded++; + } +} + +void OctreeSceneStats::colorSent(const OctreeElement* element) { + _colorSent++; + if (element->isLeaf()) { + _leavesColorSent++; + } else { + _internalColorSent++; + } +} + +void OctreeSceneStats::didntFit(const OctreeElement* element) { + _didntFit++; + if (element->isLeaf()) { + _leavesDidntFit++; + } else { + _internalDidntFit++; + } +} + +void OctreeSceneStats::colorBitsWritten() { + _colorBitsWritten++; +} + +void OctreeSceneStats::existsBitsWritten() { + _existsBitsWritten++; +} + +void OctreeSceneStats::existsInPacketBitsWritten() { + _existsInPacketBitsWritten++; +} + +void OctreeSceneStats::childBitsRemoved(bool includesExistsBits, bool includesColors) { + _existsInPacketBitsWritten--; + if (includesExistsBits) { + _existsBitsWritten--; + } + if (includesColors) { + _colorBitsWritten--; + } + _treesRemoved++; +} + +int OctreeSceneStats::packIntoMessage(unsigned char* destinationBuffer, int availableBytes) { + unsigned char* bufferStart = destinationBuffer; + + int headerLength = populateTypeAndVersion(destinationBuffer, PACKET_TYPE_VOXEL_STATS); + destinationBuffer += headerLength; + + memcpy(destinationBuffer, &_start, sizeof(_start)); + destinationBuffer += sizeof(_start); + memcpy(destinationBuffer, &_end, sizeof(_end)); + destinationBuffer += sizeof(_end); + memcpy(destinationBuffer, &_elapsed, sizeof(_elapsed)); + destinationBuffer += sizeof(_elapsed); + memcpy(destinationBuffer, &_totalEncodeTime, sizeof(_totalEncodeTime)); + destinationBuffer += sizeof(_totalEncodeTime); + memcpy(destinationBuffer, &_isFullScene, sizeof(_isFullScene)); + destinationBuffer += sizeof(_isFullScene); + memcpy(destinationBuffer, &_isMoving, sizeof(_isMoving)); + destinationBuffer += sizeof(_isMoving); + memcpy(destinationBuffer, &_packets, sizeof(_packets)); + destinationBuffer += sizeof(_packets); + memcpy(destinationBuffer, &_bytes, sizeof(_bytes)); + destinationBuffer += sizeof(_bytes); + + memcpy(destinationBuffer, &_totalInternal, sizeof(_totalInternal)); + destinationBuffer += sizeof(_totalInternal); + memcpy(destinationBuffer, &_totalLeaves, sizeof(_totalLeaves)); + destinationBuffer += sizeof(_totalLeaves); + memcpy(destinationBuffer, &_internal, sizeof(_internal)); + destinationBuffer += sizeof(_internal); + memcpy(destinationBuffer, &_leaves, sizeof(_leaves)); + destinationBuffer += sizeof(_leaves); + memcpy(destinationBuffer, &_internalSkippedDistance, sizeof(_internalSkippedDistance)); + destinationBuffer += sizeof(_internalSkippedDistance); + memcpy(destinationBuffer, &_leavesSkippedDistance, sizeof(_leavesSkippedDistance)); + destinationBuffer += sizeof(_leavesSkippedDistance); + memcpy(destinationBuffer, &_internalSkippedOutOfView, sizeof(_internalSkippedOutOfView)); + destinationBuffer += sizeof(_internalSkippedOutOfView); + memcpy(destinationBuffer, &_leavesSkippedOutOfView, sizeof(_leavesSkippedOutOfView)); + destinationBuffer += sizeof(_leavesSkippedOutOfView); + memcpy(destinationBuffer, &_internalSkippedWasInView, sizeof(_internalSkippedWasInView)); + destinationBuffer += sizeof(_internalSkippedWasInView); + memcpy(destinationBuffer, &_leavesSkippedWasInView, sizeof(_leavesSkippedWasInView)); + destinationBuffer += sizeof(_leavesSkippedWasInView); + memcpy(destinationBuffer, &_internalSkippedNoChange, sizeof(_internalSkippedNoChange)); + destinationBuffer += sizeof(_internalSkippedNoChange); + memcpy(destinationBuffer, &_leavesSkippedNoChange, sizeof(_leavesSkippedNoChange)); + destinationBuffer += sizeof(_leavesSkippedNoChange); + memcpy(destinationBuffer, &_internalSkippedOccluded, sizeof(_internalSkippedOccluded)); + destinationBuffer += sizeof(_internalSkippedOccluded); + memcpy(destinationBuffer, &_leavesSkippedOccluded, sizeof(_leavesSkippedOccluded)); + destinationBuffer += sizeof(_leavesSkippedOccluded); + memcpy(destinationBuffer, &_internalColorSent, sizeof(_internalColorSent)); + destinationBuffer += sizeof(_internalColorSent); + memcpy(destinationBuffer, &_leavesColorSent, sizeof(_leavesColorSent)); + destinationBuffer += sizeof(_leavesColorSent); + memcpy(destinationBuffer, &_internalDidntFit, sizeof(_internalDidntFit)); + destinationBuffer += sizeof(_internalDidntFit); + memcpy(destinationBuffer, &_leavesDidntFit, sizeof(_leavesDidntFit)); + destinationBuffer += sizeof(_leavesDidntFit); + memcpy(destinationBuffer, &_colorBitsWritten, sizeof(_colorBitsWritten)); + destinationBuffer += sizeof(_colorBitsWritten); + memcpy(destinationBuffer, &_existsBitsWritten, sizeof(_existsBitsWritten)); + destinationBuffer += sizeof(_existsBitsWritten); + memcpy(destinationBuffer, &_existsInPacketBitsWritten, sizeof(_existsInPacketBitsWritten)); + destinationBuffer += sizeof(_existsInPacketBitsWritten); + memcpy(destinationBuffer, &_treesRemoved, sizeof(_treesRemoved)); + destinationBuffer += sizeof(_treesRemoved); + + // add the root jurisdiction + if (_jurisdictionRoot) { + // copy the + int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_jurisdictionRoot)); + memcpy(destinationBuffer, &bytes, sizeof(bytes)); + destinationBuffer += sizeof(bytes); + memcpy(destinationBuffer, _jurisdictionRoot, bytes); + destinationBuffer += bytes; + + // if and only if there's a root jurisdiction, also include the end elements + int endNodeCount = _jurisdictionEndNodes.size(); + + memcpy(destinationBuffer, &endNodeCount, sizeof(endNodeCount)); + destinationBuffer += sizeof(endNodeCount); + + for (int i=0; i < endNodeCount; i++) { + unsigned char* endNodeCode = _jurisdictionEndNodes[i]; + int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); + memcpy(destinationBuffer, &bytes, sizeof(bytes)); + destinationBuffer += sizeof(bytes); + memcpy(destinationBuffer, endNodeCode, bytes); + destinationBuffer += bytes; + } + } else { + int bytes = 0; + memcpy(destinationBuffer, &bytes, sizeof(bytes)); + destinationBuffer += sizeof(bytes); + } + + return destinationBuffer - bufferStart; // includes header! +} + +int OctreeSceneStats::unpackFromMessage(unsigned char* sourceBuffer, int availableBytes) { + unsigned char* startPosition = sourceBuffer; + + // increment to push past the packet header + int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer); + sourceBuffer += numBytesPacketHeader; + + memcpy(&_start, sourceBuffer, sizeof(_start)); + sourceBuffer += sizeof(_start); + memcpy(&_end, sourceBuffer, sizeof(_end)); + sourceBuffer += sizeof(_end); + memcpy(&_elapsed, sourceBuffer, sizeof(_elapsed)); + sourceBuffer += sizeof(_elapsed); + memcpy(&_totalEncodeTime, sourceBuffer, sizeof(_totalEncodeTime)); + sourceBuffer += sizeof(_totalEncodeTime); + + memcpy(&_isFullScene, sourceBuffer, sizeof(_isFullScene)); + sourceBuffer += sizeof(_isFullScene); + + if (_isFullScene) { + _lastFullElapsed = _elapsed; + _lastFullTotalEncodeTime = _totalEncodeTime; + } + + memcpy(&_isMoving, sourceBuffer, sizeof(_isMoving)); + sourceBuffer += sizeof(_isMoving); + memcpy(&_packets, sourceBuffer, sizeof(_packets)); + sourceBuffer += sizeof(_packets); + memcpy(&_bytes, sourceBuffer, sizeof(_bytes)); + sourceBuffer += sizeof(_bytes); + + memcpy(&_totalInternal, sourceBuffer, sizeof(_totalInternal)); + sourceBuffer += sizeof(_totalInternal); + memcpy(&_totalLeaves, sourceBuffer, sizeof(_totalLeaves)); + sourceBuffer += sizeof(_totalLeaves); + _totalElements = _totalInternal + _totalLeaves; + + memcpy(&_internal, sourceBuffer, sizeof(_internal)); + sourceBuffer += sizeof(_internal); + memcpy(&_leaves, sourceBuffer, sizeof(_leaves)); + sourceBuffer += sizeof(_leaves); + _traversed = _internal + _leaves; + + memcpy(&_internalSkippedDistance, sourceBuffer, sizeof(_internalSkippedDistance)); + sourceBuffer += sizeof(_internalSkippedDistance); + memcpy(&_leavesSkippedDistance, sourceBuffer, sizeof(_leavesSkippedDistance)); + sourceBuffer += sizeof(_leavesSkippedDistance); + _skippedDistance = _internalSkippedDistance + _leavesSkippedDistance; + + memcpy(&_internalSkippedOutOfView, sourceBuffer, sizeof(_internalSkippedOutOfView)); + sourceBuffer += sizeof(_internalSkippedOutOfView); + memcpy(&_leavesSkippedOutOfView, sourceBuffer, sizeof(_leavesSkippedOutOfView)); + sourceBuffer += sizeof(_leavesSkippedOutOfView); + _skippedOutOfView = _internalSkippedOutOfView + _leavesSkippedOutOfView; + + memcpy(&_internalSkippedWasInView, sourceBuffer, sizeof(_internalSkippedWasInView)); + sourceBuffer += sizeof(_internalSkippedWasInView); + memcpy(&_leavesSkippedWasInView, sourceBuffer, sizeof(_leavesSkippedWasInView)); + sourceBuffer += sizeof(_leavesSkippedWasInView); + _skippedWasInView = _internalSkippedWasInView + _leavesSkippedWasInView; + + memcpy(&_internalSkippedNoChange, sourceBuffer, sizeof(_internalSkippedNoChange)); + sourceBuffer += sizeof(_internalSkippedNoChange); + memcpy(&_leavesSkippedNoChange, sourceBuffer, sizeof(_leavesSkippedNoChange)); + sourceBuffer += sizeof(_leavesSkippedNoChange); + _skippedNoChange = _internalSkippedNoChange + _leavesSkippedNoChange; + + memcpy(&_internalSkippedOccluded, sourceBuffer, sizeof(_internalSkippedOccluded)); + sourceBuffer += sizeof(_internalSkippedOccluded); + memcpy(&_leavesSkippedOccluded, sourceBuffer, sizeof(_leavesSkippedOccluded)); + sourceBuffer += sizeof(_leavesSkippedOccluded); + _skippedOccluded = _internalSkippedOccluded + _leavesSkippedOccluded; + + memcpy(&_internalColorSent, sourceBuffer, sizeof(_internalColorSent)); + sourceBuffer += sizeof(_internalColorSent); + memcpy(&_leavesColorSent, sourceBuffer, sizeof(_leavesColorSent)); + sourceBuffer += sizeof(_leavesColorSent); + _colorSent = _internalColorSent + _leavesColorSent; + + memcpy(&_internalDidntFit, sourceBuffer, sizeof(_internalDidntFit)); + sourceBuffer += sizeof(_internalDidntFit); + memcpy(&_leavesDidntFit, sourceBuffer, sizeof(_leavesDidntFit)); + sourceBuffer += sizeof(_leavesDidntFit); + _didntFit = _internalDidntFit + _leavesDidntFit; + + memcpy(&_colorBitsWritten, sourceBuffer, sizeof(_colorBitsWritten)); + sourceBuffer += sizeof(_colorBitsWritten); + memcpy(&_existsBitsWritten, sourceBuffer, sizeof(_existsBitsWritten)); + sourceBuffer += sizeof(_existsBitsWritten); + memcpy(&_existsInPacketBitsWritten, sourceBuffer, sizeof(_existsInPacketBitsWritten)); + sourceBuffer += sizeof(_existsInPacketBitsWritten); + memcpy(&_treesRemoved, sourceBuffer, sizeof(_treesRemoved)); + sourceBuffer += sizeof(_treesRemoved); + + // before allocating new juridiction, clean up existing ones + if (_jurisdictionRoot) { + delete[] _jurisdictionRoot; + _jurisdictionRoot = NULL; + } + + // clear existing endNodes before copying new ones... + for (int i=0; i < _jurisdictionEndNodes.size(); i++) { + if (_jurisdictionEndNodes[i]) { + delete[] _jurisdictionEndNodes[i]; + } + } + _jurisdictionEndNodes.clear(); + + // read the root jurisdiction + int bytes = 0; + memcpy(&bytes, sourceBuffer, sizeof(bytes)); + sourceBuffer += sizeof(bytes); + + if (bytes == 0) { + _jurisdictionRoot = NULL; + _jurisdictionEndNodes.clear(); + } else { + _jurisdictionRoot = new unsigned char[bytes]; + memcpy(_jurisdictionRoot, sourceBuffer, bytes); + sourceBuffer += bytes; + // if and only if there's a root jurisdiction, also include the end elements + _jurisdictionEndNodes.clear(); + int endNodeCount = 0; + memcpy(&endNodeCount, sourceBuffer, sizeof(endNodeCount)); + sourceBuffer += sizeof(endNodeCount); + for (int i=0; i < endNodeCount; i++) { + int bytes = 0; + memcpy(&bytes, sourceBuffer, sizeof(bytes)); + sourceBuffer += sizeof(bytes); + unsigned char* endNodeCode = new unsigned char[bytes]; + memcpy(endNodeCode, sourceBuffer, bytes); + sourceBuffer += bytes; + _jurisdictionEndNodes.push_back(endNodeCode); + } + } + + // running averages + _elapsedAverage.updateAverage((float)_elapsed); + unsigned long total = _existsInPacketBitsWritten + _colorSent; + float calculatedBPV = total == 0 ? 0 : (_bytes * 8) / total; + _bitsPerOctreeAverage.updateAverage(calculatedBPV); + + + return sourceBuffer - startPosition; // includes header! +} + + +void OctreeSceneStats::printDebugDetails() { + qDebug("\n------------------------------\n"); + qDebug("OctreeSceneStats:\n"); + qDebug(" start : %llu \n", (long long unsigned int)_start); + qDebug(" end : %llu \n", (long long unsigned int)_end); + qDebug(" elapsed : %llu \n", (long long unsigned int)_elapsed); + qDebug(" encoding : %llu \n", (long long unsigned int)_totalEncodeTime); + qDebug("\n"); + qDebug(" full scene: %s\n", debug::valueOf(_isFullScene)); + qDebug(" moving: %s\n", debug::valueOf(_isMoving)); + qDebug("\n"); + qDebug(" packets: %d\n", _packets); + qDebug(" bytes : %ld\n", _bytes); + qDebug("\n"); + qDebug(" total elements : %lu\n", _totalElements ); + qDebug(" internal : %lu\n", _totalInternal ); + qDebug(" leaves : %lu\n", _totalLeaves ); + qDebug(" traversed : %lu\n", _traversed ); + qDebug(" internal : %lu\n", _internal ); + qDebug(" leaves : %lu\n", _leaves ); + qDebug(" skipped distance : %lu\n", _skippedDistance ); + qDebug(" internal : %lu\n", _internalSkippedDistance ); + qDebug(" leaves : %lu\n", _leavesSkippedDistance ); + qDebug(" skipped out of view : %lu\n", _skippedOutOfView ); + qDebug(" internal : %lu\n", _internalSkippedOutOfView ); + qDebug(" leaves : %lu\n", _leavesSkippedOutOfView ); + qDebug(" skipped was in view : %lu\n", _skippedWasInView ); + qDebug(" internal : %lu\n", _internalSkippedWasInView ); + qDebug(" leaves : %lu\n", _leavesSkippedWasInView ); + qDebug(" skipped no change : %lu\n", _skippedNoChange ); + qDebug(" internal : %lu\n", _internalSkippedNoChange ); + qDebug(" leaves : %lu\n", _leavesSkippedNoChange ); + qDebug(" skipped occluded : %lu\n", _skippedOccluded ); + qDebug(" internal : %lu\n", _internalSkippedOccluded ); + qDebug(" leaves : %lu\n", _leavesSkippedOccluded ); + + qDebug("\n"); + qDebug(" color sent : %lu\n", _colorSent ); + qDebug(" internal : %lu\n", _internalColorSent ); + qDebug(" leaves : %lu\n", _leavesColorSent ); + qDebug(" Didn't Fit : %lu\n", _didntFit ); + qDebug(" internal : %lu\n", _internalDidntFit ); + qDebug(" leaves : %lu\n", _leavesDidntFit ); + qDebug(" color bits : %lu\n", _colorBitsWritten ); + qDebug(" exists bits : %lu\n", _existsBitsWritten ); + qDebug(" in packet bit : %lu\n", _existsInPacketBitsWritten); + qDebug(" trees removed : %lu\n", _treesRemoved ); +} + +OctreeSceneStats::ItemInfo OctreeSceneStats::_ITEMS[] = { + { "Elapsed" , GREENISH , 2 , "Elapsed,fps" }, + { "Encode" , YELLOWISH , 2 , "Time,fps" }, + { "Network" , GREYISH , 3 , "Packets,Bytes,KBPS" }, + { "Octrees on Server" , GREENISH , 3 , "Total,Internal,Leaves" }, + { "Octrees Sent" , YELLOWISH , 5 , "Total,Bits/Octree,Avg Bits/Octree,Internal,Leaves" }, + { "Colors Sent" , GREYISH , 3 , "Total,Internal,Leaves" }, + { "Bitmasks Sent" , GREENISH , 3 , "Colors,Exists,In Packets" }, + { "Traversed" , YELLOWISH , 3 , "Total,Internal,Leaves" }, + { "Skipped - Total" , GREYISH , 3 , "Total,Internal,Leaves" }, + { "Skipped - Distance" , GREENISH , 3 , "Total,Internal,Leaves" }, + { "Skipped - Out of View", YELLOWISH , 3 , "Total,Internal,Leaves" }, + { "Skipped - Was in View", GREYISH , 3 , "Total,Internal,Leaves" }, + { "Skipped - No Change" , GREENISH , 3 , "Total,Internal,Leaves" }, + { "Skipped - Occluded" , YELLOWISH , 3 , "Total,Internal,Leaves" }, + { "Didn't fit in packet" , GREYISH , 4 , "Total,Internal,Leaves,Removed" }, + { "Mode" , GREENISH , 4 , "Moving,Stationary,Partial,Full" }, +}; + +const char* OctreeSceneStats::getItemValue(Item item) { + const uint64_t USECS_PER_SECOND = 1000 * 1000; + int calcFPS, calcAverageFPS, calculatedKBPS; + switch(item) { + case ITEM_ELAPSED: { + calcFPS = (float)USECS_PER_SECOND / (float)_elapsed; + float elapsedAverage = _elapsedAverage.getAverage(); + calcAverageFPS = (float)USECS_PER_SECOND / (float)elapsedAverage; + + sprintf(_itemValueBuffer, "%llu usecs (%d fps) Average: %.0f usecs (%d fps)", + (long long unsigned int)_elapsed, calcFPS, elapsedAverage, calcAverageFPS); + break; + } + case ITEM_ENCODE: + calcFPS = (float)USECS_PER_SECOND / (float)_totalEncodeTime; + sprintf(_itemValueBuffer, "%llu usecs (%d fps)", (long long unsigned int)_totalEncodeTime, calcFPS); + break; + case ITEM_PACKETS: { + float elapsedSecs = ((float)_elapsed / (float)USECS_PER_SECOND); + calculatedKBPS = elapsedSecs == 0 ? 0 : ((_bytes * 8) / elapsedSecs) / 1000; + sprintf(_itemValueBuffer, "%d packets %lu bytes (%d kbps)", _packets, _bytes, calculatedKBPS); + break; + } + case ITEM_VOXELS_SERVER: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _totalElements, _totalInternal, _totalLeaves); + break; + } + case ITEM_VOXELS: { + unsigned long total = _existsInPacketBitsWritten + _colorSent; + float calculatedBPV = total == 0 ? 0 : (_bytes * 8) / total; + float averageBPV = _bitsPerOctreeAverage.getAverage(); + sprintf(_itemValueBuffer, "%lu (%.2f bits/octree Average: %.2f bits/octree) %lu internal %lu leaves", + total, calculatedBPV, averageBPV, _existsInPacketBitsWritten, _colorSent); + break; + } + case ITEM_TRAVERSED: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _traversed, _internal, _leaves); + break; + } + case ITEM_SKIPPED: { + unsigned long total = _skippedDistance + _skippedOutOfView + + _skippedWasInView + _skippedNoChange + _skippedOccluded; + + unsigned long internal = _internalSkippedDistance + _internalSkippedOutOfView + + _internalSkippedWasInView + _internalSkippedNoChange + _internalSkippedOccluded; + + unsigned long leaves = _leavesSkippedDistance + _leavesSkippedOutOfView + + _leavesSkippedWasInView + _leavesSkippedNoChange + _leavesSkippedOccluded; + + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + total, internal, leaves); + break; + } + case ITEM_SKIPPED_DISTANCE: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _skippedDistance, _internalSkippedDistance, _leavesSkippedDistance); + break; + } + case ITEM_SKIPPED_OUT_OF_VIEW: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _skippedOutOfView, _internalSkippedOutOfView, _leavesSkippedOutOfView); + break; + } + case ITEM_SKIPPED_WAS_IN_VIEW: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _skippedWasInView, _internalSkippedWasInView, _leavesSkippedWasInView); + break; + } + case ITEM_SKIPPED_NO_CHANGE: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _skippedNoChange, _internalSkippedNoChange, _leavesSkippedNoChange); + break; + } + case ITEM_SKIPPED_OCCLUDED: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _skippedOccluded, _internalSkippedOccluded, _leavesSkippedOccluded); + break; + } + case ITEM_COLORS: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _colorSent, _internalColorSent, _leavesColorSent); + break; + } + case ITEM_DIDNT_FIT: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves (removed: %lu)", + _didntFit, _internalDidntFit, _leavesDidntFit, _treesRemoved); + break; + } + case ITEM_BITS: { + sprintf(_itemValueBuffer, "colors: %lu, exists: %lu, in packets: %lu", + _colorBitsWritten, _existsBitsWritten, _existsInPacketBitsWritten); + break; + } + case ITEM_MODE: { + sprintf(_itemValueBuffer, "%s - %s", (_isFullScene ? "Full Scene" : "Partial Scene"), + (_isMoving ? "Moving" : "Stationary")); + break; + } + default: + sprintf(_itemValueBuffer, ""); + break; + } + return _itemValueBuffer; +} + +void OctreeSceneStats::trackIncomingOctreePacket(unsigned char* messageData, ssize_t messageLength, bool wasStatsPacket) { + _incomingPacket++; + _incomingBytes += messageLength; + if (!wasStatsPacket) { + _incomingWastedBytes += (MAX_PACKET_SIZE - messageLength); + } + + int numBytesPacketHeader = numBytesForPacketHeader(messageData); + unsigned char* dataAt = messageData + numBytesPacketHeader; + + //VOXEL_PACKET_FLAGS flags = (*(VOXEL_PACKET_FLAGS*)(dataAt)); + dataAt += sizeof(OCTREE_PACKET_FLAGS); + OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt); + dataAt += sizeof(OCTREE_PACKET_SEQUENCE); + + OCTREE_PACKET_SENT_TIME sentAt = (*(OCTREE_PACKET_SENT_TIME*)dataAt); + dataAt += sizeof(OCTREE_PACKET_SENT_TIME); + + //bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT); + //bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT); + + OCTREE_PACKET_SENT_TIME arrivedAt = usecTimestampNow(); + int flightTime = arrivedAt - sentAt; + const int USECS_PER_MSEC = 1000; + float flightTimeMsecs = flightTime / USECS_PER_MSEC; + _incomingFlightTimeAverage.updateAverage(flightTimeMsecs); + + + // detect out of order packets + if (sequence < _incomingLastSequence) { + _incomingOutOfOrder++; + } + + // detect likely lost packets + OCTREE_PACKET_SEQUENCE expected = _incomingLastSequence+1; + if (sequence > expected) { + _incomingLikelyLost++; + } + + _incomingLastSequence = sequence; +} + diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h new file mode 100644 index 0000000000..0d3490a31d --- /dev/null +++ b/libraries/octree/src/OctreeSceneStats.h @@ -0,0 +1,276 @@ +// +// OctreeSceneStats.h +// hifi +// +// Created by Brad Hefta-Gaub on 7/18/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// + +#ifndef __hifi__OctreeSceneStats__ +#define __hifi__OctreeSceneStats__ + +#include +#include +#include "JurisdictionMap.h" + +#define GREENISH 0x40ff40d0 +#define YELLOWISH 0xffef40c0 +#define GREYISH 0xd0d0d0a0 + +class OctreeElement; + +/// Collects statistics for calculating and sending a scene from a octree server to an interface client +class OctreeSceneStats { +public: + OctreeSceneStats(); + ~OctreeSceneStats(); + void reset(); + + OctreeSceneStats(const OctreeSceneStats& other); // copy constructor + OctreeSceneStats& operator= (const OctreeSceneStats& other); // copy assignment + + /// Call when beginning the computation of a scene. Initializes internal structures + void sceneStarted(bool fullScene, bool moving, OctreeElement* root, JurisdictionMap* jurisdictionMap); + bool getIsSceneStarted() const { return _isStarted; } + + /// Call when the computation of a scene is completed. Finalizes internal structures + void sceneCompleted(); + + void printDebugDetails(); + + /// Track that a packet was sent as part of the scene. + void packetSent(int bytes); + + /// Tracks the beginning of an encode pass during scene calculation. + void encodeStarted(); + + /// Tracks the ending of an encode pass during scene calculation. + void encodeStopped(); + + /// Track that a element was traversed as part of computation of a scene. + void traversed(const OctreeElement* element); + + /// Track that a element was skipped as part of computation of a scene due to being beyond the LOD distance. + void skippedDistance(const OctreeElement* element); + + /// Track that a element was skipped as part of computation of a scene due to being out of view. + void skippedOutOfView(const OctreeElement* element); + + /// Track that a element was skipped as part of computation of a scene due to previously being in view while in delta sending + void skippedWasInView(const OctreeElement* element); + + /// Track that a element was skipped as part of computation of a scene due to not having changed since last full scene sent + void skippedNoChange(const OctreeElement* element); + + /// Track that a element was skipped as part of computation of a scene due to being occluded + void skippedOccluded(const OctreeElement* element); + + /// Track that a element's color was was sent as part of computation of a scene + void colorSent(const OctreeElement* element); + + /// Track that a element was due to be sent, but didn't fit in the packet and was moved to next packet + void didntFit(const OctreeElement* element); + + /// Track that the color bitmask was was sent as part of computation of a scene + void colorBitsWritten(); + + /// Track that the exists in tree bitmask was was sent as part of computation of a scene + void existsBitsWritten(); + + /// Track that the exists in packet bitmask was was sent as part of computation of a scene + void existsInPacketBitsWritten(); + + /// Fix up tracking statistics in case where bitmasks were removed for some reason + void childBitsRemoved(bool includesExistsBits, bool includesColors); + + /// Pack the details of the statistics into a buffer for sending as a network packet + int packIntoMessage(unsigned char* destinationBuffer, int availableBytes); + + /// Unpack the details of the statistics from a buffer typically received as a network packet + int unpackFromMessage(unsigned char* sourceBuffer, int availableBytes); + + /// Indicates that a scene has been completed and the statistics are ready to be sent + bool isReadyToSend() const { return _isReadyToSend; } + + /// Mark that the scene statistics have been sent + void markAsSent() { _isReadyToSend = false; } + + unsigned char* getStatsMessage() { return &_statsMessage[0]; } + int getStatsMessageLength() const { return _statsMessageLength; } + + /// List of various items tracked by OctreeSceneStats which can be accessed via getItemInfo() and getItemValue() + enum Item { + ITEM_ELAPSED, + ITEM_ENCODE, + ITEM_PACKETS, + ITEM_VOXELS_SERVER, + ITEM_VOXELS, + ITEM_COLORS, + ITEM_BITS, + ITEM_TRAVERSED, + ITEM_SKIPPED, + ITEM_SKIPPED_DISTANCE, + ITEM_SKIPPED_OUT_OF_VIEW, + ITEM_SKIPPED_WAS_IN_VIEW, + ITEM_SKIPPED_NO_CHANGE, + ITEM_SKIPPED_OCCLUDED, + ITEM_DIDNT_FIT, + ITEM_MODE, + ITEM_COUNT + }; + + /// Meta information about each stats item + struct ItemInfo { + char const* const caption; + unsigned colorRGBA; + int detailsCount; + const char* detailsLabels; + }; + + /// Returns details about items tracked by OctreeSceneStats + /// \param Item item The item from the stats you're interested in. + ItemInfo& getItemInfo(Item item) { return _ITEMS[item]; } + + /// Returns a UI formatted value of an item tracked by OctreeSceneStats + /// \param Item item The item from the stats you're interested in. + const char* getItemValue(Item item); + + /// Returns OctCode for root element of the jurisdiction of this particular octree server + unsigned char* getJurisdictionRoot() const { return _jurisdictionRoot; } + + /// Returns list of OctCodes for end elements of the jurisdiction of this particular octree server + const std::vector& getJurisdictionEndNodes() const { return _jurisdictionEndNodes; } + + bool isMoving() const { return _isMoving; }; + unsigned long getTotalElements() const { return _totalElements; } + unsigned long getTotalInternal() const { return _totalInternal; } + unsigned long getTotalLeaves() const { return _totalLeaves; } + unsigned long getTotalEncodeTime() const { return _totalEncodeTime; } + unsigned long getElapsedTime() const { return _elapsed; } + + unsigned long getLastFullTotalEncodeTime() const { return _lastFullTotalEncodeTime; } + unsigned long getLastFullElapsedTime() const { return _lastFullElapsed; } + + // Used in client implementations to track individual octree packets + void trackIncomingOctreePacket(unsigned char* messageData, ssize_t messageLength, bool wasStatsPacket); + + unsigned int getIncomingPackets() const { return _incomingPacket; } + unsigned long getIncomingBytes() const { return _incomingBytes; } + unsigned long getIncomingWastedBytes() const { return _incomingWastedBytes; } + unsigned int getIncomingOutOfOrder() const { return _incomingOutOfOrder; } + unsigned int getIncomingLikelyLost() const { return _incomingLikelyLost; } + float getIncomingFlightTimeAverage() { return _incomingFlightTimeAverage.getAverage(); } + +private: + + void copyFromOther(const OctreeSceneStats& other); + + bool _isReadyToSend; + unsigned char _statsMessage[MAX_PACKET_SIZE]; + int _statsMessageLength; + + // scene timing data in usecs + bool _isStarted; + uint64_t _start; + uint64_t _end; + uint64_t _elapsed; + uint64_t _lastFullElapsed; + + SimpleMovingAverage _elapsedAverage; + SimpleMovingAverage _bitsPerOctreeAverage; + + uint64_t _totalEncodeTime; + uint64_t _lastFullTotalEncodeTime; + uint64_t _encodeStart; + + // scene octree related data + unsigned long _totalElements; + unsigned long _totalInternal; + unsigned long _totalLeaves; + + unsigned long _traversed; + unsigned long _internal; + unsigned long _leaves; + + unsigned long _skippedDistance; + unsigned long _internalSkippedDistance; + unsigned long _leavesSkippedDistance; + + unsigned long _skippedOutOfView; + unsigned long _internalSkippedOutOfView; + unsigned long _leavesSkippedOutOfView; + + unsigned long _skippedWasInView; + unsigned long _internalSkippedWasInView; + unsigned long _leavesSkippedWasInView; + + unsigned long _skippedNoChange; + unsigned long _internalSkippedNoChange; + unsigned long _leavesSkippedNoChange; + + unsigned long _skippedOccluded; + unsigned long _internalSkippedOccluded; + unsigned long _leavesSkippedOccluded; + + unsigned long _colorSent; + unsigned long _internalColorSent; + unsigned long _leavesColorSent; + + unsigned long _didntFit; + unsigned long _internalDidntFit; + unsigned long _leavesDidntFit; + + unsigned long _colorBitsWritten; + unsigned long _existsBitsWritten; + unsigned long _existsInPacketBitsWritten; + unsigned long _treesRemoved; + + // Accounting Notes: + // + // 1) number of octrees sent can be calculated as _colorSent + _colorBitsWritten. This works because each internal + // element in a packet will have a _colorBitsWritten included for it and each "leaf" in the packet will have a + // _colorSent written for it. Note that these "leaf" elements in the packets may not be actual leaves in the full + // tree, because LOD may cause us to send an average color for an internal element instead of recursing deeper to + // the leaves. + // + // 2) the stats balance if: (working assumption) + // if _colorSent > 0 + // _traversed = all skipped + _colorSent + _colorBitsWritten + // else + // _traversed = all skipped + _colorSent + _colorBitsWritten + _treesRemoved + // + + // scene network related data + unsigned int _packets; + unsigned long _bytes; + unsigned int _passes; + + // incoming packets stats + unsigned int _incomingPacket; + unsigned long _incomingBytes; + unsigned long _incomingWastedBytes; + unsigned int _incomingLastSequence; + unsigned int _incomingOutOfOrder; + unsigned int _incomingLikelyLost; + SimpleMovingAverage _incomingFlightTimeAverage; + + // features related items + bool _isMoving; + bool _isFullScene; + + + static ItemInfo _ITEMS[]; + static int const MAX_ITEM_VALUE_LENGTH = 128; + char _itemValueBuffer[MAX_ITEM_VALUE_LENGTH]; + + unsigned char* _jurisdictionRoot; + std::vector _jurisdictionEndNodes; +}; + +/// Map between element IDs and their reported OctreeSceneStats. Typically used by classes that need to know which elements sent +/// which octree stats +typedef std::map NodeToOctreeSceneStats; +typedef std::map::iterator NodeToOctreeSceneStatsIterator; + +#endif /* defined(__hifi__OctreeSceneStats__) */ diff --git a/libraries/voxels/src/Plane.cpp b/libraries/octree/src/Plane.cpp similarity index 100% rename from libraries/voxels/src/Plane.cpp rename to libraries/octree/src/Plane.cpp diff --git a/libraries/voxels/src/Plane.h b/libraries/octree/src/Plane.h similarity index 100% rename from libraries/voxels/src/Plane.h rename to libraries/octree/src/Plane.h diff --git a/libraries/voxels/src/ViewFrustum.cpp b/libraries/octree/src/ViewFrustum.cpp similarity index 99% rename from libraries/voxels/src/ViewFrustum.cpp rename to libraries/octree/src/ViewFrustum.cpp index 7c01a1ad03..b87b290231 100644 --- a/libraries/voxels/src/ViewFrustum.cpp +++ b/libraries/octree/src/ViewFrustum.cpp @@ -16,11 +16,11 @@ #include -#include "CoverageMap.h" +//#include "CoverageMap.h" #include "GeometryUtil.h" #include "SharedUtil.h" #include "ViewFrustum.h" -#include "VoxelConstants.h" +#include "OctreeConstants.h" using namespace std; @@ -630,7 +630,7 @@ const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_PROJECTED_POLYGON_VERT {6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // back, top, left }; -VoxelProjectedPolygon ViewFrustum::getProjectedPolygon(const AABox& box) const { +OctreeProjectedPolygon ViewFrustum::getProjectedPolygon(const AABox& box) const { const glm::vec3& bottomNearRight = box.getCorner(); glm::vec3 topFarLeft = box.calcTopFarLeft(); @@ -643,7 +643,7 @@ VoxelProjectedPolygon ViewFrustum::getProjectedPolygon(const AABox& box) const { int vertexCount = hullVertexLookup[lookUp][0]; //look up number of vertices - VoxelProjectedPolygon projectedPolygon(vertexCount); + OctreeProjectedPolygon projectedPolygon(vertexCount); bool pointInView = true; bool allPointsInView = false; // assume the best, but wait till we know we have a vertex diff --git a/libraries/voxels/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h similarity index 98% rename from libraries/voxels/src/ViewFrustum.h rename to libraries/octree/src/ViewFrustum.h index c5f2be159d..acbd740324 100644 --- a/libraries/voxels/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -17,7 +17,7 @@ #include "AABox.h" #include "Plane.h" -#include "VoxelProjectedPolygon.h" +#include "OctreeProjectedPolygon.h" const float DEFAULT_KEYHOLE_RADIUS = 3.0f; @@ -96,7 +96,7 @@ public: void printDebugDetails() const; glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const; - VoxelProjectedPolygon getProjectedPolygon(const AABox& box) const; + OctreeProjectedPolygon getProjectedPolygon(const AABox& box) const; glm::vec3 getFurthestPointFromCamera(const AABox& box) const; private: diff --git a/libraries/particle-server/CMakeLists.txt.disabled b/libraries/particle-server/CMakeLists.txt.disabled new file mode 100644 index 0000000000..9a1b7a7223 --- /dev/null +++ b/libraries/particle-server/CMakeLists.txt.disabled @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 2.8) + +set(ROOT_DIR ../..) +set(MACRO_DIR ${ROOT_DIR}/cmake/macros) + +# setup for find modules +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/") + +set(TARGET_NAME particle-server) + +find_package(Qt5Widgets REQUIRED) + +include(${MACRO_DIR}/SetupHifiLibrary.cmake) + +# grab cJSON and civetweb sources to pass as OPTIONAL_SRCS +FILE(GLOB OPTIONAL_SRCS ${ROOT_DIR}/externals/civetweb/src/*) + +setup_hifi_library(${TARGET_NAME} ${OPTIONAL_SRCS}) + +include_directories(${ROOT_DIR}/externals/civetweb/include) + +qt5_use_modules(${TARGET_NAME} Widgets) + +# inluce GLM +include(${MACRO_DIR}/IncludeGLM.cmake) +include_glm(${TARGET_NAME} ${ROOT_DIR}) + +# link in the shared library +include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) + +# link ZLIB +find_package(ZLIB) +include_directories(${ZLIB_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) + +# link in the hifi octree library +link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) + +# link in the hifi particles library +link_hifi_library(particles ${TARGET_NAME} ${ROOT_DIR}) + diff --git a/libraries/particle-server/src/ParticlePersistThread.cpp b/libraries/particle-server/src/ParticlePersistThread.cpp new file mode 100644 index 0000000000..09e6248de7 --- /dev/null +++ b/libraries/particle-server/src/ParticlePersistThread.cpp @@ -0,0 +1,86 @@ +// +// ParticlePersistThread.cpp +// particle-server +// +// Created by Brad Hefta-Gaub on 12/2/13 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// Threaded or non-threaded particle persistence +// + +#include +#include +#include +#include + +#include "ParticlePersistThread.h" +#include "ParticleServer.h" + +ParticlePersistThread::ParticlePersistThread(ParticleTree* tree, const char* filename, int persistInterval) : + _tree(tree), + _filename(filename), + _persistInterval(persistInterval), + _initialLoadComplete(false), + _loadTimeUSecs(0) { +} + +bool ParticlePersistThread::process() { + + if (!_initialLoadComplete) { + uint64_t loadStarted = usecTimestampNow(); + qDebug("loading particles from file: %s...\n", _filename); + + bool persistantFileRead; + + _tree->lockForWrite(); + { + PerformanceWarning warn(true, "Loading Particle File", true); + persistantFileRead = _tree->readFromSVOFile(_filename); + } + _tree->unlock(); + + _loadCompleted = time(0); + uint64_t loadDone = usecTimestampNow(); + _loadTimeUSecs = loadDone - loadStarted; + + _tree->clearDirtyBit(); // the tree is clean since we just loaded it + qDebug("DONE loading particles from file... fileRead=%s\n", debug::valueOf(persistantFileRead)); + + unsigned long nodeCount = ParticleNode::getNodeCount(); + unsigned long internalNodeCount = ParticleNode::getInternalNodeCount(); + unsigned long leafNodeCount = ParticleNode::getLeafNodeCount(); + qDebug("Nodes after loading scene %lu nodes %lu internal %lu leaves\n", nodeCount, internalNodeCount, leafNodeCount); + + double usecPerGet = (double)ParticleNode::getGetChildAtIndexTime() / (double)ParticleNode::getGetChildAtIndexCalls(); + qDebug("getChildAtIndexCalls=%llu getChildAtIndexTime=%llu perGet=%lf \n", + ParticleNode::getGetChildAtIndexTime(), ParticleNode::getGetChildAtIndexCalls(), usecPerGet); + + double usecPerSet = (double)ParticleNode::getSetChildAtIndexTime() / (double)ParticleNode::getSetChildAtIndexCalls(); + qDebug("setChildAtIndexCalls=%llu setChildAtIndexTime=%llu perSet=%lf\n", + ParticleNode::getSetChildAtIndexTime(), ParticleNode::getSetChildAtIndexCalls(), usecPerSet); + + _initialLoadComplete = true; + _lastCheck = usecTimestampNow(); // we just loaded, no need to save again + } + + if (isStillRunning()) { + uint64_t MSECS_TO_USECS = 1000; + uint64_t USECS_TO_SLEEP = 100 * MSECS_TO_USECS; // every 100ms + usleep(USECS_TO_SLEEP); + uint64_t now = usecTimestampNow(); + uint64_t sinceLastSave = now - _lastCheck; + uint64_t intervalToCheck = _persistInterval * MSECS_TO_USECS; + + if (sinceLastSave > intervalToCheck) { + // check the dirty bit and persist here... + _lastCheck = usecTimestampNow(); + if (_tree->isDirty()) { + qDebug("saving particles to file %s...\n",_filename); + _tree->writeToSVOFile(_filename); + _tree->clearDirtyBit(); // tree is clean after saving + qDebug("DONE saving particles to file...\n"); + } + } + } + return isStillRunning(); // keep running till they terminate us +} diff --git a/libraries/particle-server/src/ParticlePersistThread.h b/libraries/particle-server/src/ParticlePersistThread.h new file mode 100644 index 0000000000..0d1e12f56c --- /dev/null +++ b/libraries/particle-server/src/ParticlePersistThread.h @@ -0,0 +1,44 @@ +// +// ParticlePersistThread.h +// particle-server +// +// Created by Brad Hefta-Gaub on 12/2/13 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// Threaded or non-threaded particle persistence +// + +#ifndef __particle_server__ParticlePersistThread__ +#define __particle_server__ParticlePersistThread__ + +#include +#include +#include + +/// Generalized threaded processor for handling received inbound packets. +class ParticlePersistThread : public virtual GenericThread { +public: + static const int DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds + + ParticlePersistThread(ParticleTree* tree, const char* filename, int persistInterval = DEFAULT_PERSIST_INTERVAL); + + bool isInitialLoadComplete() const { return _initialLoadComplete; } + + time_t* getLoadCompleted() { return &_loadCompleted; } + uint64_t getLoadElapsedTime() const { return _loadTimeUSecs; } + +protected: + /// Implements generic processing behavior for this thread. + virtual bool process(); +private: + ParticleTree* _tree; + const char* _filename; + int _persistInterval; + bool _initialLoadComplete; + + time_t _loadCompleted; + uint64_t _loadTimeUSecs; + uint64_t _lastCheck; +}; + +#endif // __particle_server__ParticlePersistThread__ diff --git a/libraries/particle-server/src/ParticleReceiverData.cpp b/libraries/particle-server/src/ParticleReceiverData.cpp new file mode 100644 index 0000000000..0c0390c5f3 --- /dev/null +++ b/libraries/particle-server/src/ParticleReceiverData.cpp @@ -0,0 +1,276 @@ +// +// ParticleReceiverData.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 12/2/13 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include "PacketHeaders.h" +#include "SharedUtil.h" +#include "ParticleReceiverData.h" +#include +#include +#include "ParticleSendThread.h" + +ParticleReceiverData::ParticleReceiverData(Node* owningNode) : + ParticleQuery(owningNode), + _viewSent(false), + _particlePacketAvailableBytes(MAX_PACKET_SIZE), + _maxSearchLevel(1), + _maxLevelReachedInLastSearch(1), + _lastTimeBagEmpty(0), + _viewFrustumChanging(false), + _viewFrustumJustStoppedChanging(true), + _currentPacketIsColor(true), + _currentPacketIsCompressed(false), + _particleSendThread(NULL), + _lastClientBoundaryLevelAdjust(0), + _lastClientParticleSizeScale(DEFAULT_PARTICLE_SIZE_SCALE), + _lodChanged(false), + _lodInitialized(false) +{ + _particlePacket = new unsigned char[MAX_PACKET_SIZE]; + _particlePacketAt = _particlePacket; + _lastParticlePacket = new unsigned char[MAX_PACKET_SIZE]; + _lastParticlePacketLength = 0; + _duplicatePacketCount = 0; + _sequenceNumber = 0; + resetParticlePacket(true); // don't bump sequence +} + +void ParticleReceiverData::initializeParticleSendThread(ParticleServer* particleServer) { + // Create particle sending thread... + QUuid nodeUUID = getOwningNode()->getUUID(); + _particleSendThread = new ParticleSendThread(nodeUUID, particleServer); + _particleSendThread->initialize(true); +} + +bool ParticleReceiverData::packetIsDuplicate() const { + // since our packets now include header information, like sequence number, and createTime, we can't just do a memcmp + // of the entire packet, we need to compare only the packet content... + if (_lastParticlePacketLength == getPacketLength()) { + return memcmp(_lastParticlePacket + PARTICLE_PACKET_HEADER_SIZE, + _particlePacket+PARTICLE_PACKET_HEADER_SIZE , getPacketLength() - PARTICLE_PACKET_HEADER_SIZE) == 0; + } + return false; +} + +bool ParticleReceiverData::shouldSuppressDuplicatePacket() { + bool shouldSuppress = false; // assume we won't suppress + + // only consider duplicate packets + if (packetIsDuplicate()) { + _duplicatePacketCount++; + + // If this is the first suppressed packet, remember our time... + if (_duplicatePacketCount == 1) { + _firstSuppressedPacket = usecTimestampNow(); + } + + // How long has it been since we've sent one, if we're still under our max time, then keep considering + // this packet for suppression + uint64_t now = usecTimestampNow(); + long sinceFirstSuppressedPacket = now - _firstSuppressedPacket; + const long MAX_TIME_BETWEEN_DUPLICATE_PACKETS = 1000 * 1000; // 1 second. + + if (sinceFirstSuppressedPacket < MAX_TIME_BETWEEN_DUPLICATE_PACKETS) { + // Finally, if we know we've sent at least one duplicate out, then suppress the rest... + if (_duplicatePacketCount >= 1) { + shouldSuppress = true; + } + } else { + // Reset our count, we've reached our maximum time. + _duplicatePacketCount = 0; + } + } else { + // Reset our count, it wasn't a duplicate + _duplicatePacketCount = 0; + } + return shouldSuppress; +} + +void ParticleReceiverData::resetParticlePacket(bool lastWasSurpressed) { + // Whenever we call this, we will keep a copy of the last packet, so we can determine if the last packet has + // changed since we last reset it. Since we know that no two packets can ever be identical without being the same + // scene information, (e.g. the root node packet of a static scene), we can use this as a strategy for reducing + // packet send rate. + _lastParticlePacketLength = getPacketLength(); + memcpy(_lastParticlePacket, _particlePacket, _lastParticlePacketLength); + + // If we're moving, and the client asked for low res, then we force monochrome, otherwise, use + // the clients requested color state. + _currentPacketIsColor = getWantColor(); + _currentPacketIsCompressed = getWantCompression(); + PARTICLE_PACKET_FLAGS flags = 0; + if (_currentPacketIsColor) { + setAtBit(flags,PACKET_IS_COLOR_BIT); + } + if (_currentPacketIsCompressed) { + setAtBit(flags,PACKET_IS_COMPRESSED_BIT); + } + + _particlePacketAvailableBytes = MAX_PACKET_SIZE; + int numBytesPacketHeader = populateTypeAndVersion(_particlePacket, PACKET_TYPE_PARTICLE_DATA); + _particlePacketAt = _particlePacket + numBytesPacketHeader; + _particlePacketAvailableBytes -= numBytesPacketHeader; + + // pack in flags + PARTICLE_PACKET_FLAGS* flagsAt = (PARTICLE_PACKET_FLAGS*)_particlePacketAt; + *flagsAt = flags; + _particlePacketAt += sizeof(PARTICLE_PACKET_FLAGS); + _particlePacketAvailableBytes -= sizeof(PARTICLE_PACKET_FLAGS); + + // pack in sequence number + PARTICLE_PACKET_SEQUENCE* sequenceAt = (PARTICLE_PACKET_SEQUENCE*)_particlePacketAt; + *sequenceAt = _sequenceNumber; + _particlePacketAt += sizeof(PARTICLE_PACKET_SEQUENCE); + _particlePacketAvailableBytes -= sizeof(PARTICLE_PACKET_SEQUENCE); + if (!(lastWasSurpressed || _lastParticlePacketLength == PARTICLE_PACKET_HEADER_SIZE)) { + _sequenceNumber++; + } + + // pack in timestamp + PARTICLE_PACKET_SENT_TIME now = usecTimestampNow(); + PARTICLE_PACKET_SENT_TIME* timeAt = (PARTICLE_PACKET_SENT_TIME*)_particlePacketAt; + *timeAt = now; + _particlePacketAt += sizeof(PARTICLE_PACKET_SENT_TIME); + _particlePacketAvailableBytes -= sizeof(PARTICLE_PACKET_SENT_TIME); + + _particlePacketWaiting = false; +} + +void ParticleReceiverData::writeToPacket(const unsigned char* buffer, int bytes) { + // compressed packets include lead bytes which contain compressed size, this allows packing of + // multiple compressed portions together + if (_currentPacketIsCompressed) { + *(PARTICLE_PACKET_INTERNAL_SECTION_SIZE*)_particlePacketAt = bytes; + _particlePacketAt += sizeof(PARTICLE_PACKET_INTERNAL_SECTION_SIZE); + _particlePacketAvailableBytes -= sizeof(PARTICLE_PACKET_INTERNAL_SECTION_SIZE); + } + if (bytes <= _particlePacketAvailableBytes) { + memcpy(_particlePacketAt, buffer, bytes); + _particlePacketAvailableBytes -= bytes; + _particlePacketAt += bytes; + _particlePacketWaiting = true; + } +} + +ParticleReceiverData::~ParticleReceiverData() { + delete[] _particlePacket; + delete[] _lastParticlePacket; + + if (_particleSendThread) { + _particleSendThread->terminate(); + delete _particleSendThread; + } +} + +bool ParticleReceiverData::updateCurrentViewFrustum() { + bool currentViewFrustumChanged = false; + ViewFrustum newestViewFrustum; + // get position and orientation details from the camera + newestViewFrustum.setPosition(getCameraPosition()); + newestViewFrustum.setOrientation(getCameraOrientation()); + + // Also make sure it's got the correct lens details from the camera + float originalFOV = getCameraFov(); + float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND; + + newestViewFrustum.setFieldOfView(wideFOV); // hack + newestViewFrustum.setAspectRatio(getCameraAspectRatio()); + newestViewFrustum.setNearClip(getCameraNearClip()); + newestViewFrustum.setFarClip(getCameraFarClip()); + newestViewFrustum.setEyeOffsetPosition(getCameraEyeOffsetPosition()); + + // if there has been a change, then recalculate + if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) { + _currentViewFrustum = newestViewFrustum; + _currentViewFrustum.calculate(); + currentViewFrustumChanged = true; + } + + // Also check for LOD changes from the client + if (_lodInitialized) { + if (_lastClientBoundaryLevelAdjust != getBoundaryLevelAdjust()) { + _lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust(); + _lodChanged = true; + } + if (_lastClientParticleSizeScale != getParticleSizeScale()) { + _lastClientParticleSizeScale = getParticleSizeScale(); + _lodChanged = true; + } + } else { + _lodInitialized = true; + _lastClientParticleSizeScale = getParticleSizeScale(); + _lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust(); + _lodChanged = false; + } + + // When we first detect that the view stopped changing, we record this. + // but we don't change it back to false until we've completely sent this + // scene. + if (_viewFrustumChanging && !currentViewFrustumChanged) { + _viewFrustumJustStoppedChanging = true; + } + _viewFrustumChanging = currentViewFrustumChanged; + return currentViewFrustumChanged; +} + +void ParticleReceiverData::setViewSent(bool viewSent) { + _viewSent = viewSent; + if (viewSent) { + _viewFrustumJustStoppedChanging = false; + _lodChanged = false; + } +} + +void ParticleReceiverData::updateLastKnownViewFrustum() { + bool frustumChanges = !_lastKnownViewFrustum.isVerySimilar(_currentViewFrustum); + + if (frustumChanges) { + // save our currentViewFrustum into our lastKnownViewFrustum + _lastKnownViewFrustum = _currentViewFrustum; + } + + // save that we know the view has been sent. + uint64_t now = usecTimestampNow(); + setLastTimeBagEmpty(now); // is this what we want? poor names +} + + +bool ParticleReceiverData::moveShouldDump() const { + glm::vec3 oldPosition = _lastKnownViewFrustum.getPosition(); + glm::vec3 newPosition = _currentViewFrustum.getPosition(); + + // theoretically we could make this slightly larger but relative to avatar scale. + const float MAXIMUM_MOVE_WITHOUT_DUMP = 0.0f; + if (glm::distance(newPosition, oldPosition) > MAXIMUM_MOVE_WITHOUT_DUMP) { + return true; + } + return false; +} + +void ParticleReceiverData::dumpOutOfView() { + int stillInView = 0; + int outOfView = 0; + ParticleNodeBag tempBag; + while (!nodeBag.isEmpty()) { + ParticleNode* node = nodeBag.extract(); + if (node->isInView(_currentViewFrustum)) { + tempBag.insert(node); + stillInView++; + } else { + outOfView++; + } + } + if (stillInView > 0) { + while (!tempBag.isEmpty()) { + ParticleNode* node = tempBag.extract(); + if (node->isInView(_currentViewFrustum)) { + nodeBag.insert(node); + } + } + } +} + diff --git a/libraries/particle-server/src/ParticleReceiverData.h b/libraries/particle-server/src/ParticleReceiverData.h new file mode 100644 index 0000000000..d9b4eb3e34 --- /dev/null +++ b/libraries/particle-server/src/ParticleReceiverData.h @@ -0,0 +1,123 @@ +// +// ParticleReceiverData.h +// hifi +// +// Created by Brad Hefta-Gaub on 12/2/13 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// + +#ifndef __hifi__ParticleReceiverData__ +#define __hifi__ParticleReceiverData__ + +#include +#include +#include +#include + +#include +#include +#include +#include + +class ParticleSendThread; +class ParticleServer; + +class ParticleReceiverData : public ParticleQuery { +public: + ParticleReceiverData(Node* owningNode); + virtual ~ParticleReceiverData(); + + void resetParticlePacket(bool lastWasSurpressed = false); // resets particle packet to after "V" header + + void writeToPacket(const unsigned char* buffer, int bytes); // writes to end of packet + + const unsigned char* getPacket() const { return _particlePacket; } + int getPacketLength() const { return (MAX_PACKET_SIZE - _particlePacketAvailableBytes); } + bool isPacketWaiting() const { return _particlePacketWaiting; } + + bool packetIsDuplicate() const; + bool shouldSuppressDuplicatePacket(); + + int getAvailable() const { return _particlePacketAvailableBytes; } + int getMaxSearchLevel() const { return _maxSearchLevel; } + void resetMaxSearchLevel() { _maxSearchLevel = 1; } + void incrementMaxSearchLevel() { _maxSearchLevel++; } + + int getMaxLevelReached() const { return _maxLevelReachedInLastSearch; } + void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; } + + ParticleNodeBag nodeBag; + CoverageMap map; + + ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; } + ViewFrustum& getLastKnownViewFrustum() { return _lastKnownViewFrustum; } + + // These are not classic setters because they are calculating and maintaining state + // which is set asynchronously through the network receive + bool updateCurrentViewFrustum(); + void updateLastKnownViewFrustum(); + + bool getViewSent() const { return _viewSent; } + void setViewSent(bool viewSent); + + bool getViewFrustumChanging() const { return _viewFrustumChanging; } + bool getViewFrustumJustStoppedChanging() const { return _viewFrustumJustStoppedChanging; } + + bool moveShouldDump() const; + + uint64_t getLastTimeBagEmpty() const { return _lastTimeBagEmpty; } + void setLastTimeBagEmpty(uint64_t lastTimeBagEmpty) { _lastTimeBagEmpty = lastTimeBagEmpty; } + + bool getCurrentPacketIsColor() const { return _currentPacketIsColor; } + bool getCurrentPacketIsCompressed() const { return _currentPacketIsCompressed; } + bool getCurrentPacketFormatMatches() { + return (getCurrentPacketIsColor() == getWantColor() && getCurrentPacketIsCompressed() == getWantCompression()); + } + + bool hasLodChanged() const { return _lodChanged; }; + + ParticleSceneStats stats; + + void initializeParticleSendThread(ParticleServer* particleServer); + bool isParticleSendThreadInitalized() { return _particleSendThread; } + + void dumpOutOfView(); + +private: + ParticleReceiverData(const ParticleReceiverData &); + ParticleReceiverData& operator= (const ParticleReceiverData&); + + bool _viewSent; + unsigned char* _particlePacket; + unsigned char* _particlePacketAt; + int _particlePacketAvailableBytes; + bool _particlePacketWaiting; + + unsigned char* _lastParticlePacket; + int _lastParticlePacketLength; + int _duplicatePacketCount; + uint64_t _firstSuppressedPacket; + + int _maxSearchLevel; + int _maxLevelReachedInLastSearch; + ViewFrustum _currentViewFrustum; + ViewFrustum _lastKnownViewFrustum; + uint64_t _lastTimeBagEmpty; + bool _viewFrustumChanging; + bool _viewFrustumJustStoppedChanging; + bool _currentPacketIsColor; + bool _currentPacketIsCompressed; + + ParticleSendThread* _particleSendThread; + + // watch for LOD changes + int _lastClientBoundaryLevelAdjust; + float _lastClientParticleSizeScale; + bool _lodChanged; + bool _lodInitialized; + + PARTICLE_PACKET_SEQUENCE _sequenceNumber; +}; + +#endif /* defined(__hifi__ParticleReceiverData__) */ diff --git a/libraries/particle-server/src/ParticleSendThread.cpp b/libraries/particle-server/src/ParticleSendThread.cpp new file mode 100644 index 0000000000..b3a398f8ae --- /dev/null +++ b/libraries/particle-server/src/ParticleSendThread.cpp @@ -0,0 +1,551 @@ +// +// ParticleSendThread.cpp +// particle-server +// +// Created by Brad Hefta-Gaub on 12/2/13 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// Threaded or non-threaded particle packet sender +// + +#include +#include +#include +#include + +#include "ParticleSendThread.h" +#include "ParticleServer.h" +#include "ParticleServerConsts.h" + + +uint64_t startSceneSleepTime = 0; +uint64_t endSceneSleepTime = 0; + + +ParticleSendThread::ParticleSendThread(const QUuid& nodeUUID, ParticleServer* myServer) : + _nodeUUID(nodeUUID), + _myServer(myServer), + _packetData() +{ +} + +bool ParticleSendThread::process() { + uint64_t start = usecTimestampNow(); + bool gotLock = false; + + // don't do any send processing until the initial load of the particles is complete... + if (_myServer->isInitialLoadComplete()) { + Node* node = NodeList::getInstance()->nodeWithUUID(_nodeUUID); + + if (node) { + // make sure the node list doesn't kill our node while we're using it + if (node->trylock()) { + gotLock = true; + ParticleReceiverData* nodeData = NULL; + + nodeData = (ParticleReceiverData*) node->getLinkedData(); + + int packetsSent = 0; + + // Sometimes the node data has not yet been linked, in which case we can't really do anything + if (nodeData) { + bool viewFrustumChanged = nodeData->updateCurrentViewFrustum(); + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("nodeData->updateCurrentViewFrustum() changed=%s\n", debug::valueOf(viewFrustumChanged)); + } + packetsSent = deepestLevelParticleDistributor(node, nodeData, viewFrustumChanged); + } + + node->unlock(); // we're done with this node for now. + } + } + } else { + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + qDebug("ParticleSendThread::process() waiting for isInitialLoadComplete()\n"); + } + } + + // Only sleep if we're still running and we got the lock last time we tried, otherwise try to get the lock asap + if (isStillRunning() && gotLock) { + // dynamically sleep until we need to fire off the next set of particles + int elapsed = (usecTimestampNow() - start); + int usecToSleep = VOXEL_SEND_INTERVAL_USECS - elapsed; + + if (usecToSleep > 0) { + PerformanceWarning warn(false,"ParticleSendThread... usleep()",false,&_usleepTime,&_usleepCalls); + usleep(usecToSleep); + } else { + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + std::cout << "Last send took too much time, not sleeping!\n"; + } + } + } + + return isStillRunning(); // keep running till they terminate us +} + +uint64_t ParticleSendThread::_usleepTime = 0; +uint64_t ParticleSendThread::_usleepCalls = 0; + +uint64_t ParticleSendThread::_totalBytes = 0; +uint64_t ParticleSendThread::_totalWastedBytes = 0; +uint64_t ParticleSendThread::_totalPackets = 0; + +int ParticleSendThread::handlePacketSend(Node* node, ParticleReceiverData* nodeData, int& trueBytesSent, int& truePacketsSent) { + bool debug = _myServer->wantsDebugParticleSending(); + uint64_t now = usecTimestampNow(); + + bool packetSent = false; // did we send a packet? + int packetsSent = 0; + // Here's where we check to see if this packet is a duplicate of the last packet. If it is, we will silently + // obscure the packet and not send it. This allows the callers and upper level logic to not need to know about + // this rate control savings. + if (nodeData->shouldSuppressDuplicatePacket()) { + nodeData->resetParticlePacket(true); // we still need to reset it though! + return packetsSent; // without sending... + } + + const unsigned char* messageData = nodeData->getPacket(); + int numBytesPacketHeader = numBytesForPacketHeader(messageData); + const unsigned char* dataAt = messageData + numBytesPacketHeader; + dataAt += sizeof(VOXEL_PACKET_FLAGS); + VOXEL_PACKET_SEQUENCE sequence = (*(VOXEL_PACKET_SEQUENCE*)dataAt); + dataAt += sizeof(VOXEL_PACKET_SEQUENCE); + + + // If we've got a stats message ready to send, then see if we can piggyback them together + if (nodeData->stats.isReadyToSend()) { + // Send the stats message to the client + unsigned char* statsMessage = nodeData->stats.getStatsMessage(); + int statsMessageLength = nodeData->stats.getStatsMessageLength(); + + // If the size of the stats message and the particle message will fit in a packet, then piggyback them + if (nodeData->getPacketLength() + statsMessageLength < MAX_PACKET_SIZE) { + + // copy particle message to back of stats message + memcpy(statsMessage + statsMessageLength, nodeData->getPacket(), nodeData->getPacketLength()); + statsMessageLength += nodeData->getPacketLength(); + + // since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since + // there was nothing else to send. + int thisWastedBytes = 0; + _totalWastedBytes += thisWastedBytes; + _totalBytes += nodeData->getPacketLength(); + _totalPackets++; + if (debug) { + qDebug("Adding stats to packet at %llu [%llu]: sequence: %d size:%d [%llu] wasted bytes:%d [%llu]\n", + now, + _totalPackets, + sequence, nodeData->getPacketLength(), _totalBytes, + thisWastedBytes, _totalWastedBytes); + } + + // actually send it + NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(), statsMessage, statsMessageLength); + packetSent = true; + } else { + // not enough room in the packet, send two packets + NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(), statsMessage, statsMessageLength); + + // since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since + // there was nothing else to send. + int thisWastedBytes = 0; + _totalWastedBytes += thisWastedBytes; + _totalBytes += statsMessageLength; + _totalPackets++; + if (debug) { + qDebug("Sending separate stats packet at %llu [%llu]: size:%d [%llu] wasted bytes:%d [%llu]\n", + now, + _totalPackets, + statsMessageLength, _totalBytes, + thisWastedBytes, _totalWastedBytes); + } + + trueBytesSent += statsMessageLength; + truePacketsSent++; + packetsSent++; + + NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(), + nodeData->getPacket(), nodeData->getPacketLength()); + + packetSent = true; + + thisWastedBytes = MAX_PACKET_SIZE - nodeData->getPacketLength(); + _totalWastedBytes += thisWastedBytes; + _totalBytes += nodeData->getPacketLength(); + _totalPackets++; + if (debug) { + qDebug("Sending packet at %llu [%llu]: sequence: %d size:%d [%llu] wasted bytes:%d [%llu]\n", + now, + _totalPackets, + sequence, nodeData->getPacketLength(), _totalBytes, + thisWastedBytes, _totalWastedBytes); + } + } + nodeData->stats.markAsSent(); + } else { + // If there's actually a packet waiting, then send it. + if (nodeData->isPacketWaiting()) { + // just send the particle packet + NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(), + nodeData->getPacket(), nodeData->getPacketLength()); + packetSent = true; + + int thisWastedBytes = MAX_PACKET_SIZE - nodeData->getPacketLength(); + _totalWastedBytes += thisWastedBytes; + _totalBytes += nodeData->getPacketLength(); + _totalPackets++; + if (debug) { + qDebug("Sending packet at %llu [%llu]: sequence:%d size:%d [%llu] wasted bytes:%d [%llu]\n", + now, + _totalPackets, + sequence, nodeData->getPacketLength(), _totalBytes, + thisWastedBytes, _totalWastedBytes); + } + } + } + // remember to track our stats + if (packetSent) { + nodeData->stats.packetSent(nodeData->getPacketLength()); + trueBytesSent += nodeData->getPacketLength(); + truePacketsSent++; + packetsSent++; + nodeData->resetParticlePacket(); + } + + return packetsSent; +} + +/// Version of particle distributor that sends the deepest LOD level at once +int ParticleSendThread::deepestLevelParticleDistributor(Node* node, ParticleReceiverData* nodeData, bool viewFrustumChanged) { + + int truePacketsSent = 0; + int trueBytesSent = 0; + int packetsSentThisInterval = 0; + bool somethingToSend = true; // assume we have something + + // FOR NOW... node tells us if it wants to receive only view frustum deltas + bool wantDelta = viewFrustumChanged && nodeData->getWantDelta(); + + // If our packet already has content in it, then we must use the color choice of the waiting packet. + // If we're starting a fresh packet, then... + // If we're moving, and the client asked for low res, then we force monochrome, otherwise, use + // the clients requested color state. + bool wantColor = nodeData->getWantColor(); + bool wantCompression = nodeData->getWantCompression(); + + // If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color + // then let's just send that waiting packet. + if (!nodeData->getCurrentPacketFormatMatches()) { + if (nodeData->isPacketWaiting()) { + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("wantColor=%s wantCompression=%s SENDING PARTIAL PACKET! currentPacketIsColor=%s currentPacketIsCompressed=%s\n", + debug::valueOf(wantColor), debug::valueOf(wantCompression), + debug::valueOf(nodeData->getCurrentPacketIsColor()), + debug::valueOf(nodeData->getCurrentPacketIsCompressed()) ); + } + packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); + } else { + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("wantColor=%s wantCompression=%s FIXING HEADER! currentPacketIsColor=%s currentPacketIsCompressed=%s\n", + debug::valueOf(wantColor), debug::valueOf(wantCompression), + debug::valueOf(nodeData->getCurrentPacketIsColor()), + debug::valueOf(nodeData->getCurrentPacketIsCompressed()) ); + } + nodeData->resetParticlePacket(); + } + int targetSize = MAX_VOXEL_PACKET_DATA_SIZE; + if (wantCompression) { + targetSize = nodeData->getAvailable() - sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE); + } + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("line:%d _packetData.changeSettings() wantCompression=%s targetSize=%d\n",__LINE__, + debug::valueOf(wantCompression), targetSize); + } + + _packetData.changeSettings(wantCompression, targetSize); + } + + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("wantColor/isColor=%s/%s wantCompression/isCompressed=%s/%s viewFrustumChanged=%s, getWantLowResMoving()=%s\n", + debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()), + debug::valueOf(wantCompression), debug::valueOf(nodeData->getCurrentPacketIsCompressed()), + debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->getWantLowResMoving())); + } + + const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL; + + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("deepestLevelParticleDistributor() viewFrustumChanged=%s, nodeBag.isEmpty=%s, viewSent=%s\n", + debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()), + debug::valueOf(nodeData->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 || nodeData->nodeBag.isEmpty()) { + uint64_t now = usecTimestampNow(); + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("(viewFrustumChanged=%s || nodeData->nodeBag.isEmpty() =%s)...\n", + debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty())); + if (nodeData->getLastTimeBagEmpty() > 0) { + float elapsedSceneSend = (now - nodeData->getLastTimeBagEmpty()) / 1000000.0f; + if (viewFrustumChanged) { + printf("viewFrustumChanged resetting after elapsed time to send scene = %f seconds", elapsedSceneSend); + } else { + printf("elapsed time to send scene = %f seconds", elapsedSceneSend); + } + printf(" [occlusionCulling:%s, wantDelta:%s, wantColor:%s ]\n", + debug::valueOf(nodeData->getWantOcclusionCulling()), debug::valueOf(wantDelta), + debug::valueOf(wantColor)); + } + } + + // if our view has changed, we need to reset these things... + if (viewFrustumChanged) { + if (_myServer->wantDumpParticlesOnMove() || nodeData->moveShouldDump() || nodeData->hasLodChanged()) { + nodeData->dumpOutOfView(); + } + nodeData->map.erase(); + } + + if (!viewFrustumChanged && !nodeData->getWantDelta()) { + // only set our last sent time if we weren't resetting due to frustum change + uint64_t now = usecTimestampNow(); + nodeData->setLastTimeBagEmpty(now); + } + + // track completed scenes and send out the stats packet accordingly + nodeData->stats.sceneCompleted(); + ::endSceneSleepTime = _usleepTime; + unsigned long sleepTime = ::endSceneSleepTime - ::startSceneSleepTime; + + unsigned long encodeTime = nodeData->stats.getTotalEncodeTime(); + unsigned long elapsedTime = nodeData->stats.getElapsedTime(); + packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); + + if (_myServer->wantsDebugParticleSending()) { + qDebug("Scene completed at %llu encodeTime:%lu sleepTime:%lu elapsed:%lu Packets:%llu Bytes:%llu Wasted:%llu\n", + usecTimestampNow(), encodeTime, sleepTime, elapsedTime, _totalPackets, _totalBytes, _totalWastedBytes); + } + + if (_myServer->wantDisplayParticleStats()) { + nodeData->stats.printDebugDetails(); + } + + // start tracking our stats + bool isFullScene = ((!viewFrustumChanged || !nodeData->getWantDelta()) + && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged(); + + // If we're starting a full scene, then definitely we want to empty the nodeBag + if (isFullScene) { + nodeData->nodeBag.deleteAll(); + } + + if (_myServer->wantsDebugParticleSending()) { + qDebug("Scene started at %llu Packets:%llu Bytes:%llu Wasted:%llu\n", + usecTimestampNow(),_totalPackets,_totalBytes,_totalWastedBytes); + } + + ::startSceneSleepTime = _usleepTime; + nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, + _myServer->getServerTree().rootNode, _myServer->getJurisdiction()); + + // This is the start of "resending" the scene. + bool dontRestartSceneOnMove = false; // this is experimental + if (dontRestartSceneOnMove) { + if (nodeData->nodeBag.isEmpty()) { + nodeData->nodeBag.insert(_myServer->getServerTree().rootNode); // only in case of empty + } + } else { + nodeData->nodeBag.insert(_myServer->getServerTree().rootNode); // original behavior, reset on move or empty + } + } + + // If we have something in our nodeBag, then turn them into packets and send them out... + if (!nodeData->nodeBag.isEmpty()) { + int bytesWritten = 0; + uint64_t start = usecTimestampNow(); + uint64_t startCompressTimeMsecs = ParticlePacketData::getCompressContentTime() / 1000; + uint64_t startCompressCalls = ParticlePacketData::getCompressContentCalls(); + + int clientMaxPacketsPerInterval = std::max(1,(nodeData->getMaxParticlePacketsPerSecond() / INTERVALS_PER_SECOND)); + int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); + + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n", + truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(), + nodeData->getMaxParticlePacketsPerSecond(), clientMaxPacketsPerInterval); + } + + int extraPackingAttempts = 0; + while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval) { + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n", + truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(), + nodeData->getMaxParticlePacketsPerSecond(), clientMaxPacketsPerInterval); + } + + bool lastNodeDidntFit = false; // assume each node fits + if (!nodeData->nodeBag.isEmpty()) { + ParticleNode* subTree = nodeData->nodeBag.extract(); + bool wantOcclusionCulling = nodeData->getWantOcclusionCulling(); + CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP; + + float particleSizeScale = nodeData->getParticleSizeScale(); + int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); + + int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving() + ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); + + + bool isFullScene = ((!viewFrustumChanged || !nodeData->getWantDelta()) && + nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged(); + + EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor, + WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum, + wantOcclusionCulling, coverageMap, boundaryLevelAdjust, particleSizeScale, + nodeData->getLastTimeBagEmpty(), + isFullScene, &nodeData->stats, _myServer->getJurisdiction()); + + + _myServer->getServerTree().lockForRead(); + nodeData->stats.encodeStarted(); + bytesWritten = _myServer->getServerTree().encodeTreeBitstream(subTree, &_packetData, nodeData->nodeBag, params); + + // if we're trying to fill a full size packet, then we use this logic to determine if we have a DIDNT_FIT case. + if (_packetData.getTargetSize() == MAX_VOXEL_PACKET_DATA_SIZE) { + if (_packetData.hasContent() && bytesWritten == 0 && + params.stopReason == EncodeBitstreamParams::DIDNT_FIT) { + lastNodeDidntFit = true; + } + } else { + // in compressed mode and we are trying to pack more... and we don't care if the _packetData has + // content or not... because in this case even if we were unable to pack any data, we want to drop + // below to our sendNow logic, but we do want to track that we attempted to pack extra + extraPackingAttempts++; + if (bytesWritten == 0 && params.stopReason == EncodeBitstreamParams::DIDNT_FIT) { + lastNodeDidntFit = true; + } + } + + nodeData->stats.encodeStopped(); + _myServer->getServerTree().unlock(); + } else { + // If the bag was empty then we didn't even attempt to encode, and so we know the bytesWritten were 0 + bytesWritten = 0; + somethingToSend = false; // this will cause us to drop out of the loop... + } + + // If the last node didn't fit, but we're in compressed mode, then we actually want to see if we can fit a + // little bit more in this packet. To do this we + + // We only consider sending anything if there is something in the _packetData to send... But + // if bytesWritten == 0 it means either the subTree couldn't fit or we had an empty bag... Both cases + // mean we should send the previous packet contents and reset it. + if (lastNodeDidntFit) { + if (_packetData.hasContent()) { + // if for some reason the finalized size is greater than our available size, then probably the "compressed" + // form actually inflated beyond our padding, and in this case we will send the current packet, then + // write to out new packet... + int writtenSize = _packetData.getFinalizedSize() + + (nodeData->getCurrentPacketIsCompressed() ? sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE) : 0); + + + if (writtenSize > nodeData->getAvailable()) { + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("writtenSize[%d] > available[%d] too big, sending packet as is.\n", + writtenSize, nodeData->getAvailable()); + } + packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); + } + + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("calling writeToPacket() available=%d compressedSize=%d uncompressedSize=%d target=%d\n", + nodeData->getAvailable(), _packetData.getFinalizedSize(), + _packetData.getUncompressedSize(), _packetData.getTargetSize()); + } + nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize()); + extraPackingAttempts = 0; + } + + // If we're not running compressed, the we know we can just send now. Or if we're running compressed, but + // the packet doesn't have enough space to bother attempting to pack more... + bool sendNow = true; + + if (nodeData->getCurrentPacketIsCompressed() && + nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING && + extraPackingAttempts <= REASONABLE_NUMBER_OF_PACKING_ATTEMPTS) { + sendNow = false; // try to pack more + } + + int targetSize = MAX_VOXEL_PACKET_DATA_SIZE; + if (sendNow) { + packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); + if (wantCompression) { + targetSize = nodeData->getAvailable() - sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE); + } + } else { + // If we're in compressed mode, then we want to see if we have room for more in this wire packet. + // but we've finalized the _packetData, so we want to start a new section, we will do that by + // resetting the packet settings with the max uncompressed size of our current available space + // in the wire packet. We also include room for our section header, and a little bit of padding + // to account for the fact that whenc compressing small amounts of data, we sometimes end up with + // a larger compressed size then uncompressed size + targetSize = nodeData->getAvailable() - sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING; + } + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("line:%d _packetData.changeSettings() wantCompression=%s targetSize=%d\n",__LINE__, + debug::valueOf(nodeData->getWantCompression()), targetSize); + } + _packetData.changeSettings(nodeData->getWantCompression(), targetSize); // will do reset + } + } + + uint64_t end = usecTimestampNow(); + int elapsedmsec = (end - start)/1000; + + uint64_t endCompressCalls = ParticlePacketData::getCompressContentCalls(); + int elapsedCompressCalls = endCompressCalls - startCompressCalls; + + uint64_t endCompressTimeMsecs = ParticlePacketData::getCompressContentTime() / 1000; + int elapsedCompressTimeMsecs = endCompressTimeMsecs - startCompressTimeMsecs; + + + if (elapsedmsec > 100) { + if (elapsedmsec > 1000) { + int elapsedsec = (end - start)/1000000; + printf("WARNING! packetLoop() took %d seconds [%d milliseconds %d calls in compress] to generate %d bytes in %d packets %d nodes still to send\n", + elapsedsec, elapsedCompressTimeMsecs, elapsedCompressCalls, trueBytesSent, truePacketsSent, nodeData->nodeBag.count()); + } else { + printf("WARNING! packetLoop() took %d milliseconds [%d milliseconds %d calls in compress] to generate %d bytes in %d packets, %d nodes still to send\n", + elapsedmsec, elapsedCompressTimeMsecs, elapsedCompressCalls, trueBytesSent, truePacketsSent, nodeData->nodeBag.count()); + } + } else if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("packetLoop() took %d milliseconds [%d milliseconds %d calls in compress] to generate %d bytes in %d packets, %d nodes still to send\n", + elapsedmsec, elapsedCompressTimeMsecs, elapsedCompressCalls, trueBytesSent, truePacketsSent, nodeData->nodeBag.count()); + } + + // if after sending packets we've emptied our bag, then we want to remember that we've sent all + // the particles from the current view frustum + if (nodeData->nodeBag.isEmpty()) { + nodeData->updateLastKnownViewFrustum(); + nodeData->setViewSent(true); + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + nodeData->map.printStats(); + } + nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes + } + + if (_myServer->wantsDebugParticleSending() && _myServer->wantsVerboseDebug()) { + printf("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n", + truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(), + nodeData->getMaxParticlePacketsPerSecond(), clientMaxPacketsPerInterval); + } + + } // end if bag wasn't empty, and so we sent stuff... + + return truePacketsSent; +} + diff --git a/libraries/particle-server/src/ParticleSendThread.h b/libraries/particle-server/src/ParticleSendThread.h new file mode 100644 index 0000000000..c57b4c005b --- /dev/null +++ b/libraries/particle-server/src/ParticleSendThread.h @@ -0,0 +1,47 @@ +// +// ParticleSendThread.h +// particle-server +// +// Created by Brad Hefta-Gaub on 12/2/13 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// Threaded or non-threaded object for sending particles to a client +// + +#ifndef __particle_server__ParticleSendThread__ +#define __particle_server__ParticleSendThread__ + +#include +#include +#include +#include +#include "ParticleReceiverData.h" +#include "ParticleServer.h" + +/// Threaded processor for sending particle packets to a single client +class ParticleSendThread : public virtual GenericThread { +public: + ParticleSendThread(const QUuid& nodeUUID, ParticleServer* myServer); + + static uint64_t _totalBytes; + static uint64_t _totalWastedBytes; + static uint64_t _totalPackets; + + static uint64_t _usleepTime; + static uint64_t _usleepCalls; + +protected: + /// Implements generic processing behavior for this thread. + virtual bool process(); + +private: + QUuid _nodeUUID; + ParticleServer* _myServer; + + int handlePacketSend(Node* node, ParticleReceiverData* nodeData, int& trueBytesSent, int& truePacketsSent); + int deepestLevelParticleDistributor(Node* node, ParticleReceiverData* nodeData, bool viewFrustumChanged); + + ParticlePacketData _packetData; +}; + +#endif // __particle_server__ParticleSendThread__ diff --git a/libraries/particle-server/src/ParticleServer.cpp b/libraries/particle-server/src/ParticleServer.cpp new file mode 100644 index 0000000000..062074fe86 --- /dev/null +++ b/libraries/particle-server/src/ParticleServer.cpp @@ -0,0 +1,802 @@ +// +// ParticleSever.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 12/2/13 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +//#include +#include "ParticleReceiverData.h" +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include "Syssocket.h" +#include "Systime.h" +#else +#include +#include +#include +#endif + +#include "ParticleSever.h" +#include "ParticleSeverConsts.h" + +const char* LOCAL_PARTICLES_PERSIST_FILE = "resources/particles.svo"; +const char* PARTICLES_PERSIST_FILE = "/etc/highfidelity/particle-server/resources/particles.svo"; + +void attachParticleReceiverDataToNode(Node* newNode) { + if (newNode->getLinkedData() == NULL) { + ParticleReceiverData* particleNodeData = new ParticleReceiverData(newNode); + newNode->setLinkedData(particleNodeData); + } +} + +ParticleSever* ParticleSever::_theInstance = NULL; + +ParticleSever::ParticleSever(const unsigned char* dataBuffer, int numBytes) : Assignment(dataBuffer, numBytes), + _serverTree(true) { + _argc = 0; + _argv = NULL; + + _packetsPerClientPerInterval = 10; + _wantParticlePersist = true; + _wantLocalDomain = false; + _debugParticleSending = false; + _shouldShowAnimationDebug = false; + _displayParticleStats = false; + _debugParticleReceiving = false; + _sendEnvironments = true; + _sendMinimalEnvironment = false; + _dumpParticlesOnMove = false; + _verboseDebug = false; + _jurisdiction = NULL; + _jurisdictionSender = NULL; + _ParticleSeverPacketProcessor = NULL; + _particlePersistThread = NULL; + _parsedArgV = NULL; + + _started = time(0); + _startedUSecs = usecTimestampNow(); + + _theInstance = this; +} + +ParticleSever::~ParticleSever() { + if (_parsedArgV) { + for (int i = 0; i < _argc; i++) { + delete[] _parsedArgV[i]; + } + delete[] _parsedArgV; + } +} + +void ParticleSever::initMongoose(int port) { + // setup the mongoose web server + struct mg_callbacks callbacks = {}; + + QString documentRoot = QString("%1/resources/web").arg(QCoreApplication::applicationDirPath()); + QString listenPort = QString("%1").arg(port); + + + // list of options. Last element must be NULL. + const char* options[] = { + "listening_ports", listenPort.toLocal8Bit().constData(), + "document_root", documentRoot.toLocal8Bit().constData(), + NULL }; + + callbacks.begin_request = civetwebRequestHandler; + + // Start the web server. + mg_start(&callbacks, NULL, options); +} + +int ParticleSever::civetwebRequestHandler(struct mg_connection* connection) { + const struct mg_request_info* ri = mg_get_request_info(connection); + + ParticleSever* theServer = GetInstance(); + +#ifdef FORCE_CRASH + if (strcmp(ri->uri, "/force_crash") == 0 && strcmp(ri->request_method, "GET") == 0) { + qDebug() << "About to force a crash!\n"; + int foo; + int* forceCrash = &foo; + mg_printf(connection, "%s", "HTTP/1.0 200 OK\r\n\r\n"); + mg_printf(connection, "%s", "forcing a crash....\r\n"); + delete[] forceCrash; + mg_printf(connection, "%s", "did it crash....\r\n"); + return 1; + } +#endif + + bool showStats = false; + if (strcmp(ri->uri, "/") == 0 && strcmp(ri->request_method, "GET") == 0) { + showStats = true; + } + + if (strcmp(ri->uri, "/resetStats") == 0 && strcmp(ri->request_method, "GET") == 0) { + theServer->_ParticleSeverPacketProcessor->resetStats(); + showStats = true; + } + + if (showStats) { + uint64_t checkSum; + // return a 200 + mg_printf(connection, "%s", "HTTP/1.0 200 OK\r\n"); + mg_printf(connection, "%s", "Content-Type: text/html\r\n\r\n"); + mg_printf(connection, "%s", "\r\n"); + mg_printf(connection, "%s", "
\r\n");
+        mg_printf(connection, "%s", "Your Particle Server is running... [RELOAD]\r\n");
+
+        tm* localtm = localtime(&theServer->_started);
+        const int MAX_TIME_LENGTH = 128;
+        char buffer[MAX_TIME_LENGTH];
+        strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", localtm);
+        mg_printf(connection, "Running since: %s", buffer);
+
+        // Convert now to tm struct for UTC
+        tm* gmtm = gmtime(&theServer->_started);
+        if (gmtm != NULL) {
+            strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", gmtm);
+            mg_printf(connection, " [%s UTM] ", buffer);
+        }
+        mg_printf(connection, "%s", "\r\n");
+
+        uint64_t now  = usecTimestampNow();
+        const int USECS_PER_MSEC = 1000;
+        uint64_t msecsElapsed = (now - theServer->_startedUSecs) / USECS_PER_MSEC;
+        const int MSECS_PER_SEC = 1000;
+        const int SECS_PER_MIN = 60;
+        const int MIN_PER_HOUR = 60;
+        const int MSECS_PER_MIN = MSECS_PER_SEC * SECS_PER_MIN;
+
+        float seconds = (msecsElapsed % MSECS_PER_MIN)/(float)MSECS_PER_SEC;
+        int minutes = (msecsElapsed/(MSECS_PER_MIN)) % MIN_PER_HOUR;
+        int hours = (msecsElapsed/(MSECS_PER_MIN * MIN_PER_HOUR));
+
+        mg_printf(connection, "%s", "Uptime: ");
+        if (hours > 0) {
+            mg_printf(connection, "%d hour%s ", hours, (hours > 1) ? "s" : "" );
+        }
+        if (minutes > 0) {
+            mg_printf(connection, "%d minute%s ", minutes, (minutes > 1) ? "s" : "");
+        }
+        if (seconds > 0) {
+            mg_printf(connection, "%.3f seconds ", seconds);
+        }
+        mg_printf(connection, "%s", "\r\n");
+        mg_printf(connection, "%s", "\r\n");
+
+
+        // display particle file load time
+        if (theServer->isInitialLoadComplete()) {
+            time_t* loadCompleted = theServer->getLoadCompleted();
+            if (loadCompleted) {
+                tm* particlesLoadedAtLocal = localtime(loadCompleted);
+                const int MAX_TIME_LENGTH = 128;
+                char buffer[MAX_TIME_LENGTH];
+                strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", particlesLoadedAtLocal);
+                mg_printf(connection, "Particles Loaded At: %s", buffer);
+
+                // Convert now to tm struct for UTC
+                tm* particlesLoadedAtUTM = gmtime(theServer->getLoadCompleted());
+                if (gmtm != NULL) {
+                    strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", particlesLoadedAtUTM);
+                    mg_printf(connection, " [%s UTM] ", buffer);
+                }
+            } else {
+                mg_printf(connection, "%s", "Particle Persist Disabled...\r\n");
+            }
+            mg_printf(connection, "%s", "\r\n");
+
+
+            uint64_t msecsElapsed = theServer->getLoadElapsedTime() / USECS_PER_MSEC;;
+            float seconds = (msecsElapsed % MSECS_PER_MIN)/(float)MSECS_PER_SEC;
+            int minutes = (msecsElapsed/(MSECS_PER_MIN)) % MIN_PER_HOUR;
+            int hours = (msecsElapsed/(MSECS_PER_MIN * MIN_PER_HOUR));
+
+            mg_printf(connection, "%s", "Particles Load Took: ");
+            if (hours > 0) {
+                mg_printf(connection, "%d hour%s ", hours, (hours > 1) ? "s" : "" );
+            }
+            if (minutes > 0) {
+                mg_printf(connection, "%d minute%s ", minutes, (minutes > 1) ? "s" : "");
+            }
+            if (seconds >= 0) {
+                mg_printf(connection, "%.3f seconds", seconds);
+            }
+            mg_printf(connection, "%s", "\r\n");
+
+        } else {
+            mg_printf(connection, "%s", "Particles not yet loaded...\r\n");
+        }
+
+        mg_printf(connection, "%s", "\r\n");
+
+        mg_printf(connection, "%s", "\r\n");
+
+        mg_printf(connection, "%s", "Configuration:\r\n");
+
+        for (int i = 1; i < theServer->_argc; i++) {
+            mg_printf(connection, "%s ", theServer->_argv[i]);
+        }
+        mg_printf(connection, "%s", "\r\n"); // one to end the config line
+        mg_printf(connection, "%s", "\r\n"); // two more for spacing
+        mg_printf(connection, "%s", "\r\n");
+
+        // display scene stats
+        unsigned long nodeCount = ParticleNode::getNodeCount();
+        unsigned long internalNodeCount = ParticleNode::getInternalNodeCount();
+        unsigned long leafNodeCount = ParticleNode::getLeafNodeCount();
+        
+        QLocale locale(QLocale::English);
+        const float AS_PERCENT = 100.0;
+        mg_printf(connection, "%s", "Current Nodes in scene:\r\n");
+        mg_printf(connection, "       Total Nodes: %s nodes\r\n",
+                    locale.toString((uint)nodeCount).rightJustified(16, ' ').toLocal8Bit().constData());
+        mg_printf(connection, "    Internal Nodes: %s nodes (%5.2f%%)\r\n",
+            locale.toString((uint)internalNodeCount).rightJustified(16, ' ').toLocal8Bit().constData(),
+            ((float)internalNodeCount / (float)nodeCount) * AS_PERCENT);
+        mg_printf(connection, "        Leaf Nodes: %s nodes (%5.2f%%)\r\n", 
+            locale.toString((uint)leafNodeCount).rightJustified(16, ' ').toLocal8Bit().constData(),
+            ((float)leafNodeCount / (float)nodeCount) * AS_PERCENT);
+        mg_printf(connection, "%s", "\r\n");
+        mg_printf(connection, "%s", "\r\n");
+
+
+        // display outbound packet stats
+        mg_printf(connection, "%s", "Particle Packet Statistics...\r\n");
+        uint64_t totalOutboundPackets = ParticleSendThread::_totalPackets;
+        uint64_t totalOutboundBytes = ParticleSendThread::_totalBytes;
+        uint64_t totalWastedBytes = ParticleSendThread::_totalWastedBytes;
+        uint64_t totalBytesOfOctalCodes = ParticlePacketData::getTotalBytesOfOctalCodes();
+        uint64_t totalBytesOfBitMasks = ParticlePacketData::getTotalBytesOfBitMasks();
+        uint64_t totalBytesOfColor = ParticlePacketData::getTotalBytesOfColor();
+
+        const int COLUMN_WIDTH = 10;
+        mg_printf(connection, "           Total Outbound Packets: %s packets\r\n",
+            locale.toString((uint)totalOutboundPackets).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+        mg_printf(connection, "             Total Outbound Bytes: %s bytes\r\n",
+            locale.toString((uint)totalOutboundBytes).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+        mg_printf(connection, "               Total Wasted Bytes: %s bytes\r\n",
+            locale.toString((uint)totalWastedBytes).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+        mg_printf(connection, "            Total OctalCode Bytes: %s bytes (%5.2f%%)\r\n",
+            locale.toString((uint)totalBytesOfOctalCodes).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(),
+            ((float)totalBytesOfOctalCodes / (float)totalOutboundBytes) * AS_PERCENT);
+        mg_printf(connection, "             Total BitMasks Bytes: %s bytes (%5.2f%%)\r\n",
+            locale.toString((uint)totalBytesOfBitMasks).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(),
+            ((float)totalBytesOfBitMasks / (float)totalOutboundBytes) * AS_PERCENT);
+        mg_printf(connection, "                Total Color Bytes: %s bytes (%5.2f%%)\r\n",
+            locale.toString((uint)totalBytesOfColor).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(),
+            ((float)totalBytesOfColor / (float)totalOutboundBytes) * AS_PERCENT);
+
+        mg_printf(connection, "%s", "\r\n");
+        mg_printf(connection, "%s", "\r\n");
+
+        // display inbound packet stats
+        mg_printf(connection, "%s", "Particle Edit Statistics... [RESET]\r\n");
+        uint64_t averageTransitTimePerPacket = theServer->_ParticleSeverPacketProcessor->getAverageTransitTimePerPacket();
+        uint64_t averageProcessTimePerPacket = theServer->_particleServerPacketProcessor->getAverageProcessTimePerPacket();
+        uint64_t averageLockWaitTimePerPacket = theServer->_particleServerPacketProcessor->getAverageLockWaitTimePerPacket();
+        uint64_t averageProcessTimePerParticle = theServer->_particleServerPacketProcessor->getAverageProcessTimePerParticle();
+        uint64_t averageLockWaitTimePerParticle = theServer->_particleServerPacketProcessor->getAverageLockWaitTimePerParticle();
+        uint64_t totalParticlesProcessed = theServer->_particleServerPacketProcessor->getTotalParticlesProcessed();
+        uint64_t totalPacketsProcessed = theServer->_particleServerPacketProcessor->getTotalPacketsProcessed();
+
+        float averageParticlesPerPacket = totalPacketsProcessed == 0 ? 0 : totalParticlesProcessed / totalPacketsProcessed;
+
+        mg_printf(connection, "           Total Inbound Packets: %s packets\r\n",
+            locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+        mg_printf(connection, "            Total Inbound Particles: %s particles\r\n",
+            locale.toString((uint)totalParticlesProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+        mg_printf(connection, "   Average Inbound Particles/Packet: %f particles/packet\r\n", averageParticlesPerPacket);
+        mg_printf(connection, "     Average Transit Time/Packet: %s usecs\r\n", 
+            locale.toString((uint)averageTransitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+        mg_printf(connection, "     Average Process Time/Packet: %s usecs\r\n",
+            locale.toString((uint)averageProcessTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+        mg_printf(connection, "   Average Wait Lock Time/Packet: %s usecs\r\n", 
+            locale.toString((uint)averageLockWaitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+        mg_printf(connection, "      Average Process Time/Particle: %s usecs\r\n",
+            locale.toString((uint)averageProcessTimePerParticle).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+        mg_printf(connection, "    Average Wait Lock Time/Particle: %s usecs\r\n", 
+            locale.toString((uint)averageLockWaitTimePerParticle).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+
+
+        int senderNumber = 0;
+        NodeToSenderStatsMap& allSenderStats = theServer->_particleServerPacketProcessor->getSingleSenderStats();
+        for (NodeToSenderStatsMapIterator i = allSenderStats.begin(); i != allSenderStats.end(); i++) {
+            senderNumber++;
+            QUuid senderID = i->first;
+            SingleSenderStats& senderStats = i->second;
+
+            mg_printf(connection, "\r\n             Stats for sender %d uuid: %s\r\n", senderNumber, 
+                senderID.toString().toLocal8Bit().constData());
+
+            averageTransitTimePerPacket = senderStats.getAverageTransitTimePerPacket();
+            averageProcessTimePerPacket = senderStats.getAverageProcessTimePerPacket();
+            averageLockWaitTimePerPacket = senderStats.getAverageLockWaitTimePerPacket();
+            averageProcessTimePerParticle = senderStats.getAverageProcessTimePerParticle();
+            averageLockWaitTimePerParticle = senderStats.getAverageLockWaitTimePerParticle();
+            totalParticlesProcessed = senderStats.getTotalParticlesProcessed();
+            totalPacketsProcessed = senderStats.getTotalPacketsProcessed();
+
+            averageParticlesPerPacket = totalPacketsProcessed == 0 ? 0 : totalParticlesProcessed / totalPacketsProcessed;
+
+            mg_printf(connection, "               Total Inbound Packets: %s packets\r\n",
+                locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+            mg_printf(connection, "                Total Inbound Particles: %s particles\r\n",
+                locale.toString((uint)totalParticlesProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+            mg_printf(connection, "       Average Inbound Particles/Packet: %f particles/packet\r\n", averageParticlesPerPacket);
+            mg_printf(connection, "         Average Transit Time/Packet: %s usecs\r\n", 
+                locale.toString((uint)averageTransitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+            mg_printf(connection, "         Average Process Time/Packet: %s usecs\r\n",
+                locale.toString((uint)averageProcessTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+            mg_printf(connection, "       Average Wait Lock Time/Packet: %s usecs\r\n", 
+                locale.toString((uint)averageLockWaitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+            mg_printf(connection, "          Average Process Time/Particle: %s usecs\r\n",
+                locale.toString((uint)averageProcessTimePerParticle).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+            mg_printf(connection, "        Average Wait Lock Time/Particle: %s usecs\r\n", 
+                locale.toString((uint)averageLockWaitTimePerParticle).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
+
+        }
+
+
+        mg_printf(connection, "%s", "\r\n");
+        mg_printf(connection, "%s", "\r\n");
+
+        // display memory usage stats
+        mg_printf(connection, "%s", "Current Memory Usage Statistics\r\n");
+        mg_printf(connection, "\r\nParticleNode size... %ld bytes\r\n", sizeof(ParticleNode));
+        mg_printf(connection, "%s", "\r\n");
+
+        const char* memoryScaleLabel;
+        const float MEGABYTES = 1000000.f;
+        const float GIGABYTES = 1000000000.f;
+        float memoryScale;
+        if (ParticleNode::getTotalMemoryUsage() / MEGABYTES < 1000.0f) {
+            memoryScaleLabel = "MB";
+            memoryScale = MEGABYTES;
+        } else {
+            memoryScaleLabel = "GB";
+            memoryScale = GIGABYTES;
+        }
+
+        mg_printf(connection, "Particle Node Memory Usage:         %8.2f %s\r\n", 
+            ParticleNode::getParticleMemoryUsage() / memoryScale, memoryScaleLabel);
+        mg_printf(connection, "Octcode Memory Usage:            %8.2f %s\r\n", 
+            ParticleNode::getOctcodeMemoryUsage() / memoryScale, memoryScaleLabel);
+        mg_printf(connection, "External Children Memory Usage:  %8.2f %s\r\n", 
+            ParticleNode::getExternalChildrenMemoryUsage() / memoryScale, memoryScaleLabel);
+        mg_printf(connection, "%s", "                                 -----------\r\n");
+        mg_printf(connection, "                         Total:  %8.2f %s\r\n", 
+            ParticleNode::getTotalMemoryUsage() / memoryScale, memoryScaleLabel);
+
+        mg_printf(connection, "%s", "\r\n");
+        mg_printf(connection, "%s", "ParticleNode Children Population Statistics...\r\n");
+        checkSum = 0;
+        for (int i=0; i <= NUMBER_OF_CHILDREN; i++) {
+            checkSum += ParticleNode::getChildrenCount(i);
+            mg_printf(connection, "    Nodes with %d children:      %s nodes (%5.2f%%)\r\n", i, 
+                locale.toString((uint)ParticleNode::getChildrenCount(i)).rightJustified(16, ' ').toLocal8Bit().constData(),
+                ((float)ParticleNode::getChildrenCount(i) / (float)nodeCount) * AS_PERCENT);
+        }
+        mg_printf(connection, "%s", "                                ----------------------\r\n");
+        mg_printf(connection, "                    Total:      %s nodes\r\n", 
+            locale.toString((uint)checkSum).rightJustified(16, ' ').toLocal8Bit().constData());
+
+#ifdef BLENDED_UNION_CHILDREN
+        mg_printf(connection, "%s", "\r\n");
+        mg_printf(connection, "%s", "ParticleNode Children Encoding Statistics...\r\n");
+        
+        mg_printf(connection, "    Single or No Children:      %10.llu nodes (%5.2f%%)\r\n",
+            ParticleNode::getSingleChildrenCount(), ((float)ParticleNode::getSingleChildrenCount() / (float)nodeCount) * AS_PERCENT);
+        mg_printf(connection, "    Two Children as Offset:     %10.llu nodes (%5.2f%%)\r\n", 
+            ParticleNode::getTwoChildrenOffsetCount(), 
+            ((float)ParticleNode::getTwoChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT);
+        mg_printf(connection, "    Two Children as External:   %10.llu nodes (%5.2f%%)\r\n", 
+            ParticleNode::getTwoChildrenExternalCount(), 
+            ((float)ParticleNode::getTwoChildrenExternalCount() / (float)nodeCount) * AS_PERCENT);
+        mg_printf(connection, "    Three Children as Offset:   %10.llu nodes (%5.2f%%)\r\n", 
+            ParticleNode::getThreeChildrenOffsetCount(), 
+            ((float)ParticleNode::getThreeChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT);
+        mg_printf(connection, "    Three Children as External: %10.llu nodes (%5.2f%%)\r\n", 
+            ParticleNode::getThreeChildrenExternalCount(), 
+            ((float)ParticleNode::getThreeChildrenExternalCount() / (float)nodeCount) * AS_PERCENT);
+        mg_printf(connection, "    Children as External Array: %10.llu nodes (%5.2f%%)\r\n",
+            ParticleNode::getExternalChildrenCount(), 
+            ((float)ParticleNode::getExternalChildrenCount() / (float)nodeCount) * AS_PERCENT);
+
+        checkSum = ParticleNode::getSingleChildrenCount() +
+                            ParticleNode::getTwoChildrenOffsetCount() + ParticleNode::getTwoChildrenExternalCount() + 
+                            ParticleNode::getThreeChildrenOffsetCount() + ParticleNode::getThreeChildrenExternalCount() + 
+                            ParticleNode::getExternalChildrenCount();
+
+        mg_printf(connection, "%s", "                                ----------------\r\n");
+        mg_printf(connection, "                         Total: %10.llu nodes\r\n", checkSum);
+        mg_printf(connection, "                      Expected: %10.lu nodes\r\n", nodeCount);
+
+        mg_printf(connection, "%s", "\r\n");
+        mg_printf(connection, "%s", "In other news....\r\n");
+        mg_printf(connection, "could store 4 children internally:     %10.llu nodes\r\n",
+            ParticleNode::getCouldStoreFourChildrenInternally());
+        mg_printf(connection, "could NOT store 4 children internally: %10.llu nodes\r\n", 
+            ParticleNode::getCouldNotStoreFourChildrenInternally());
+#endif
+
+        mg_printf(connection, "%s", "\r\n");
+        mg_printf(connection, "%s", "\r\n");
+        mg_printf(connection, "%s", "
\r\n"); + + mg_printf(connection, "%s", "
"); + + return 1; + } else { + // have mongoose process this request from the document_root + return 0; + } +} + + +void ParticleSever::setArguments(int argc, char** argv) { + _argc = argc; + _argv = const_cast(argv); + + qDebug("ParticleSever::setArguments()\n"); + for (int i = 0; i < _argc; i++) { + qDebug("_argv[%d]=%s\n", i, _argv[i]); + } + +} + +void ParticleSever::parsePayload() { + + if (getNumPayloadBytes() > 0) { + QString config((const char*) _payload); + + // Now, parse the config + QStringList configList = config.split(" "); + + int argCount = configList.size() + 1; + + qDebug("ParticleSever::parsePayload()... argCount=%d\n",argCount); + + _parsedArgV = new char*[argCount]; + const char* dummy = "config-from-payload"; + _parsedArgV[0] = new char[strlen(dummy) + sizeof(char)]; + strcpy(_parsedArgV[0], dummy); + + for (int i = 1; i < argCount; i++) { + QString configItem = configList.at(i-1); + _parsedArgV[i] = new char[configItem.length() + sizeof(char)]; + strcpy(_parsedArgV[i], configItem.toLocal8Bit().constData()); + qDebug("ParticleSever::parsePayload()... _parsedArgV[%d]=%s\n", i, _parsedArgV[i]); + } + + setArguments(argCount, _parsedArgV); + } +} + +//int main(int argc, const char * argv[]) { +void ParticleSever::run() { + + const char PARTICLE_SERVER_LOGGING_TARGET_NAME[] = "particle-server"; + + // change the logging target name while this is running + Logging::setTargetName(PARTICLE_SERVER_LOGGING_TARGET_NAME); + + // Now would be a good time to parse our arguments, if we got them as assignment + if (getNumPayloadBytes() > 0) { + parsePayload(); + } + + qInstallMessageHandler(Logging::verboseMessageHandler); + + const char* STATUS_PORT = "--statusPort"; + const char* statusPort = getCmdOption(_argc, _argv, STATUS_PORT); + if (statusPort) { + int statusPortNumber = atoi(statusPort); + initMongoose(statusPortNumber); + } + + + const char* JURISDICTION_FILE = "--jurisdictionFile"; + const char* jurisdictionFile = getCmdOption(_argc, _argv, JURISDICTION_FILE); + if (jurisdictionFile) { + qDebug("jurisdictionFile=%s\n", jurisdictionFile); + + qDebug("about to readFromFile().... jurisdictionFile=%s\n", jurisdictionFile); + _jurisdiction = new JurisdictionMap(jurisdictionFile); + qDebug("after readFromFile().... jurisdictionFile=%s\n", jurisdictionFile); + } else { + const char* JURISDICTION_ROOT = "--jurisdictionRoot"; + const char* jurisdictionRoot = getCmdOption(_argc, _argv, JURISDICTION_ROOT); + if (jurisdictionRoot) { + qDebug("jurisdictionRoot=%s\n", jurisdictionRoot); + } + + const char* JURISDICTION_ENDNODES = "--jurisdictionEndNodes"; + const char* jurisdictionEndNodes = getCmdOption(_argc, _argv, JURISDICTION_ENDNODES); + if (jurisdictionEndNodes) { + qDebug("jurisdictionEndNodes=%s\n", jurisdictionEndNodes); + } + + if (jurisdictionRoot || jurisdictionEndNodes) { + _jurisdiction = new JurisdictionMap(jurisdictionRoot, jurisdictionEndNodes); + } + } + + // should we send environments? Default is yes, but this command line suppresses sending + const char* DUMP_PARTICLES_ON_MOVE = "--dumpParticlesOnMove"; + _dumpParticlesOnMove = cmdOptionExists(_argc, _argv, DUMP_PARTICLES_ON_MOVE); + qDebug("dumpParticlesOnMove=%s\n", debug::valueOf(_dumpParticlesOnMove)); + + // should we send environments? Default is yes, but this command line suppresses sending + const char* SEND_ENVIRONMENTS = "--sendEnvironments"; + bool dontSendEnvironments = !cmdOptionExists(_argc, _argv, SEND_ENVIRONMENTS); + if (dontSendEnvironments) { + qDebug("Sending environments suppressed...\n"); + _sendEnvironments = false; + } else { + // should we send environments? Default is yes, but this command line suppresses sending + const char* MINIMAL_ENVIRONMENT = "--minimalEnvironment"; + _sendMinimalEnvironment = cmdOptionExists(_argc, _argv, MINIMAL_ENVIRONMENT); + qDebug("Using Minimal Environment=%s\n", debug::valueOf(_sendMinimalEnvironment)); + } + qDebug("Sending environments=%s\n", debug::valueOf(_sendEnvironments)); + + NodeList* nodeList = NodeList::getInstance(); + nodeList->setOwnerType(NODE_TYPE_PARTICLE_SERVER); + + // we need to ask the DS about agents so we can ping/reply with them + const char nodeTypesOfInterest[] = { NODE_TYPE_AGENT, NODE_TYPE_ANIMATION_SERVER}; + nodeList->setNodeTypesOfInterest(nodeTypesOfInterest, sizeof(nodeTypesOfInterest)); + + setvbuf(stdout, NULL, _IOLBF, 0); + + // tell our NodeList about our desire to get notifications + nodeList->addHook(&_nodeWatcher); + nodeList->linkedDataCreateCallback = &attachParticleReceiverDataToNode; + + nodeList->startSilentNodeRemovalThread(); + srand((unsigned)time(0)); + + const char* DISPLAY_PARTICLE_STATS = "--displayParticleStats"; + _displayParticleStats = cmdOptionExists(_argc, _argv, DISPLAY_PARTICLE_STATS); + qDebug("displayParticleStats=%s\n", debug::valueOf(_displayParticleStats)); + + const char* VERBOSE_DEBUG = "--verboseDebug"; + _verboseDebug = cmdOptionExists(_argc, _argv, VERBOSE_DEBUG); + qDebug("verboseDebug=%s\n", debug::valueOf(_verboseDebug)); + + const char* DEBUG_PARTICLE_SENDING = "--debugParticleSending"; + _debugParticleSending = cmdOptionExists(_argc, _argv, DEBUG_PARTICLE_SENDING); + qDebug("debugParticleSending=%s\n", debug::valueOf(_debugParticleSending)); + + const char* DEBUG_PARTICLE_RECEIVING = "--debugParticleReceiving"; + _debugParticleReceiving = cmdOptionExists(_argc, _argv, DEBUG_PARTICLE_RECEIVING); + qDebug("debugParticleReceiving=%s\n", debug::valueOf(_debugParticleReceiving)); + + const char* WANT_ANIMATION_DEBUG = "--shouldShowAnimationDebug"; + _shouldShowAnimationDebug = cmdOptionExists(_argc, _argv, WANT_ANIMATION_DEBUG); + qDebug("shouldShowAnimationDebug=%s\n", debug::valueOf(_shouldShowAnimationDebug)); + + // By default we will particle persist, if you want to disable this, then pass in this parameter + const char* NO_PARTICLE_PERSIST = "--NoParticlePersist"; + if (cmdOptionExists(_argc, _argv, NO_PARTICLE_PERSIST)) { + _wantParticlePersist = false; + } + qDebug("wantParticlePersist=%s\n", debug::valueOf(_wantParticlePersist)); + + // if we want Particle Persistence, set up the local file and persist thread + if (_wantParticlePersist) { + + // Check to see if the user passed in a command line option for setting packet send rate + const char* PARTICLES_PERSIST_FILENAME = "--particlesPersistFilename"; + const char* particlesPersistFilenameParameter = getCmdOption(_argc, _argv, PARTICLES_PERSIST_FILENAME); + if (particlesPersistFilenameParameter) { + strcpy(_particlePersistFilename, particlesPersistFilenameParameter); + } else { + //strcpy(particlePersistFilename, _wantLocalDomain ? LOCAL_PARTICLES_PERSIST_FILE : PARTICLES_PERSIST_FILE); + strcpy(_particlePersistFilename, LOCAL_PARTICLES_PERSIST_FILE); + } + + qDebug("particlePersistFilename=%s\n", _particlePersistFilename); + + // now set up ParticlePersistThread + _particlePersistThread = new ParticlePersistThread(&_serverTree, _particlePersistFilename); + if (_particlePersistThread) { + _particlePersistThread->initialize(true); + } + } + + // Check to see if the user passed in a command line option for loading an old style local + // Particle File. If so, load it now. This is not the same as a particle persist file + const char* INPUT_FILE = "-i"; + const char* particlesFilename = getCmdOption(_argc, _argv, INPUT_FILE); + if (particlesFilename) { + _serverTree.readFromSVOFile(particlesFilename); + } + + // Check to see if the user passed in a command line option for setting packet send rate + const char* PACKETS_PER_SECOND = "--packetsPerSecond"; + const char* packetsPerSecond = getCmdOption(_argc, _argv, PACKETS_PER_SECOND); + if (packetsPerSecond) { + _packetsPerClientPerInterval = atoi(packetsPerSecond) / INTERVALS_PER_SECOND; + if (_packetsPerClientPerInterval < 1) { + _packetsPerClientPerInterval = 1; + } + qDebug("packetsPerSecond=%s PACKETS_PER_CLIENT_PER_INTERVAL=%d\n", packetsPerSecond, _packetsPerClientPerInterval); + } + + sockaddr senderAddress; + + unsigned char* packetData = new unsigned char[MAX_PACKET_SIZE]; + ssize_t packetLength; + + timeval lastDomainServerCheckIn = {}; + + // set up our jurisdiction broadcaster... + _jurisdictionSender = new JurisdictionSender(_jurisdiction); + if (_jurisdictionSender) { + _jurisdictionSender->initialize(true); + } + + // set up our ParticleSeverPacketProcessor + _particleServerPacketProcessor = new ParticleSeverPacketProcessor(this); + if (_particleServerPacketProcessor) { + _particleServerPacketProcessor->initialize(true); + } + + // Convert now to tm struct for local timezone + tm* localtm = localtime(&_started); + const int MAX_TIME_LENGTH = 128; + char localBuffer[MAX_TIME_LENGTH] = { 0 }; + char utcBuffer[MAX_TIME_LENGTH] = { 0 }; + strftime(localBuffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", localtm); + // Convert now to tm struct for UTC + tm* gmtm = gmtime(&_started); + if (gmtm != NULL) { + strftime(utcBuffer, MAX_TIME_LENGTH, " [%m/%d/%Y %X UTC]", gmtm); + } + qDebug() << "Now running... started at: " << localBuffer << utcBuffer << "\n"; + + + // loop to send to nodes requesting data + while (true) { + // check for >= in case one gets past the goalie + if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { + qDebug() << "Exit loop... getInstance()->getNumNoReplyDomainCheckIns() >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS\n"; + break; + } + + // send a check in packet to the domain server if DOMAIN_SERVER_CHECK_IN_USECS has elapsed + if (usecTimestampNow() - usecTimestamp(&lastDomainServerCheckIn) >= DOMAIN_SERVER_CHECK_IN_USECS) { + gettimeofday(&lastDomainServerCheckIn, NULL); + NodeList::getInstance()->sendDomainServerCheckIn(); + } + + // ping our inactive nodes to punch holes with them + nodeList->possiblyPingInactiveNodes(); + + if (nodeList->getNodeSocket()->receive(&senderAddress, packetData, &packetLength) && + packetVersionMatch(packetData)) { + + int numBytesPacketHeader = numBytesForPacketHeader(packetData); + + if (packetData[0] == PACKET_TYPE_PARTICLE_QUERY) { + bool debug = false; + if (debug) { + qDebug("Got PACKET_TYPE_PARTICLE_QUERY at %llu.\n", usecTimestampNow()); + } + + // If we got a PACKET_TYPE_PARTICLE_QUERY, then we're talking to an NODE_TYPE_AVATAR, and we + // need to make sure we have it in our nodeList. + QUuid nodeUUID = QUuid::fromRfc4122(QByteArray((char*)packetData + numBytesPacketHeader, + NUM_BYTES_RFC4122_UUID)); + + Node* node = nodeList->nodeWithUUID(nodeUUID); + + if (node) { + nodeList->updateNodeWithData(node, &senderAddress, packetData, packetLength); + if (!node->getActiveSocket()) { + // we don't have an active socket for this node, but they're talking to us + // this means they've heard from us and can reply, let's assume public is active + node->activatePublicSocket(); + } + ParticleReceiverData* nodeData = (ParticleReceiverData*) node->getLinkedData(); + if (nodeData && !nodeData->isParticleSendThreadInitalized()) { + nodeData->initializeParticleSendThread(this); + } + } + } else if (packetData[0] == PACKET_TYPE_PARTICLE_JURISDICTION_REQUEST) { + if (_jurisdictionSender) { + _jurisdictionSender->queueReceivedPacket(senderAddress, packetData, packetLength); + } + } else if (_particleServerPacketProcessor && + (packetData[0] == PACKET_TYPE_SET_VOXEL + || packetData[0] == PACKET_TYPE_SET_PARTICLE_DESTRUCTIVE + || packetData[0] == PACKET_TYPE_ERASE_VOXEL)) { + + + const char* messageName; + switch (packetData[0]) { + case PACKET_TYPE_SET_VOXEL: + messageName = "PACKET_TYPE_SET_VOXEL"; + break; + case PACKET_TYPE_SET_PARTICLE_DESTRUCTIVE: + messageName = "PACKET_TYPE_SET_PARTICLE_DESTRUCTIVE"; + break; + case PACKET_TYPE_ERASE_VOXEL: + messageName = "PACKET_TYPE_ERASE_VOXEL"; + break; + } + _particleServerPacketProcessor->queueReceivedPacket(senderAddress, packetData, packetLength); + } else { + // let processNodeData handle it. + NodeList::getInstance()->processNodeData(&senderAddress, packetData, packetLength); + } + } + } + + // call NodeList::clear() so that all of our node specific objects, including our sending threads, are + // properly shutdown and cleaned up. + NodeList::getInstance()->clear(); + + if (_jurisdictionSender) { + _jurisdictionSender->terminate(); + delete _jurisdictionSender; + } + + if (_particleServerPacketProcessor) { + _particleServerPacketProcessor->terminate(); + delete _particleServerPacketProcessor; + } + + if (_particlePersistThread) { + _particlePersistThread->terminate(); + delete _particlePersistThread; + } + + // tell our NodeList we're done with notifications + nodeList->removeHook(&_nodeWatcher); + + delete _jurisdiction; + _jurisdiction = NULL; + + qDebug() << "ParticleSever::run()... DONE\n"; +} + + +void ParticleSever::nodeAdded(Node* node) { + // do nothing +} + +void ParticleSever::nodeKilled(Node* node) { + // Use this to cleanup our node + if (node->getType() == NODE_TYPE_AGENT) { + ParticleReceiverData* nodeData = (ParticleReceiverData*)node->getLinkedData(); + if (nodeData) { + node->setLinkedData(NULL); + delete nodeData; + } + } +}; diff --git a/libraries/particle-server/src/ParticleServer.h b/libraries/particle-server/src/ParticleServer.h new file mode 100644 index 0000000000..d33bcd8eaa --- /dev/null +++ b/libraries/particle-server/src/ParticleServer.h @@ -0,0 +1,91 @@ +// +// ParticleServer.h +// particle-server +// +// Created by Brad Hefta-Gaub on 12/2/13 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// + +#ifndef __particle_server__ParticleServer__ +#define __particle_server__ParticleServer__ + +#include +#include +#include + +#include + +#include "civetweb.h" + +#include "ParticlePersistThread.h" +#include "ParticleSendThread.h" +#include "ParticleServerConsts.h" +#include "ParticleServerPacketProcessor.h" + +/// Handles assignments of type ParticleServer - sending particles to various clients. +class ParticleServer : public Assignment { +public: + ParticleServer(const unsigned char* dataBuffer, int numBytes); + + ~ParticleServer(); + + /// runs the particle server assignment + void run(); + + /// allows setting of run arguments + void setArguments(int argc, char** argv); + + bool wantsDebugParticleSending() const { return _debugParticleSending; } + bool wantsDebugParticleReceiving() const { return _debugParticleReceiving; } + bool wantsVerboseDebug() const { return _verboseDebug; } + bool wantShowAnimationDebug() const { return _shouldShowAnimationDebug; } + bool wantDumpParticlesOnMove() const { return _dumpParticlesOnMove; } + bool wantDisplayParticleStats() const { return _displayParticleStats; } + + ParticleTree& getServerTree() { return _serverTree; } + JurisdictionMap* getJurisdiction() { return _jurisdiction; } + + int getPacketsPerClientPerInterval() const { return _packetsPerClientPerInterval; } + + static ParticleServer* GetInstance() { return _theInstance; } + + bool isInitialLoadComplete() const { return (_particlePersistThread) ? _particlePersistThread->isInitialLoadComplete() : true; } + time_t* getLoadCompleted() { return (_particlePersistThread) ? _particlePersistThread->getLoadCompleted() : NULL; } + uint64_t getLoadElapsedTime() const { return (_particlePersistThread) ? _particlePersistThread->getLoadElapsedTime() : 0; } + +private: + int _argc; + const char** _argv; + char** _parsedArgV; + + char _particlePersistFilename[MAX_FILENAME_LENGTH]; + int _packetsPerClientPerInterval; + ParticleTree _serverTree; // this IS a reaveraging tree + bool _wantParticlePersist; + bool _wantLocalDomain; + bool _debugParticleSending; + bool _shouldShowAnimationDebug; + bool _displayParticleStats; + bool _debugParticleReceiving; + bool _dumpParticlesOnMove; + bool _verboseDebug; + JurisdictionMap* _jurisdiction; + JurisdictionSender* _jurisdictionSender; + ParticleServerPacketProcessor* _particleServerPacketProcessor; + ParticlePersistThread* _particlePersistThread; + + NodeWatcher _nodeWatcher; // used to cleanup AGENT data when agents are killed + + void parsePayload(); + + void initMongoose(int port); + + static int civetwebRequestHandler(struct mg_connection *connection); + static ParticleServer* _theInstance; + + time_t _started; + uint64_t _startedUSecs; +}; + +#endif // __particle_server__ParticleServer__ diff --git a/libraries/particle-server/src/ParticleServerConsts.h b/libraries/particle-server/src/ParticleServerConsts.h new file mode 100644 index 0000000000..5940674443 --- /dev/null +++ b/libraries/particle-server/src/ParticleServerConsts.h @@ -0,0 +1,28 @@ +// ParticleServerConsts.h +// particle-server +// +// Created by Brad Hefta-Gaub on 12/2/13 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// + +#ifndef __particle_server__ParticleServerConsts__ +#define __particle_server__ParticleServerConsts__ + +#include +#include // for MAX_PACKET_SIZE +#include +#include + +#include "ParticleServerPacketProcessor.h" + +const int MAX_FILENAME_LENGTH = 1024; +const int INTERVALS_PER_SECOND = 60; +const int PARTICLE_SEND_INTERVAL_USECS = (1000 * 1000)/INTERVALS_PER_SECOND; +const int SENDING_TIME_TO_SPARE = 5 * 1000; // usec of sending interval to spare for calculating particles +const int ENVIRONMENT_SEND_INTERVAL_USECS = 1000000; + +extern const char* LOCAL_PARTICLES_PERSIST_FILE; +extern const char* PARTICLES_PERSIST_FILE; + +#endif // __particle_server__ParticleServerConsts__ diff --git a/libraries/particle-server/src/ParticleServerPacketProcessor.cpp b/libraries/particle-server/src/ParticleServerPacketProcessor.cpp new file mode 100644 index 0000000000..9d0b771c8e --- /dev/null +++ b/libraries/particle-server/src/ParticleServerPacketProcessor.cpp @@ -0,0 +1,222 @@ +// +// ParticleServerPacketProcessor.cpp +// particle-server +// +// Created by Brad Hefta-Gaub on 12/2/13 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// Threaded or non-threaded network packet processor for the particle-server +// + +#include +#include + +#include "ParticleServer.h" +#include "ParticleServerConsts.h" +#include "ParticleServerPacketProcessor.h" + +static QUuid DEFAULT_NODE_ID_REF; + +ParticleServerPacketProcessor::ParticleServerPacketProcessor(ParticleServer* myServer) : + _myServer(myServer), + _receivedPacketCount(0), + _totalTransitTime(0), + _totalProcessTime(0), + _totalLockWaitTime(0), + _totalParticlesInPacket(0), + _totalPackets(0) +{ +} + +void ParticleServerPacketProcessor::resetStats() { + _totalTransitTime = 0; + _totalProcessTime = 0; + _totalLockWaitTime = 0; + _totalParticlesInPacket = 0; + _totalPackets = 0; + + _singleSenderStats.clear(); +} + + +void ParticleServerPacketProcessor::processPacket(sockaddr& senderAddress, unsigned char* packetData, ssize_t packetLength) { + + bool debugProcessPacket = _myServer->wantsVerboseDebug(); + + if (debugProcessPacket) { + printf("ParticleServerPacketProcessor::processPacket() packetData=%p packetLength=%ld\n", packetData, packetLength); + } + + int numBytesPacketHeader = numBytesForPacketHeader(packetData); + + if (packetData[0] == PACKET_TYPE_SET_PARTICLE || packetData[0] == PACKET_TYPE_SET_PARTICLE_DESTRUCTIVE) { + bool destructive = (packetData[0] == PACKET_TYPE_SET_PARTICLE_DESTRUCTIVE); + PerformanceWarning warn(_myServer->wantShowAnimationDebug(), + destructive ? "PACKET_TYPE_SET_PARTICLE_DESTRUCTIVE" : "PACKET_TYPE_SET_PARTICLE", + _myServer->wantShowAnimationDebug()); + + _receivedPacketCount++; + + unsigned short int sequence = (*((unsigned short int*)(packetData + numBytesPacketHeader))); + uint64_t sentAt = (*((uint64_t*)(packetData + numBytesPacketHeader + sizeof(sequence)))); + uint64_t arrivedAt = usecTimestampNow(); + uint64_t transitTime = arrivedAt - sentAt; + int particlesInPacket = 0; + uint64_t processTime = 0; + uint64_t lockWaitTime = 0; + + if (_myServer->wantShowAnimationDebug() || _myServer->wantsDebugParticleReceiving()) { + printf("PROCESSING THREAD: got %s - %d command from client receivedBytes=%ld sequence=%d transitTime=%llu usecs\n", + destructive ? "PACKET_TYPE_SET_PARTICLE_DESTRUCTIVE" : "PACKET_TYPE_SET_PARTICLE", + _receivedPacketCount, packetLength, sequence, transitTime); + } + int atByte = numBytesPacketHeader + sizeof(sequence) + sizeof(sentAt); + unsigned char* particleData = (unsigned char*)&packetData[atByte]; + while (atByte < packetLength) { + int maxSize = packetLength - atByte; + + if (debugProcessPacket) { + printf("ParticleServerPacketProcessor::processPacket() %s packetData=%p packetLength=%ld particleData=%p atByte=%d maxSize=%d\n", + destructive ? "PACKET_TYPE_SET_PARTICLE_DESTRUCTIVE" : "PACKET_TYPE_SET_PARTICLE", + packetData, packetLength, particleData, atByte, maxSize); + } + + int octets = numberOfThreeBitSectionsInCode(particleData, maxSize); + + if (octets == OVERFLOWED_OCTCODE_BUFFER) { + printf("WARNING! Got particle edit record that would overflow buffer in numberOfThreeBitSectionsInCode(), "); + printf("bailing processing of packet!\n"); + break; + } + + const int COLOR_SIZE_IN_BYTES = 3; + int particleDataSize = bytesRequiredForCodeLength(octets) + COLOR_SIZE_IN_BYTES; + int particleCodeSize = bytesRequiredForCodeLength(octets); + + if (atByte + particleDataSize <= packetLength) { + if (_myServer->wantShowAnimationDebug()) { + int red = particleData[particleCodeSize + RED_INDEX]; + int green = particleData[particleCodeSize + GREEN_INDEX]; + int blue = particleData[particleCodeSize + BLUE_INDEX]; + + float* vertices = firstVertexForCode(particleData); + printf("inserting particle: %f,%f,%f r=%d,g=%d,b=%d\n", vertices[0], vertices[1], vertices[2], red, green, blue); + delete[] vertices; + } + + uint64_t startLock = usecTimestampNow(); + _myServer->getServerTree().lockForWrite(); + uint64_t startProcess = usecTimestampNow(); + _myServer->getServerTree().readCodeColorBufferToTree(particleData, destructive); + _myServer->getServerTree().unlock(); + uint64_t endProcess = usecTimestampNow(); + + particlesInPacket++; + + uint64_t thisProcessTime = endProcess - startProcess; + uint64_t thisLockWaitTime = startProcess - startLock; + + processTime += thisProcessTime; + lockWaitTime += thisLockWaitTime; + + // skip to next particle edit record in the packet + particleData += particleDataSize; + atByte += particleDataSize; + } else { + printf("WARNING! Got particle edit record that would overflow buffer, bailing processing of packet!\n"); + break; + } + } + + if (debugProcessPacket) { + printf("ParticleServerPacketProcessor::processPacket() DONE LOOPING FOR %s packetData=%p packetLength=%ld particleData=%p atByte=%d\n", + destructive ? "PACKET_TYPE_SET_PARTICLE_DESTRUCTIVE" : "PACKET_TYPE_SET_PARTICLE", + packetData, packetLength, particleData, atByte); + } + + // Make sure our Node and NodeList knows we've heard from this node. + Node* senderNode = NodeList::getInstance()->nodeWithAddress(&senderAddress); + QUuid& nodeUUID = DEFAULT_NODE_ID_REF; + if (senderNode) { + senderNode->setLastHeardMicrostamp(usecTimestampNow()); + nodeUUID = senderNode->getUUID(); + if (debugProcessPacket) { + qDebug() << "sender has uuid=" << nodeUUID << "\n"; + } + } else { + if (debugProcessPacket) { + qDebug() << "sender has no known nodeUUID.\n"; + } + } + trackInboundPackets(nodeUUID, sequence, transitTime, particlesInPacket, processTime, lockWaitTime); + + } else if (packetData[0] == PACKET_TYPE_ERASE_PARTICLE) { + + _receivedPacketCount++; + + unsigned short int sequence = (*((unsigned short int*)(packetData + numBytesPacketHeader))); + uint64_t sentAt = (*((uint64_t*)(packetData + numBytesPacketHeader + sizeof(sequence)))); + uint64_t arrivedAt = usecTimestampNow(); + uint64_t transitTime = arrivedAt - sentAt; + + if (_myServer->wantShowAnimationDebug() || _myServer->wantsDebugParticleReceiving()) { + printf("PROCESSING THREAD: got PACKET_TYPE_ERASE_PARTICLE - %d command from client receivedBytes=%ld sequence=%d transitTime=%llu usecs\n", + _receivedPacketCount, packetLength, sequence, transitTime); + } + + // Send these bits off to the ParticleTree class to process them + _myServer->getServerTree().lockForWrite(); + _myServer->getServerTree().processRemoveParticleBitstream((unsigned char*)packetData, packetLength); + _myServer->getServerTree().unlock(); + + // Make sure our Node and NodeList knows we've heard from this node. + Node* node = NodeList::getInstance()->nodeWithAddress(&senderAddress); + if (node) { + node->setLastHeardMicrostamp(usecTimestampNow()); + } + } else { + printf("unknown packet ignored... packetData[0]=%c\n", packetData[0]); + } +} + +void ParticleServerPacketProcessor::trackInboundPackets(const QUuid& nodeUUID, int sequence, uint64_t transitTime, + int particlesInPacket, uint64_t processTime, uint64_t lockWaitTime) { + + _totalTransitTime += transitTime; + _totalProcessTime += processTime; + _totalLockWaitTime += lockWaitTime; + _totalParticlesInPacket += particlesInPacket; + _totalPackets++; + + // find the individual senders stats and track them there too... + // see if this is the first we've heard of this node... + if (_singleSenderStats.find(nodeUUID) == _singleSenderStats.end()) { + SingleSenderStats stats; + + stats._totalTransitTime += transitTime; + stats._totalProcessTime += processTime; + stats._totalLockWaitTime += lockWaitTime; + stats._totalParticlesInPacket += particlesInPacket; + stats._totalPackets++; + + _singleSenderStats[nodeUUID] = stats; + } else { + SingleSenderStats& stats = _singleSenderStats[nodeUUID]; + stats._totalTransitTime += transitTime; + stats._totalProcessTime += processTime; + stats._totalLockWaitTime += lockWaitTime; + stats._totalParticlesInPacket += particlesInPacket; + stats._totalPackets++; + } +} + + +SingleSenderStats::SingleSenderStats() { + _totalTransitTime = 0; + _totalProcessTime = 0; + _totalLockWaitTime = 0; + _totalParticlesInPacket = 0; + _totalPackets = 0; +} + + diff --git a/libraries/particle-server/src/ParticleServerPacketProcessor.h b/libraries/particle-server/src/ParticleServerPacketProcessor.h new file mode 100644 index 0000000000..1b4c2b87b1 --- /dev/null +++ b/libraries/particle-server/src/ParticleServerPacketProcessor.h @@ -0,0 +1,83 @@ +// +// ParticleServerPacketProcessor.h +// particle-server +// +// Created by Brad Hefta-Gaub on 8/21/13 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// Threaded or non-threaded network packet processor for the particle-server +// + +#ifndef __particle_server__ParticleServerPacketProcessor__ +#define __particle_server__ParticleServerPacketProcessor__ + +#include + +#include +class ParticleServer; + +class SingleSenderStats { +public: + SingleSenderStats(); + + uint64_t getAverageTransitTimePerPacket() const { return _totalPackets == 0 ? 0 : _totalTransitTime / _totalPackets; } + uint64_t getAverageProcessTimePerPacket() const { return _totalPackets == 0 ? 0 : _totalProcessTime / _totalPackets; } + uint64_t getAverageLockWaitTimePerPacket() const { return _totalPackets == 0 ? 0 : _totalLockWaitTime / _totalPackets; } + uint64_t getTotalParticlesProcessed() const { return _totalParticlesInPacket; } + uint64_t getTotalPacketsProcessed() const { return _totalPackets; } + uint64_t getAverageProcessTimePerParticle() const + { return _totalParticlesInPacket == 0 ? 0 : _totalProcessTime / _totalParticlesInPacket; } + uint64_t getAverageLockWaitTimePerParticle() const + { return _totalParticlesInPacket == 0 ? 0 : _totalLockWaitTime / _totalParticlesInPacket; } + + uint64_t _totalTransitTime; + uint64_t _totalProcessTime; + uint64_t _totalLockWaitTime; + uint64_t _totalParticlesInPacket; + uint64_t _totalPackets; +}; + +typedef std::map NodeToSenderStatsMap; +typedef std::map::iterator NodeToSenderStatsMapIterator; + + +/// Handles processing of incoming network packets for the particle-server. As with other ReceivedPacketProcessor classes +/// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket() +class ParticleServerPacketProcessor : public ReceivedPacketProcessor { + +public: + ParticleServerPacketProcessor(ParticleServer* myServer); + + uint64_t getAverageTransitTimePerPacket() const { return _totalPackets == 0 ? 0 : _totalTransitTime / _totalPackets; } + uint64_t getAverageProcessTimePerPacket() const { return _totalPackets == 0 ? 0 : _totalProcessTime / _totalPackets; } + uint64_t getAverageLockWaitTimePerPacket() const { return _totalPackets == 0 ? 0 : _totalLockWaitTime / _totalPackets; } + uint64_t getTotalParticlesProcessed() const { return _totalParticlesInPacket; } + uint64_t getTotalPacketsProcessed() const { return _totalPackets; } + uint64_t getAverageProcessTimePerParticle() const + { return _totalParticlesInPacket == 0 ? 0 : _totalProcessTime / _totalParticlesInPacket; } + uint64_t getAverageLockWaitTimePerParticle() const + { return _totalParticlesInPacket == 0 ? 0 : _totalLockWaitTime / _totalParticlesInPacket; } + + void resetStats(); + + NodeToSenderStatsMap& getSingleSenderStats() { return _singleSenderStats; } + +protected: + virtual void processPacket(sockaddr& senderAddress, unsigned char* packetData, ssize_t packetLength); + +private: + void trackInboundPackets(const QUuid& nodeUUID, int sequence, uint64_t transitTime, + int particlesInPacket, uint64_t processTime, uint64_t lockWaitTime); + + ParticleServer* _myServer; + int _receivedPacketCount; + + uint64_t _totalTransitTime; + uint64_t _totalProcessTime; + uint64_t _totalLockWaitTime; + uint64_t _totalParticlesInPacket; + uint64_t _totalPackets; + + NodeToSenderStatsMap _singleSenderStats; +}; +#endif // __particle_server__ParticleServerPacketProcessor__ diff --git a/libraries/particles/CMakeLists.txt b/libraries/particles/CMakeLists.txt new file mode 100644 index 0000000000..3bbff3b433 --- /dev/null +++ b/libraries/particles/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 2.8) + +set(ROOT_DIR ../..) +set(MACRO_DIR ${ROOT_DIR}/cmake/macros) + +# setup for find modules +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/") + +set(TARGET_NAME particles) + +find_package(Qt5Widgets REQUIRED) + +include(${MACRO_DIR}/SetupHifiLibrary.cmake) +setup_hifi_library(${TARGET_NAME}) + +qt5_use_modules(${TARGET_NAME} Widgets) + +include(${MACRO_DIR}/IncludeGLM.cmake) +include_glm(${TARGET_NAME} ${ROOT_DIR}) + +include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) +link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) + +# link ZLIB +find_package(ZLIB) +include_directories(${ZLIB_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) diff --git a/libraries/particles/src/ParticleTree.cpp b/libraries/particles/src/ParticleTree.cpp new file mode 100644 index 0000000000..a8215ba4cf --- /dev/null +++ b/libraries/particles/src/ParticleTree.cpp @@ -0,0 +1 @@ +// ParticleTree.cpp \ No newline at end of file diff --git a/libraries/voxels/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp similarity index 99% rename from libraries/voxels/src/GeometryUtil.cpp rename to libraries/shared/src/GeometryUtil.cpp index 3360f68cfa..b632cd4bb6 100644 --- a/libraries/voxels/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -9,8 +9,7 @@ #include -#include - +#include "SharedUtil.h" #include "GeometryUtil.h" glm::vec3 computeVectorFromPointToSegment(const glm::vec3& point, const glm::vec3& start, const glm::vec3& end) { diff --git a/libraries/voxels/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h similarity index 100% rename from libraries/voxels/src/GeometryUtil.h rename to libraries/shared/src/GeometryUtil.h diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index a915db2506..2476929c28 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -323,23 +323,24 @@ bool encodeVoxelEditMessageDetails(unsigned char command, int voxelCount, VoxelD } -////////////////////////////////////////////////////////////////////////////////////////// -// Function: pointToVoxel() -// Description: Given a universal point with location x,y,z this will return the voxel -// voxel code corresponding to the closest voxel which encloses a cube with -// lower corners at x,y,z, having side of length S. -// The input values x,y,z range 0.0 <= v < 1.0 -// TO DO: This code is not very DRY. It should be cleaned up to be DRYer. -// IMPORTANT: The voxel is returned to you a buffer which you MUST delete when you are -// done with it. -// Usage: -// unsigned char* voxelData = pointToVoxel(x,y,z,s,red,green,blue); -// tree->readCodeColorBufferToTree(voxelData); -// delete voxelData; -// -// Complaints: Brad :) +unsigned char* pointToOctalCode(float x, float y, float z, float s) { + return pointToVoxel(x, y, z, s); +} + +/// Given a universal point with location x,y,z this will return the voxel +/// voxel code corresponding to the closest voxel which encloses a cube with +/// lower corners at x,y,z, having side of length S. +/// The input values x,y,z range 0.0 <= v < 1.0 +/// IMPORTANT: The voxel is returned to you a buffer which you MUST delete when you are +/// done with it. unsigned char* pointToVoxel(float x, float y, float z, float s, unsigned char r, unsigned char g, unsigned char b ) { + // special case for size 1, the root node + if (s >= 1.0) { + unsigned char* voxelOut = new unsigned char; + *voxelOut = 0; + } + float xTest, yTest, zTest, sTest; xTest = yTest = zTest = sTest = 0.5f; diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index e0d87ea4a8..52623530a0 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -24,7 +24,12 @@ #include #endif -typedef unsigned char rgbColor[3]; +const int BYTES_PER_COLOR = 3; +const int BYTES_PER_FLAGS = 1; +typedef unsigned char rgbColor[BYTES_PER_COLOR]; +typedef unsigned char colorPart; +typedef unsigned char nodeColor[BYTES_PER_COLOR + BYTES_PER_FLAGS]; +typedef unsigned char rgbColor[BYTES_PER_COLOR]; static const float ZERO = 0.0f; static const float ONE = 1.0f; @@ -86,6 +91,7 @@ struct VoxelDetail { }; unsigned char* pointToVoxel(float x, float y, float z, float s, unsigned char r = 0, unsigned char g = 0, unsigned char b = 0); +unsigned char* pointToOctalCode(float x, float y, float z, float s); // Creates a full Voxel edit message, including command header, sequence, and details bool createVoxelEditMessage(unsigned char command, short int sequence, diff --git a/libraries/voxel-server-library/CMakeLists.txt b/libraries/voxel-server-library/CMakeLists.txt index 2239731f3c..a44fffcc7c 100644 --- a/libraries/voxel-server-library/CMakeLists.txt +++ b/libraries/voxel-server-library/CMakeLists.txt @@ -24,9 +24,6 @@ qt5_use_modules(${TARGET_NAME} Widgets) include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} ${ROOT_DIR}) -include(${MACRO_DIR}/LinkHifiLibrary.cmake) -link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) - # link ZLIB find_package(ZLIB) include_directories(${ZLIB_INCLUDE_DIRS}) @@ -36,6 +33,9 @@ target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) +# link in the hifi octree library +link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) + # link in the hifi voxels library link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR}) diff --git a/libraries/voxel-server-library/src/VoxelNodeData.cpp b/libraries/voxel-server-library/src/VoxelNodeData.cpp index b7dec1053b..a4c4bfebe2 100644 --- a/libraries/voxel-server-library/src/VoxelNodeData.cpp +++ b/libraries/voxel-server-library/src/VoxelNodeData.cpp @@ -26,7 +26,7 @@ VoxelNodeData::VoxelNodeData(Node* owningNode) : _currentPacketIsCompressed(false), _voxelSendThread(NULL), _lastClientBoundaryLevelAdjust(0), - _lastClientVoxelSizeScale(DEFAULT_VOXEL_SIZE_SCALE), + _lastClientVoxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE), _lodChanged(false), _lodInitialized(false) { @@ -196,13 +196,13 @@ bool VoxelNodeData::updateCurrentViewFrustum() { _lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust(); _lodChanged = true; } - if (_lastClientVoxelSizeScale != getVoxelSizeScale()) { - _lastClientVoxelSizeScale = getVoxelSizeScale(); + if (_lastClientVoxelSizeScale != getOctreeSizeScale()) { + _lastClientVoxelSizeScale = getOctreeSizeScale(); _lodChanged = true; } } else { _lodInitialized = true; - _lastClientVoxelSizeScale = getVoxelSizeScale(); + _lastClientVoxelSizeScale = getOctreeSizeScale(); _lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust(); _lodChanged = false; } @@ -254,9 +254,9 @@ bool VoxelNodeData::moveShouldDump() const { void VoxelNodeData::dumpOutOfView() { int stillInView = 0; int outOfView = 0; - VoxelNodeBag tempBag; + OctreeElementBag tempBag; while (!nodeBag.isEmpty()) { - VoxelNode* node = nodeBag.extract(); + OctreeElement* node = nodeBag.extract(); if (node->isInView(_currentViewFrustum)) { tempBag.insert(node); stillInView++; @@ -266,7 +266,7 @@ void VoxelNodeData::dumpOutOfView() { } if (stillInView > 0) { while (!tempBag.isEmpty()) { - VoxelNode* node = tempBag.extract(); + OctreeElement* node = tempBag.extract(); if (node->isInView(_currentViewFrustum)) { nodeBag.insert(node); } diff --git a/libraries/voxel-server-library/src/VoxelNodeData.h b/libraries/voxel-server-library/src/VoxelNodeData.h index bd9e3eef16..7a39fb4362 100644 --- a/libraries/voxel-server-library/src/VoxelNodeData.h +++ b/libraries/voxel-server-library/src/VoxelNodeData.h @@ -16,7 +16,7 @@ #include #include -#include +#include #include class VoxelSendThread; @@ -46,7 +46,7 @@ public: int getMaxLevelReached() const { return _maxLevelReachedInLastSearch; } void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; } - VoxelNodeBag nodeBag; + OctreeElementBag nodeBag; CoverageMap map; ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; } diff --git a/libraries/voxel-server-library/src/VoxelPersistThread.cpp b/libraries/voxel-server-library/src/VoxelPersistThread.cpp index 6a9e5a53d5..6f0d8d6b1c 100644 --- a/libraries/voxel-server-library/src/VoxelPersistThread.cpp +++ b/libraries/voxel-server-library/src/VoxelPersistThread.cpp @@ -46,18 +46,18 @@ bool VoxelPersistThread::process() { _tree->clearDirtyBit(); // the tree is clean since we just loaded it qDebug("DONE loading voxels from file... fileRead=%s\n", debug::valueOf(persistantFileRead)); - unsigned long nodeCount = VoxelNode::getNodeCount(); - unsigned long internalNodeCount = VoxelNode::getInternalNodeCount(); - unsigned long leafNodeCount = VoxelNode::getLeafNodeCount(); + unsigned long nodeCount = OctreeElement::getNodeCount(); + unsigned long internalNodeCount = OctreeElement::getInternalNodeCount(); + unsigned long leafNodeCount = OctreeElement::getLeafNodeCount(); qDebug("Nodes after loading scene %lu nodes %lu internal %lu leaves\n", nodeCount, internalNodeCount, leafNodeCount); - double usecPerGet = (double)VoxelNode::getGetChildAtIndexTime() / (double)VoxelNode::getGetChildAtIndexCalls(); + double usecPerGet = (double)OctreeElement::getGetChildAtIndexTime() / (double)OctreeElement::getGetChildAtIndexCalls(); qDebug("getChildAtIndexCalls=%llu getChildAtIndexTime=%llu perGet=%lf \n", - VoxelNode::getGetChildAtIndexTime(), VoxelNode::getGetChildAtIndexCalls(), usecPerGet); + OctreeElement::getGetChildAtIndexTime(), OctreeElement::getGetChildAtIndexCalls(), usecPerGet); - double usecPerSet = (double)VoxelNode::getSetChildAtIndexTime() / (double)VoxelNode::getSetChildAtIndexCalls(); + double usecPerSet = (double)OctreeElement::getSetChildAtIndexTime() / (double)OctreeElement::getSetChildAtIndexCalls(); qDebug("setChildAtIndexCalls=%llu setChildAtIndexTime=%llu perSet=%lf\n", - VoxelNode::getSetChildAtIndexTime(), VoxelNode::getSetChildAtIndexCalls(), usecPerSet); + OctreeElement::getSetChildAtIndexTime(), OctreeElement::getSetChildAtIndexCalls(), usecPerSet); _initialLoadComplete = true; _lastCheck = usecTimestampNow(); // we just loaded, no need to save again diff --git a/libraries/voxel-server-library/src/VoxelSendThread.cpp b/libraries/voxel-server-library/src/VoxelSendThread.cpp index b1a5b004b8..01f6e9ae76 100644 --- a/libraries/voxel-server-library/src/VoxelSendThread.cpp +++ b/libraries/voxel-server-library/src/VoxelSendThread.cpp @@ -353,17 +353,16 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod } ::startSceneSleepTime = _usleepTime; - nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, - _myServer->getServerTree().rootNode, _myServer->getJurisdiction()); + nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getServerTree().getRoot(), _myServer->getJurisdiction()); // This is the start of "resending" the scene. bool dontRestartSceneOnMove = false; // this is experimental if (dontRestartSceneOnMove) { if (nodeData->nodeBag.isEmpty()) { - nodeData->nodeBag.insert(_myServer->getServerTree().rootNode); // only in case of empty + nodeData->nodeBag.insert(_myServer->getServerTree().getRoot()); // only in case of empty } } else { - nodeData->nodeBag.insert(_myServer->getServerTree().rootNode); // original behavior, reset on move or empty + nodeData->nodeBag.insert(_myServer->getServerTree().getRoot()); // original behavior, reset on move or empty } } @@ -376,13 +375,13 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod bool shouldSendEnvironments = _myServer->wantSendEnvironments() && shouldDo(ENVIRONMENT_SEND_INTERVAL_USECS, VOXEL_SEND_INTERVAL_USECS); - int clientMaxPacketsPerInterval = std::max(1,(nodeData->getMaxVoxelPacketsPerSecond() / INTERVALS_PER_SECOND)); + int clientMaxPacketsPerInterval = std::max(1,(nodeData->getMaxOctreePacketsPerSecond() / INTERVALS_PER_SECOND)); int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) { printf("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n", truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(), - nodeData->getMaxVoxelPacketsPerSecond(), clientMaxPacketsPerInterval); + nodeData->getMaxOctreePacketsPerSecond(), clientMaxPacketsPerInterval); } int extraPackingAttempts = 0; @@ -390,16 +389,16 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) { printf("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n", truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(), - nodeData->getMaxVoxelPacketsPerSecond(), clientMaxPacketsPerInterval); + nodeData->getMaxOctreePacketsPerSecond(), clientMaxPacketsPerInterval); } bool lastNodeDidntFit = false; // assume each node fits if (!nodeData->nodeBag.isEmpty()) { - VoxelNode* subTree = nodeData->nodeBag.extract(); + OctreeElement* subTree = nodeData->nodeBag.extract(); bool wantOcclusionCulling = nodeData->getWantOcclusionCulling(); CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP; - float voxelSizeScale = nodeData->getVoxelSizeScale(); + float voxelSizeScale = nodeData->getOctreeSizeScale(); int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving() @@ -563,7 +562,7 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) { printf("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n", truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(), - nodeData->getMaxVoxelPacketsPerSecond(), clientMaxPacketsPerInterval); + nodeData->getMaxOctreePacketsPerSecond(), clientMaxPacketsPerInterval); } } // end if bag wasn't empty, and so we sent stuff... diff --git a/libraries/voxel-server-library/src/VoxelSendThread.h b/libraries/voxel-server-library/src/VoxelSendThread.h index ebd38ea146..5fc41fbc72 100644 --- a/libraries/voxel-server-library/src/VoxelSendThread.h +++ b/libraries/voxel-server-library/src/VoxelSendThread.h @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include "VoxelNodeData.h" #include "VoxelServer.h" diff --git a/libraries/voxel-server-library/src/VoxelServer.cpp b/libraries/voxel-server-library/src/VoxelServer.cpp index 4f0bf1185e..13cb8515be 100644 --- a/libraries/voxel-server-library/src/VoxelServer.cpp +++ b/libraries/voxel-server-library/src/VoxelServer.cpp @@ -244,9 +244,9 @@ int VoxelServer::civetwebRequestHandler(struct mg_connection* connection) { mg_printf(connection, "%s", "\r\n"); // display scene stats - unsigned long nodeCount = VoxelNode::getNodeCount(); - unsigned long internalNodeCount = VoxelNode::getInternalNodeCount(); - unsigned long leafNodeCount = VoxelNode::getLeafNodeCount(); + unsigned long nodeCount = OctreeElement::getNodeCount(); + unsigned long internalNodeCount = OctreeElement::getInternalNodeCount(); + unsigned long leafNodeCount = OctreeElement::getLeafNodeCount(); QLocale locale(QLocale::English); const float AS_PERCENT = 100.0; @@ -365,14 +365,14 @@ int VoxelServer::civetwebRequestHandler(struct mg_connection* connection) { // display memory usage stats mg_printf(connection, "%s", "Current Memory Usage Statistics\r\n"); - mg_printf(connection, "\r\nVoxelNode size... %ld bytes\r\n", sizeof(VoxelNode)); + mg_printf(connection, "\r\nVoxelTreeElement size... %ld bytes\r\n", sizeof(VoxelTreeElement)); mg_printf(connection, "%s", "\r\n"); const char* memoryScaleLabel; const float MEGABYTES = 1000000.f; const float GIGABYTES = 1000000000.f; float memoryScale; - if (VoxelNode::getTotalMemoryUsage() / MEGABYTES < 1000.0f) { + if (OctreeElement::getTotalMemoryUsage() / MEGABYTES < 1000.0f) { memoryScaleLabel = "MB"; memoryScale = MEGABYTES; } else { @@ -381,23 +381,23 @@ int VoxelServer::civetwebRequestHandler(struct mg_connection* connection) { } mg_printf(connection, "Voxel Node Memory Usage: %8.2f %s\r\n", - VoxelNode::getVoxelMemoryUsage() / memoryScale, memoryScaleLabel); + OctreeElement::getVoxelMemoryUsage() / memoryScale, memoryScaleLabel); mg_printf(connection, "Octcode Memory Usage: %8.2f %s\r\n", - VoxelNode::getOctcodeMemoryUsage() / memoryScale, memoryScaleLabel); + OctreeElement::getOctcodeMemoryUsage() / memoryScale, memoryScaleLabel); mg_printf(connection, "External Children Memory Usage: %8.2f %s\r\n", - VoxelNode::getExternalChildrenMemoryUsage() / memoryScale, memoryScaleLabel); + OctreeElement::getExternalChildrenMemoryUsage() / memoryScale, memoryScaleLabel); mg_printf(connection, "%s", " -----------\r\n"); mg_printf(connection, " Total: %8.2f %s\r\n", - VoxelNode::getTotalMemoryUsage() / memoryScale, memoryScaleLabel); + OctreeElement::getTotalMemoryUsage() / memoryScale, memoryScaleLabel); mg_printf(connection, "%s", "\r\n"); - mg_printf(connection, "%s", "VoxelNode Children Population Statistics...\r\n"); + mg_printf(connection, "%s", "OctreeElement Children Population Statistics...\r\n"); checkSum = 0; for (int i=0; i <= NUMBER_OF_CHILDREN; i++) { - checkSum += VoxelNode::getChildrenCount(i); + checkSum += OctreeElement::getChildrenCount(i); mg_printf(connection, " Nodes with %d children: %s nodes (%5.2f%%)\r\n", i, - locale.toString((uint)VoxelNode::getChildrenCount(i)).rightJustified(16, ' ').toLocal8Bit().constData(), - ((float)VoxelNode::getChildrenCount(i) / (float)nodeCount) * AS_PERCENT); + locale.toString((uint)OctreeElement::getChildrenCount(i)).rightJustified(16, ' ').toLocal8Bit().constData(), + ((float)OctreeElement::getChildrenCount(i) / (float)nodeCount) * AS_PERCENT); } mg_printf(connection, "%s", " ----------------------\r\n"); mg_printf(connection, " Total: %s nodes\r\n", @@ -405,30 +405,30 @@ int VoxelServer::civetwebRequestHandler(struct mg_connection* connection) { #ifdef BLENDED_UNION_CHILDREN mg_printf(connection, "%s", "\r\n"); - mg_printf(connection, "%s", "VoxelNode Children Encoding Statistics...\r\n"); + mg_printf(connection, "%s", "OctreeElement Children Encoding Statistics...\r\n"); mg_printf(connection, " Single or No Children: %10.llu nodes (%5.2f%%)\r\n", - VoxelNode::getSingleChildrenCount(), ((float)VoxelNode::getSingleChildrenCount() / (float)nodeCount) * AS_PERCENT); + OctreeElement::getSingleChildrenCount(), ((float)OctreeElement::getSingleChildrenCount() / (float)nodeCount) * AS_PERCENT); mg_printf(connection, " Two Children as Offset: %10.llu nodes (%5.2f%%)\r\n", - VoxelNode::getTwoChildrenOffsetCount(), - ((float)VoxelNode::getTwoChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT); + OctreeElement::getTwoChildrenOffsetCount(), + ((float)OctreeElement::getTwoChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT); mg_printf(connection, " Two Children as External: %10.llu nodes (%5.2f%%)\r\n", - VoxelNode::getTwoChildrenExternalCount(), - ((float)VoxelNode::getTwoChildrenExternalCount() / (float)nodeCount) * AS_PERCENT); + OctreeElement::getTwoChildrenExternalCount(), + ((float)OctreeElement::getTwoChildrenExternalCount() / (float)nodeCount) * AS_PERCENT); mg_printf(connection, " Three Children as Offset: %10.llu nodes (%5.2f%%)\r\n", - VoxelNode::getThreeChildrenOffsetCount(), - ((float)VoxelNode::getThreeChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT); + OctreeElement::getThreeChildrenOffsetCount(), + ((float)OctreeElement::getThreeChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT); mg_printf(connection, " Three Children as External: %10.llu nodes (%5.2f%%)\r\n", - VoxelNode::getThreeChildrenExternalCount(), - ((float)VoxelNode::getThreeChildrenExternalCount() / (float)nodeCount) * AS_PERCENT); + OctreeElement::getThreeChildrenExternalCount(), + ((float)OctreeElement::getThreeChildrenExternalCount() / (float)nodeCount) * AS_PERCENT); mg_printf(connection, " Children as External Array: %10.llu nodes (%5.2f%%)\r\n", - VoxelNode::getExternalChildrenCount(), - ((float)VoxelNode::getExternalChildrenCount() / (float)nodeCount) * AS_PERCENT); + OctreeElement::getExternalChildrenCount(), + ((float)OctreeElement::getExternalChildrenCount() / (float)nodeCount) * AS_PERCENT); - checkSum = VoxelNode::getSingleChildrenCount() + - VoxelNode::getTwoChildrenOffsetCount() + VoxelNode::getTwoChildrenExternalCount() + - VoxelNode::getThreeChildrenOffsetCount() + VoxelNode::getThreeChildrenExternalCount() + - VoxelNode::getExternalChildrenCount(); + checkSum = OctreeElement::getSingleChildrenCount() + + OctreeElement::getTwoChildrenOffsetCount() + OctreeElement::getTwoChildrenExternalCount() + + OctreeElement::getThreeChildrenOffsetCount() + OctreeElement::getThreeChildrenExternalCount() + + OctreeElement::getExternalChildrenCount(); mg_printf(connection, "%s", " ----------------\r\n"); mg_printf(connection, " Total: %10.llu nodes\r\n", checkSum); @@ -437,9 +437,9 @@ int VoxelServer::civetwebRequestHandler(struct mg_connection* connection) { mg_printf(connection, "%s", "\r\n"); mg_printf(connection, "%s", "In other news....\r\n"); mg_printf(connection, "could store 4 children internally: %10.llu nodes\r\n", - VoxelNode::getCouldStoreFourChildrenInternally()); + OctreeElement::getCouldStoreFourChildrenInternally()); mg_printf(connection, "could NOT store 4 children internally: %10.llu nodes\r\n", - VoxelNode::getCouldNotStoreFourChildrenInternally()); + OctreeElement::getCouldNotStoreFourChildrenInternally()); #endif mg_printf(connection, "%s", "\r\n"); diff --git a/libraries/voxel-server-library/src/VoxelServerPacketProcessor.cpp b/libraries/voxel-server-library/src/VoxelServerPacketProcessor.cpp index d1449f72ea..f69fabd06f 100644 --- a/libraries/voxel-server-library/src/VoxelServerPacketProcessor.cpp +++ b/libraries/voxel-server-library/src/VoxelServerPacketProcessor.cpp @@ -166,7 +166,7 @@ void VoxelServerPacketProcessor::processPacket(sockaddr& senderAddress, unsigned // Send these bits off to the VoxelTree class to process them _myServer->getServerTree().lockForWrite(); - _myServer->getServerTree().processRemoveVoxelBitstream((unsigned char*)packetData, packetLength); + _myServer->getServerTree().processRemoveOctreeElementsBitstream((unsigned char*)packetData, packetLength); _myServer->getServerTree().unlock(); // Make sure our Node and NodeList knows we've heard from this node. diff --git a/libraries/voxels/CMakeLists.txt b/libraries/voxels/CMakeLists.txt index b6f7ae9718..da97e83ec9 100644 --- a/libraries/voxels/CMakeLists.txt +++ b/libraries/voxels/CMakeLists.txt @@ -20,6 +20,7 @@ include_glm(${TARGET_NAME} ${ROOT_DIR}) include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) +link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) # link ZLIB find_package(ZLIB) diff --git a/libraries/voxels/src/VoxelConstants.h b/libraries/voxels/src/VoxelConstants.h index e9bc48f9b5..b57562f15e 100644 --- a/libraries/voxels/src/VoxelConstants.h +++ b/libraries/voxels/src/VoxelConstants.h @@ -18,24 +18,10 @@ #include #include #include +#include // this is where the coordinate system is represented -const glm::vec3 IDENTITY_RIGHT = glm::vec3( 1.0f, 0.0f, 0.0f); -const glm::vec3 IDENTITY_UP = glm::vec3( 0.0f, 1.0f, 0.0f); -const glm::vec3 IDENTITY_FRONT = glm::vec3( 0.0f, 0.0f,-1.0f); -const bool LOW_RES_MONO = false; // while in "low res mode" do voxels switch to monochrome -const uint64_t CHANGE_FUDGE = 1000 * 200; // useconds of fudge in determining if we want to resend changed voxels - -const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe - -// This controls the LOD. Larger number will make smaller voxels visible at greater distance. -const float DEFAULT_VOXEL_SIZE_SCALE = TREE_SCALE * 400.0f; -const float MAX_LOD_SIZE_MULTIPLIER = 2000.0f; - -const int NUMBER_OF_CHILDREN = 8; - -const int MAX_TREE_SLICE_BYTES = 26; const int DEFAULT_MAX_VOXELS_PER_SYSTEM = 500000; const int VERTICES_PER_VOXEL = 24; // 6 sides * 4 corners per side const int VERTEX_POINTS_PER_VOXEL = 3 * VERTICES_PER_VOXEL; // xyz for each VERTICE_PER_VOXEL @@ -56,13 +42,7 @@ const float VIEW_CULLING_RATE_IN_MILLISECONDS = 1000.0f; // once a second is fin const uint64_t CLIENT_TO_SERVER_VOXEL_SEND_INTERVAL_USECS = 1000 * 5; // 1 packet every 50 milliseconds -const float VIEW_FRUSTUM_FOV_OVERSEND = 60.0f; - -// These are guards to prevent our voxel tree recursive routines from spinning out of control -const int UNREASONABLY_DEEP_RECURSION = 20; // use this for something that you want to be shallow, but not spin out -const int DANGEROUSLY_DEEP_RECURSION = 200; // use this for something that needs to go deeper const int DEFAULT_MAX_VOXEL_PPS = 600; // the default maximum PPS we think a voxel server should send to a client -const bool VOXEL_PACKETS_COMPRESSED = false; #endif \ No newline at end of file diff --git a/libraries/voxels/src/VoxelNodeBag.h b/libraries/voxels/src/VoxelNodeBag.h deleted file mode 100644 index 9779302c59..0000000000 --- a/libraries/voxels/src/VoxelNodeBag.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// VoxelNodeBag.h -// hifi -// -// Created by Brad Hefta-Gaub on 4/25/2013 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. -// -// This class is used by the VoxelTree:encodeTreeBitstream() functions to store extra nodes that need to be sent -// it's a generic bag style storage mechanism. But It has the property that you can't put the same node into the bag -// more than once (in other words, it de-dupes automatically), also, it supports collapsing it's several peer nodes -// into a parent node in cases where you add enough peers that it makes more sense to just add the parent. -// - -#ifndef __hifi__VoxelNodeBag__ -#define __hifi__VoxelNodeBag__ - -#include "VoxelNode.h" - -class VoxelNodeBag : public VoxelNodeDeleteHook { - -public: - VoxelNodeBag(); - ~VoxelNodeBag(); - - void insert(VoxelNode* node); // put a node into the bag - VoxelNode* extract(); // pull a node out of the bag (could come in any order) - bool contains(VoxelNode* node); // is this node in the bag? - void remove(VoxelNode* node); // remove a specific item from the bag - - bool isEmpty() const { return (_elementsInUse == 0); } - int count() const { return _elementsInUse; } - - void deleteAll(); - - static void voxelNodeDeleteHook(VoxelNode* node, void* extraData); - - virtual void voxelDeleted(VoxelNode* node); - -private: - - VoxelNode** _bagElements; - int _elementsInUse; - int _sizeOfElementsArray; - int _hookID; -}; - -#endif /* defined(__hifi__VoxelNodeBag__) */ diff --git a/libraries/voxels/src/VoxelPacketData.cpp b/libraries/voxels/src/VoxelPacketData.cpp index e5c169881e..0053cff226 100644 --- a/libraries/voxels/src/VoxelPacketData.cpp +++ b/libraries/voxels/src/VoxelPacketData.cpp @@ -9,317 +9,8 @@ #include #include "VoxelPacketData.h" -bool VoxelPacketData::_debug = false; -uint64_t VoxelPacketData::_totalBytesOfOctalCodes = 0; -uint64_t VoxelPacketData::_totalBytesOfBitMasks = 0; -uint64_t VoxelPacketData::_totalBytesOfColor = 0; +// currently just an alias for OctreePacketData - - -VoxelPacketData::VoxelPacketData(bool enableCompression, int targetSize) { - changeSettings(enableCompression, targetSize); // does reset... -} - -void VoxelPacketData::changeSettings(bool enableCompression, int targetSize) { - _enableCompression = enableCompression; - _targetSize = std::min(MAX_VOXEL_UNCOMRESSED_PACKET_SIZE, targetSize); - reset(); -} - -void VoxelPacketData::reset() { - _bytesInUse = 0; - _bytesAvailable = _targetSize; - _subTreeAt = 0; - _compressedBytes = 0; - _bytesInUseLastCheck = 0; - _dirty = false; - - _bytesOfOctalCodes = 0; - _bytesOfBitMasks = 0; - _bytesOfColor = 0; - _bytesOfOctalCodesCurrentSubTree = 0; -} - -VoxelPacketData::~VoxelPacketData() { -} - -bool VoxelPacketData::append(const unsigned char* data, int length) { - bool success = false; - - if (length <= _bytesAvailable) { - memcpy(&_uncompressed[_bytesInUse], data, length); - _bytesInUse += length; - _bytesAvailable -= length; - success = true; - _dirty = true; - } - return success; -} - -bool VoxelPacketData::append(unsigned char byte) { - bool success = false; - if (_bytesAvailable > 0) { - _uncompressed[_bytesInUse] = byte; - _bytesInUse++; - _bytesAvailable--; - success = true; - _dirty = true; - } - return success; -} - -bool VoxelPacketData::updatePriorBitMask(int offset, unsigned char bitmask) { - bool success = false; - if (offset >= 0 && offset < _bytesInUse) { - _uncompressed[offset] = bitmask; - success = true; - _dirty = true; - } - return success; -} - -bool VoxelPacketData::updatePriorBytes(int offset, const unsigned char* replacementBytes, int length) { - bool success = false; - if (length >= 0 && offset >= 0 && ((offset + length) <= _bytesInUse)) { - memcpy(&_uncompressed[offset], replacementBytes, length); // copy new content - success = true; - _dirty = true; - } - return success; -} - -bool VoxelPacketData::startSubTree(const unsigned char* octcode) { - _bytesOfOctalCodesCurrentSubTree = _bytesOfOctalCodes; - bool success = false; - int possibleStartAt = _bytesInUse; - int length = 0; - if (octcode) { - length = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(octcode)); - success = append(octcode, length); // handles checking compression - } else { - // NULL case, means root node, which is 0 - unsigned char byte = 0; - length = 1; - success = append(byte); // handles checking compression - } - if (success) { - _subTreeAt = possibleStartAt; - } - if (success) { - _bytesOfOctalCodes += length; - _totalBytesOfOctalCodes += length; - } - return success; -} - -const unsigned char* VoxelPacketData::getFinalizedData() { - if (!_enableCompression) { - return &_uncompressed[0]; - } - - if (_dirty) { - if (_debug) { - printf("getFinalizedData() _compressedBytes=%d _bytesInUse=%d\n",_compressedBytes, _bytesInUse); - } - compressContent(); - } - return &_compressed[0]; -} - -int VoxelPacketData::getFinalizedSize() { - if (!_enableCompression) { - return _bytesInUse; - } - - if (_dirty) { - if (_debug) { - printf("getFinalizedSize() _compressedBytes=%d _bytesInUse=%d\n",_compressedBytes, _bytesInUse); - } - compressContent(); - } - - return _compressedBytes; -} - - -void VoxelPacketData::endSubTree() { - _subTreeAt = _bytesInUse; -} - -void VoxelPacketData::discardSubTree() { - int bytesInSubTree = _bytesInUse - _subTreeAt; - _bytesInUse -= bytesInSubTree; - _bytesAvailable += bytesInSubTree; - _subTreeAt = _bytesInUse; // should be the same actually... - _dirty = true; - - // rewind to start of this subtree, other items rewound by endLevel() - int reduceBytesOfOctalCodes = _bytesOfOctalCodes - _bytesOfOctalCodesCurrentSubTree; - _bytesOfOctalCodes = _bytesOfOctalCodesCurrentSubTree; - _totalBytesOfOctalCodes -= reduceBytesOfOctalCodes; -} - -LevelDetails VoxelPacketData::startLevel() { - LevelDetails key(_bytesInUse, _bytesOfOctalCodes, _bytesOfBitMasks, _bytesOfColor); - return key; -} - -void VoxelPacketData::discardLevel(LevelDetails key) { - int bytesInLevel = _bytesInUse - key._startIndex; - - // reset statistics... - int reduceBytesOfOctalCodes = _bytesOfOctalCodes - key._bytesOfOctalCodes; - int reduceBytesOfBitMasks = _bytesOfBitMasks - key._bytesOfBitmasks; - int reduceBytesOfColor = _bytesOfColor - key._bytesOfColor; - - _bytesOfOctalCodes = key._bytesOfOctalCodes; - _bytesOfBitMasks = key._bytesOfBitmasks; - _bytesOfColor = key._bytesOfColor; - - _totalBytesOfOctalCodes -= reduceBytesOfOctalCodes; - _totalBytesOfBitMasks -= reduceBytesOfBitMasks; - _totalBytesOfColor -= reduceBytesOfColor; - - if (_debug) { - printf("discardLevel() BEFORE _dirty=%s bytesInLevel=%d _compressedBytes=%d _bytesInUse=%d\n", - debug::valueOf(_dirty), bytesInLevel, _compressedBytes, _bytesInUse); - } - - _bytesInUse -= bytesInLevel; - _bytesAvailable += bytesInLevel; - _dirty = true; - - if (_debug) { - printf("discardLevel() AFTER _dirty=%s bytesInLevel=%d _compressedBytes=%d _bytesInUse=%d\n", - debug::valueOf(_dirty), bytesInLevel, _compressedBytes, _bytesInUse); - } -} - -bool VoxelPacketData::endLevel(LevelDetails key) { - bool success = true; - return success; -} - -bool VoxelPacketData::appendBitMask(unsigned char bitmask) { - bool success = append(bitmask); // handles checking compression - if (success) { - _bytesOfBitMasks++; - _totalBytesOfBitMasks++; - } - return success; -} - -bool VoxelPacketData::appendColor(const nodeColor& color) { - // eventually we can make this use a dictionary... - bool success = false; - const int BYTES_PER_COLOR = 3; - if (_bytesAvailable > BYTES_PER_COLOR) { - // handles checking compression... - if (append(color[RED_INDEX])) { - if (append(color[GREEN_INDEX])) { - if (append(color[BLUE_INDEX])) { - success = true; - } - } - } - } - if (success) { - _bytesOfColor += BYTES_PER_COLOR; - _totalBytesOfColor += BYTES_PER_COLOR; - } - return success; -} - -uint64_t VoxelPacketData::_compressContentTime = 0; -uint64_t VoxelPacketData::_compressContentCalls = 0; - -bool VoxelPacketData::compressContent() { - PerformanceWarning warn(false, "VoxelPacketData::compressContent()", false, &_compressContentTime, &_compressContentCalls); - - // without compression, we always pass... - if (!_enableCompression) { - return true; - } - - _bytesInUseLastCheck = _bytesInUse; - - bool success = false; - const int MAX_COMPRESSION = 9; - - // we only want to compress the data payload, not the message header - const uchar* uncompressedData = &_uncompressed[0]; - int uncompressedSize = _bytesInUse; - - QByteArray compressedData = qCompress(uncompressedData, uncompressedSize, MAX_COMPRESSION); - - if (compressedData.size() < MAX_VOXEL_PACKET_DATA_SIZE) { - _compressedBytes = compressedData.size(); - for (int i = 0; i < _compressedBytes; i++) { - _compressed[i] = compressedData[i]; - } - _dirty = false; - success = true; - } - return success; -} - - -void VoxelPacketData::loadFinalizedContent(const unsigned char* data, int length) { - reset(); - - if (data && length > 0) { - - if (_enableCompression) { - QByteArray compressedData; - for (int i = 0; i < length; i++) { - compressedData[i] = data[i]; - _compressed[i] = compressedData[i]; - } - _compressedBytes = length; - QByteArray uncompressedData = qUncompress(compressedData); - if (uncompressedData.size() <= _bytesAvailable) { - _bytesInUse = uncompressedData.size(); - _bytesAvailable -= uncompressedData.size(); - - for (int i = 0; i < _bytesInUse; i++) { - _uncompressed[i] = uncompressedData[i]; - } - } - } else { - for (int i = 0; i < length; i++) { - _uncompressed[i] = _compressed[i] = data[i]; - } - _bytesInUse = _compressedBytes = length; - } - } else { - if (_debug) { - printf("VoxelPacketData::loadCompressedContent()... length = 0, nothing to do...\n"); - } - } -} - -void VoxelPacketData::debugContent() { - printf("VoxelPacketData::debugContent()... COMPRESSED DATA.... size=%d\n",_compressedBytes); - int perline=0; - for (int i = 0; i < _compressedBytes; i++) { - printf("%.2x ",_compressed[i]); - perline++; - if (perline >= 30) { - printf("\n"); - perline=0; - } - } - printf("\n"); - - printf("VoxelPacketData::debugContent()... UNCOMPRESSED DATA.... size=%d\n",_bytesInUse); - perline=0; - for (int i = 0; i < _bytesInUse; i++) { - printf("%.2x ",_uncompressed[i]); - perline++; - if (perline >= 30) { - printf("\n"); - perline=0; - } - } - printf("\n"); -} +VoxelPacketData::VoxelPacketData(bool enableCompression, int maxFinalizedSize) : + OctreePacketData(enableCompression, maxFinalizedSize) { +}; diff --git a/libraries/voxels/src/VoxelPacketData.h b/libraries/voxels/src/VoxelPacketData.h index 8dbcef29a0..200bd7d30c 100644 --- a/libraries/voxels/src/VoxelPacketData.h +++ b/libraries/voxels/src/VoxelPacketData.h @@ -20,8 +20,11 @@ #define __hifi__VoxelPacketData__ #include + +#include + #include "VoxelConstants.h" -#include "VoxelNode.h" +#include "VoxelTreeElement.h" typedef unsigned char VOXEL_PACKET_FLAGS; typedef uint16_t VOXEL_PACKET_SEQUENCE; @@ -35,148 +38,10 @@ const int MAX_VOXEL_PACKET_DATA_SIZE = MAX_PACKET_SIZE - VOXEL_PACKET_HEADER_SIZ const int MAX_VOXEL_UNCOMRESSED_PACKET_SIZE = MAX_VOXEL_PACKET_DATA_SIZE; -const int MINIMUM_ATTEMPT_MORE_PACKING = sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE) + 40; -const int COMPRESS_PADDING = 15; -const int REASONABLE_NUMBER_OF_PACKING_ATTEMPTS = 5; - -const int PACKET_IS_COLOR_BIT = 0; -const int PACKET_IS_COMPRESSED_BIT = 1; - -/// An opaque key used when starting, ending, and discarding encoding/packing levels of VoxelPacketData -class LevelDetails { - LevelDetails(int startIndex, int bytesOfOctalCodes, int bytesOfBitmasks, int bytesOfColor) : - _startIndex(startIndex), - _bytesOfOctalCodes(bytesOfOctalCodes), - _bytesOfBitmasks(bytesOfBitmasks), - _bytesOfColor(bytesOfColor) { - } - - friend class VoxelPacketData; - -private: - int _startIndex; - int _bytesOfOctalCodes; - int _bytesOfBitmasks; - int _bytesOfColor; -}; - -/// Handles packing of the data portion of PACKET_TYPE_VOXEL_DATA messages. -class VoxelPacketData { +/// Handles packing of the data portion of PACKET_TYPE_VOXEL_DATA messages. +class VoxelPacketData : public OctreePacketData { public: - VoxelPacketData(bool enableCompression = false, int maxFinalizedSize = MAX_VOXEL_PACKET_DATA_SIZE); - ~VoxelPacketData(); - - /// change compression and target size settings - void changeSettings(bool enableCompression = false, int targetSize = MAX_VOXEL_PACKET_DATA_SIZE); - - /// reset completely, all data is discarded - void reset(); - - /// call to begin encoding a subtree starting at this point, this will append the octcode to the uncompressed stream - /// at this point. May fail if new datastream is too long. In failure case the stream remains in it's previous state. - bool startSubTree(const unsigned char* octcode = NULL); - - // call to indicate that the current subtree is complete and changes should be committed. - void endSubTree(); - - // call rollback the current subtree and restore the stream to the state prior to starting the subtree encoding - void discardSubTree(); - - /// starts a level marker. returns an opaque key which can be used to discard the level - LevelDetails startLevel(); - - /// discards all content back to a previous marker key - void discardLevel(LevelDetails key); - - /// ends a level, and performs any expensive finalization. may fail if finalization creates a stream which is too large - /// if the finalization would fail, the packet will automatically discard the previous level. - bool endLevel(LevelDetails key); - - /// appends a bitmask to the end of the stream, may fail if new data stream is too long to fit in packet - bool appendBitMask(unsigned char bitmask); - - /// updates the value of a bitmask from a previously appended portion of the uncompressed stream, might fail if the new - /// bitmask would cause packet to be less compressed, or if offset was out of range. - bool updatePriorBitMask(int offset, unsigned char bitmask); - - /// updates the uncompressed content of the stream starting at byte offset with replacementBytes for length. - /// Might fail if the new bytes would cause packet to be less compressed, or if offset and length was out of range. - bool updatePriorBytes(int offset, const unsigned char* replacementBytes, int length); - - /// appends a color to the end of the stream, may fail if new data stream is too long to fit in packet - bool appendColor(const nodeColor& color); - - /// returns a byte offset from beginning of the uncompressed stream based on offset from end. - /// Positive offsetFromEnd returns that many bytes before the end of uncompressed stream - int getUncompressedByteOffset(int offsetFromEnd = 0) const { return _bytesInUse - offsetFromEnd; } - - /// get access to the finalized data (it may be compressed or rewritten into optimal form) - const unsigned char* getFinalizedData(); - /// get size of the finalized data (it may be compressed or rewritten into optimal form) - int getFinalizedSize(); - - /// get pointer to the start of uncompressed stream buffer - const unsigned char* getUncompressedData() { return &_uncompressed[0]; } - /// the size of the packet in uncompressed form - int getUncompressedSize() { return _bytesInUse; } - - /// has some content been written to the packet - bool hasContent() const { return (_bytesInUse > 0); } - - /// load finalized content to allow access to decoded content for parsing - void loadFinalizedContent(const unsigned char* data, int length); - - /// returns whether or not zlib compression enabled on finalization - bool isCompressed() const { return _enableCompression; } - - /// returns the target uncompressed size - int getTargetSize() const { return _targetSize; } - - /// displays contents for debugging - void debugContent(); - - static uint64_t getCompressContentTime() { return _compressContentTime; } /// total time spent compressing content - static uint64_t getCompressContentCalls() { return _compressContentCalls; } /// total calls to compress content - static uint64_t getTotalBytesOfOctalCodes() { return _totalBytesOfOctalCodes; } /// total bytes for octal codes - static uint64_t getTotalBytesOfBitMasks() { return _totalBytesOfBitMasks; } /// total bytes of bitmasks - static uint64_t getTotalBytesOfColor() { return _totalBytesOfColor; } /// total bytes of color - -private: - /// appends raw bytes, might fail if byte would cause packet to be too large - bool append(const unsigned char* data, int length); - - /// append a single byte, might fail if byte would cause packet to be too large - bool append(unsigned char byte); - - int _targetSize; - bool _enableCompression; - - unsigned char _uncompressed[MAX_VOXEL_UNCOMRESSED_PACKET_SIZE]; - int _bytesInUse; - int _bytesAvailable; - int _subTreeAt; - - bool compressContent(); - - unsigned char _compressed[MAX_VOXEL_UNCOMRESSED_PACKET_SIZE]; - int _compressedBytes; - int _bytesInUseLastCheck; - bool _dirty; - - // statistics... - int _bytesOfOctalCodes; - int _bytesOfBitMasks; - int _bytesOfColor; - int _bytesOfOctalCodesCurrentSubTree; - - static bool _debug; - - static uint64_t _compressContentTime; - static uint64_t _compressContentCalls; - - static uint64_t _totalBytesOfOctalCodes; - static uint64_t _totalBytesOfBitMasks; - static uint64_t _totalBytesOfColor; + VoxelPacketData(bool enableCompression = false, int maxFinalizedSize = MAX_OCTREE_PACKET_DATA_SIZE); }; #endif /* defined(__hifi__VoxelPacketData__) */ \ No newline at end of file diff --git a/libraries/voxels/src/VoxelQuery.cpp b/libraries/voxels/src/VoxelQuery.cpp index 1dc8eb0d8f..b595f448cb 100644 --- a/libraries/voxels/src/VoxelQuery.cpp +++ b/libraries/voxels/src/VoxelQuery.cpp @@ -6,147 +6,9 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // -#include -#include -#include - -#include -#include -#include -#include -#include "VoxelConstants.h" - #include "VoxelQuery.h" -using namespace std; - -static const float fingerVectorRadix = 4; // bits of precision when converting from float<->fixed - -VoxelQuery::VoxelQuery(Node* owningNode) : - NodeData(owningNode), - _uuid(), - _cameraPosition(0,0,0), - _cameraOrientation(), - _cameraFov(0.0f), - _cameraAspectRatio(0.0f), - _cameraNearClip(0.0f), - _cameraFarClip(0.0f), - _wantColor(true), - _wantDelta(true), - _wantLowResMoving(true), - _wantOcclusionCulling(false), // disabled by default - _wantCompression(false), // disabled by default - _maxVoxelPPS(DEFAULT_MAX_VOXEL_PPS), - _voxelSizeScale(DEFAULT_VOXEL_SIZE_SCALE) -{ - +VoxelQuery::VoxelQuery(Node* owningNode) : OctreeQuery(owningNode) { } -VoxelQuery::~VoxelQuery() { - // nothing to do -} - -int VoxelQuery::getBroadcastData(unsigned char* destinationBuffer) { - unsigned char* bufferStart = destinationBuffer; - - // TODO: DRY this up to a shared method - // that can pack any type given the number of bytes - // and return the number of bytes to push the pointer - - // UUID - QByteArray uuidByteArray = _uuid.toRfc4122(); - memcpy(destinationBuffer, uuidByteArray.constData(), uuidByteArray.size()); - destinationBuffer += uuidByteArray.size(); - - // camera details - memcpy(destinationBuffer, &_cameraPosition, sizeof(_cameraPosition)); - destinationBuffer += sizeof(_cameraPosition); - destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _cameraOrientation); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _cameraFov); - destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _cameraAspectRatio); - destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraNearClip); - destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraFarClip); - memcpy(destinationBuffer, &_cameraEyeOffsetPosition, sizeof(_cameraEyeOffsetPosition)); - destinationBuffer += sizeof(_cameraEyeOffsetPosition); - - // bitMask of less than byte wide items - unsigned char bitItems = 0; - if (_wantLowResMoving) { setAtBit(bitItems, WANT_LOW_RES_MOVING_BIT); } - if (_wantColor) { setAtBit(bitItems, WANT_COLOR_AT_BIT); } - if (_wantDelta) { setAtBit(bitItems, WANT_DELTA_AT_BIT); } - if (_wantOcclusionCulling) { setAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); } - if (_wantCompression) { setAtBit(bitItems, WANT_COMPRESSION); } - - *destinationBuffer++ = bitItems; - - // desired Max Voxel PPS - memcpy(destinationBuffer, &_maxVoxelPPS, sizeof(_maxVoxelPPS)); - destinationBuffer += sizeof(_maxVoxelPPS); - - // desired voxelSizeScale - memcpy(destinationBuffer, &_voxelSizeScale, sizeof(_voxelSizeScale)); - destinationBuffer += sizeof(_voxelSizeScale); - - // desired boundaryLevelAdjust - memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust)); - destinationBuffer += sizeof(_boundaryLevelAdjust); - - return destinationBuffer - bufferStart; -} - -// called on the other nodes - assigns it to my views of the others -int VoxelQuery::parseData(unsigned char* sourceBuffer, int numBytes) { - - // increment to push past the packet header - int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer); - sourceBuffer += numBytesPacketHeader; - - unsigned char* startPosition = sourceBuffer; - - // push past the node session UUID - sourceBuffer += NUM_BYTES_RFC4122_UUID; - - // user UUID - _uuid = QUuid::fromRfc4122(QByteArray((char*) sourceBuffer, NUM_BYTES_RFC4122_UUID)); - sourceBuffer += NUM_BYTES_RFC4122_UUID; - - // camera details - memcpy(&_cameraPosition, sourceBuffer, sizeof(_cameraPosition)); - sourceBuffer += sizeof(_cameraPosition); - sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _cameraOrientation); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_cameraFov); - sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer,_cameraAspectRatio); - sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraNearClip); - sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraFarClip); - memcpy(&_cameraEyeOffsetPosition, sourceBuffer, sizeof(_cameraEyeOffsetPosition)); - sourceBuffer += sizeof(_cameraEyeOffsetPosition); - - // voxel sending features... - unsigned char bitItems = 0; - bitItems = (unsigned char)*sourceBuffer++; - _wantLowResMoving = oneAtBit(bitItems, WANT_LOW_RES_MOVING_BIT); - _wantColor = oneAtBit(bitItems, WANT_COLOR_AT_BIT); - _wantDelta = oneAtBit(bitItems, WANT_DELTA_AT_BIT); - _wantOcclusionCulling = oneAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); - _wantCompression = oneAtBit(bitItems, WANT_COMPRESSION); - - // desired Max Voxel PPS - memcpy(&_maxVoxelPPS, sourceBuffer, sizeof(_maxVoxelPPS)); - sourceBuffer += sizeof(_maxVoxelPPS); - - // desired voxelSizeScale - memcpy(&_voxelSizeScale, sourceBuffer, sizeof(_voxelSizeScale)); - sourceBuffer += sizeof(_voxelSizeScale); - - // desired boundaryLevelAdjust - memcpy(&_boundaryLevelAdjust, sourceBuffer, sizeof(_boundaryLevelAdjust)); - sourceBuffer += sizeof(_boundaryLevelAdjust); - - return sourceBuffer - startPosition; -} - -glm::vec3 VoxelQuery::calculateCameraDirection() const { - glm::vec3 direction = glm::vec3(_cameraOrientation * glm::vec4(IDENTITY_FRONT, 0.0f)); - return direction; -} diff --git a/libraries/voxels/src/VoxelQuery.h b/libraries/voxels/src/VoxelQuery.h index ed7814db09..0a4cdda6a4 100644 --- a/libraries/voxels/src/VoxelQuery.h +++ b/libraries/voxels/src/VoxelQuery.h @@ -9,102 +9,14 @@ #ifndef __hifi__VoxelQuery__ #define __hifi__VoxelQuery__ -#include -#include -#include +#include -#include -#include +class VoxelQuery : public OctreeQuery { -#include -#include -#include - -#include - -#include - -// First bitset -const int WANT_LOW_RES_MOVING_BIT = 0; -const int WANT_COLOR_AT_BIT = 1; -const int WANT_DELTA_AT_BIT = 2; -const int WANT_OCCLUSION_CULLING_BIT = 3; -const int WANT_COMPRESSION = 4; // 5th bit - -class VoxelQuery : public NodeData { - Q_OBJECT - public: VoxelQuery(Node* owningNode = NULL); - virtual ~VoxelQuery(); - int getBroadcastData(unsigned char* destinationBuffer); - int parseData(unsigned char* sourceBuffer, int numBytes); - - QUuid& getUUID() { return _uuid; } - void setUUID(const QUuid& uuid) { _uuid = uuid; } - - // getters for camera details - const glm::vec3& getCameraPosition() const { return _cameraPosition; } - const glm::quat& getCameraOrientation() const { return _cameraOrientation; } - float getCameraFov() const { return _cameraFov; } - float getCameraAspectRatio() const { return _cameraAspectRatio; } - float getCameraNearClip() const { return _cameraNearClip; } - float getCameraFarClip() const { return _cameraFarClip; } - const glm::vec3& getCameraEyeOffsetPosition() const { return _cameraEyeOffsetPosition; } - - glm::vec3 calculateCameraDirection() const; - - // setters for camera details - void setCameraPosition(const glm::vec3& position) { _cameraPosition = position; } - void setCameraOrientation(const glm::quat& orientation) { _cameraOrientation = orientation; } - void setCameraFov(float fov) { _cameraFov = fov; } - void setCameraAspectRatio(float aspectRatio) { _cameraAspectRatio = aspectRatio; } - void setCameraNearClip(float nearClip) { _cameraNearClip = nearClip; } - void setCameraFarClip(float farClip) { _cameraFarClip = farClip; } - void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; } - - // related to Voxel Sending strategies - bool getWantColor() const { return _wantColor; } - bool getWantDelta() const { return _wantDelta; } - bool getWantLowResMoving() const { return _wantLowResMoving; } - bool getWantOcclusionCulling() const { return _wantOcclusionCulling; } - bool getWantCompression() const { return _wantCompression; } - int getMaxVoxelPacketsPerSecond() const { return _maxVoxelPPS; } - float getVoxelSizeScale() const { return _voxelSizeScale; } - int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } - -public slots: - void setWantLowResMoving(bool wantLowResMoving) { _wantLowResMoving = wantLowResMoving; } - void setWantColor(bool wantColor) { _wantColor = wantColor; } - void setWantDelta(bool wantDelta) { _wantDelta = wantDelta; } - void setWantOcclusionCulling(bool wantOcclusionCulling) { _wantOcclusionCulling = wantOcclusionCulling; } - void setWantCompression(bool wantCompression) { _wantCompression = wantCompression; } - void setMaxVoxelPacketsPerSecond(int maxVoxelPPS) { _maxVoxelPPS = maxVoxelPPS; } - void setVoxelSizeScale(float voxelSizeScale) { _voxelSizeScale = voxelSizeScale; } - void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; } - -protected: - QUuid _uuid; - - // camera details for the avatar - glm::vec3 _cameraPosition; - glm::quat _cameraOrientation; - float _cameraFov; - float _cameraAspectRatio; - float _cameraNearClip; - float _cameraFarClip; - glm::vec3 _cameraEyeOffsetPosition; - - // voxel server sending items - bool _wantColor; - bool _wantDelta; - bool _wantLowResMoving; - bool _wantOcclusionCulling; - bool _wantCompression; - int _maxVoxelPPS; - float _voxelSizeScale; /// used for LOD calculations - int _boundaryLevelAdjust; /// used for LOD calculations + // currently just an alias private: // privatize the copy constructor and assignment operator so they cannot be called diff --git a/libraries/voxels/src/VoxelSceneStats.cpp b/libraries/voxels/src/VoxelSceneStats.cpp index 5bff303198..2f486a2fa4 100644 --- a/libraries/voxels/src/VoxelSceneStats.cpp +++ b/libraries/voxels/src/VoxelSceneStats.cpp @@ -7,836 +7,8 @@ // // -#include -#include - -#include -#include - -#include "VoxelPacketData.h" -#include "VoxelNode.h" #include "VoxelSceneStats.h" -const int samples = 100; -VoxelSceneStats::VoxelSceneStats() : - _elapsedAverage(samples), - _bitsPerVoxelAverage(samples), - _incomingFlightTimeAverage(samples), - _jurisdictionRoot(NULL) -{ - reset(); - _isReadyToSend = false; - _isStarted = false; - _lastFullTotalEncodeTime = 0; - _lastFullElapsed = 0; - _incomingPacket = 0; - _incomingBytes = 0; - _incomingWastedBytes = 0; - _incomingLastSequence = 0; - _incomingOutOfOrder = 0; - _incomingLikelyLost = 0; - -} - -// copy constructor -VoxelSceneStats::VoxelSceneStats(const VoxelSceneStats& other) : -_jurisdictionRoot(NULL) { - copyFromOther(other); -} - -// copy assignment -VoxelSceneStats& VoxelSceneStats::operator=(const VoxelSceneStats& other) { - copyFromOther(other); - return *this; -} - -void VoxelSceneStats::copyFromOther(const VoxelSceneStats& other) { - _totalEncodeTime = other._totalEncodeTime; - _elapsed = other._elapsed; - _lastFullTotalEncodeTime = other._lastFullTotalEncodeTime; - _lastFullElapsed = other._lastFullElapsed; - _encodeStart = other._encodeStart; - - _packets = other._packets; - _bytes = other._bytes; - _passes = other._passes; - - _totalVoxels = other._totalVoxels; - _totalInternal = other._totalInternal; - _totalLeaves = other._totalLeaves; - - _traversed = other._traversed; - _internal = other._internal; - _leaves = other._leaves; - - _skippedDistance = other._skippedDistance; - _internalSkippedDistance = other._internalSkippedDistance; - _leavesSkippedDistance = other._leavesSkippedDistance; - - _skippedOutOfView = other._skippedOutOfView; - _internalSkippedOutOfView = other._internalSkippedOutOfView; - _leavesSkippedOutOfView = other._leavesSkippedOutOfView; - - _skippedWasInView = other._skippedWasInView; - _internalSkippedWasInView = other._internalSkippedWasInView; - _leavesSkippedWasInView = other._leavesSkippedWasInView; - - _skippedNoChange = other._skippedNoChange; - _internalSkippedNoChange = other._internalSkippedNoChange; - _leavesSkippedNoChange = other._leavesSkippedNoChange; - - _skippedOccluded = other._skippedOccluded; - _internalSkippedOccluded = other._internalSkippedOccluded; - _leavesSkippedOccluded = other._leavesSkippedOccluded; - - _colorSent = other._colorSent; - _internalColorSent = other._internalColorSent; - _leavesColorSent = other._leavesColorSent; - - _didntFit = other._didntFit; - _internalDidntFit = other._internalDidntFit; - _leavesDidntFit = other._leavesDidntFit; - - _colorBitsWritten = other._colorBitsWritten; - _existsBitsWritten = other._existsBitsWritten; - _existsInPacketBitsWritten = other._existsInPacketBitsWritten; - _treesRemoved = other._treesRemoved; - - // before copying the jurisdictions, delete any current values... - if (_jurisdictionRoot) { - delete[] _jurisdictionRoot; - _jurisdictionRoot = NULL; - } - for (int i=0; i < _jurisdictionEndNodes.size(); i++) { - if (_jurisdictionEndNodes[i]) { - delete[] _jurisdictionEndNodes[i]; - } - } - _jurisdictionEndNodes.clear(); - - // Now copy the values from the other - if (other._jurisdictionRoot) { - int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(other._jurisdictionRoot)); - _jurisdictionRoot = new unsigned char[bytes]; - memcpy(_jurisdictionRoot, other._jurisdictionRoot, bytes); - } - for (int i=0; i < other._jurisdictionEndNodes.size(); i++) { - unsigned char* endNodeCode = other._jurisdictionEndNodes[i]; - if (endNodeCode) { - int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); - unsigned char* endNodeCodeCopy = new unsigned char[bytes]; - memcpy(endNodeCodeCopy, endNodeCode, bytes); - _jurisdictionEndNodes.push_back(endNodeCodeCopy); - } - } - - _incomingPacket = other._incomingPacket; - _incomingBytes = other._incomingBytes; - _incomingWastedBytes = other._incomingWastedBytes; - _incomingLastSequence = other._incomingLastSequence; - _incomingOutOfOrder = other._incomingOutOfOrder; - _incomingLikelyLost = other._incomingLikelyLost; -} - - -VoxelSceneStats::~VoxelSceneStats() { - reset(); -} - -void VoxelSceneStats::sceneStarted(bool isFullScene, bool isMoving, VoxelNode* root, JurisdictionMap* jurisdictionMap) { - reset(); // resets packet and voxel stats - _isStarted = true; - _start = usecTimestampNow(); - - _totalVoxels = VoxelNode::getNodeCount(); - _totalInternal = VoxelNode::getInternalNodeCount(); - _totalLeaves = VoxelNode::getLeafNodeCount(); - - _isFullScene = isFullScene; - _isMoving = isMoving; - - if (_jurisdictionRoot) { - delete[] _jurisdictionRoot; - _jurisdictionRoot = NULL; - } - // clear existing endNodes before copying new ones... - for (int i=0; i < _jurisdictionEndNodes.size(); i++) { - if (_jurisdictionEndNodes[i]) { - delete[] _jurisdictionEndNodes[i]; - } - } - _jurisdictionEndNodes.clear(); - - // setup jurisdictions - if (jurisdictionMap) { - unsigned char* jurisdictionRoot = jurisdictionMap->getRootOctalCode(); - if (jurisdictionRoot) { - int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(jurisdictionRoot)); - _jurisdictionRoot = new unsigned char[bytes]; - memcpy(_jurisdictionRoot, jurisdictionRoot, bytes); - } - - // copy new endNodes... - for (int i=0; i < jurisdictionMap->getEndNodeCount(); i++) { - unsigned char* endNodeCode = jurisdictionMap->getEndNodeOctalCode(i); - if (endNodeCode) { - int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); - unsigned char* endNodeCodeCopy = new unsigned char[bytes]; - memcpy(endNodeCodeCopy, endNodeCode, bytes); - _jurisdictionEndNodes.push_back(endNodeCodeCopy); - } - } - } -} - -void VoxelSceneStats::sceneCompleted() { - if (_isStarted) { - _end = usecTimestampNow(); - _elapsed = _end - _start; - _elapsedAverage.updateAverage((float)_elapsed); - - if (_isFullScene) { - _lastFullElapsed = _elapsed; - _lastFullTotalEncodeTime = _totalEncodeTime; - } - - _statsMessageLength = packIntoMessage(_statsMessage, sizeof(_statsMessage)); - _isReadyToSend = true; - _isStarted = false; - } -} - -void VoxelSceneStats::encodeStarted() { - _encodeStart = usecTimestampNow(); -} - -void VoxelSceneStats::encodeStopped() { - _totalEncodeTime += (usecTimestampNow() - _encodeStart); -} - -void VoxelSceneStats::reset() { - _totalEncodeTime = 0; - _encodeStart = 0; - - _packets = 0; - _bytes = 0; - _passes = 0; - - _totalVoxels = 0; - _totalInternal = 0; - _totalLeaves = 0; - - _traversed = 0; - _internal = 0; - _leaves = 0; - - _skippedDistance = 0; - _internalSkippedDistance = 0; - _leavesSkippedDistance = 0; - - _skippedOutOfView = 0; - _internalSkippedOutOfView = 0; - _leavesSkippedOutOfView = 0; - - _skippedWasInView = 0; - _internalSkippedWasInView = 0; - _leavesSkippedWasInView = 0; - - _skippedNoChange = 0; - _internalSkippedNoChange = 0; - _leavesSkippedNoChange = 0; - - _skippedOccluded = 0; - _internalSkippedOccluded = 0; - _leavesSkippedOccluded = 0; - - _colorSent = 0; - _internalColorSent = 0; - _leavesColorSent = 0; - - _didntFit = 0; - _internalDidntFit = 0; - _leavesDidntFit = 0; - - _colorBitsWritten = 0; - _existsBitsWritten = 0; - _existsInPacketBitsWritten = 0; - _treesRemoved = 0; - - if (_jurisdictionRoot) { - delete[] _jurisdictionRoot; - _jurisdictionRoot = NULL; - } - for (int i=0; i < _jurisdictionEndNodes.size(); i++) { - if (_jurisdictionEndNodes[i]) { - delete[] _jurisdictionEndNodes[i]; - } - } - _jurisdictionEndNodes.clear(); -} - -void VoxelSceneStats::packetSent(int bytes) { - _packets++; - _bytes += bytes; -} - -void VoxelSceneStats::traversed(const VoxelNode* node) { - _traversed++; - if (node->isLeaf()) { - _leaves++; - } else { - _internal++; - } -} - -void VoxelSceneStats::skippedDistance(const VoxelNode* node) { - _skippedDistance++; - if (node->isLeaf()) { - _leavesSkippedDistance++; - } else { - _internalSkippedDistance++; - } -} - -void VoxelSceneStats::skippedOutOfView(const VoxelNode* node) { - _skippedOutOfView++; - if (node->isLeaf()) { - _leavesSkippedOutOfView++; - } else { - _internalSkippedOutOfView++; - } -} - -void VoxelSceneStats::skippedWasInView(const VoxelNode* node) { - _skippedWasInView++; - if (node->isLeaf()) { - _leavesSkippedWasInView++; - } else { - _internalSkippedWasInView++; - } -} - -void VoxelSceneStats::skippedNoChange(const VoxelNode* node) { - _skippedNoChange++; - if (node->isLeaf()) { - _leavesSkippedNoChange++; - } else { - _internalSkippedNoChange++; - } -} - -void VoxelSceneStats::skippedOccluded(const VoxelNode* node) { - _skippedOccluded++; - if (node->isLeaf()) { - _leavesSkippedOccluded++; - } else { - _internalSkippedOccluded++; - } -} - -void VoxelSceneStats::colorSent(const VoxelNode* node) { - _colorSent++; - if (node->isLeaf()) { - _leavesColorSent++; - } else { - _internalColorSent++; - } -} - -void VoxelSceneStats::didntFit(const VoxelNode* node) { - _didntFit++; - if (node->isLeaf()) { - _leavesDidntFit++; - } else { - _internalDidntFit++; - } -} - -void VoxelSceneStats::colorBitsWritten() { - _colorBitsWritten++; -} - -void VoxelSceneStats::existsBitsWritten() { - _existsBitsWritten++; -} - -void VoxelSceneStats::existsInPacketBitsWritten() { - _existsInPacketBitsWritten++; -} - -void VoxelSceneStats::childBitsRemoved(bool includesExistsBits, bool includesColors) { - _existsInPacketBitsWritten--; - if (includesExistsBits) { - _existsBitsWritten--; - } - if (includesColors) { - _colorBitsWritten--; - } - _treesRemoved++; -} - -int VoxelSceneStats::packIntoMessage(unsigned char* destinationBuffer, int availableBytes) { - unsigned char* bufferStart = destinationBuffer; - - int headerLength = populateTypeAndVersion(destinationBuffer, PACKET_TYPE_VOXEL_STATS); - destinationBuffer += headerLength; - - memcpy(destinationBuffer, &_start, sizeof(_start)); - destinationBuffer += sizeof(_start); - memcpy(destinationBuffer, &_end, sizeof(_end)); - destinationBuffer += sizeof(_end); - memcpy(destinationBuffer, &_elapsed, sizeof(_elapsed)); - destinationBuffer += sizeof(_elapsed); - memcpy(destinationBuffer, &_totalEncodeTime, sizeof(_totalEncodeTime)); - destinationBuffer += sizeof(_totalEncodeTime); - memcpy(destinationBuffer, &_isFullScene, sizeof(_isFullScene)); - destinationBuffer += sizeof(_isFullScene); - memcpy(destinationBuffer, &_isMoving, sizeof(_isMoving)); - destinationBuffer += sizeof(_isMoving); - memcpy(destinationBuffer, &_packets, sizeof(_packets)); - destinationBuffer += sizeof(_packets); - memcpy(destinationBuffer, &_bytes, sizeof(_bytes)); - destinationBuffer += sizeof(_bytes); - - memcpy(destinationBuffer, &_totalInternal, sizeof(_totalInternal)); - destinationBuffer += sizeof(_totalInternal); - memcpy(destinationBuffer, &_totalLeaves, sizeof(_totalLeaves)); - destinationBuffer += sizeof(_totalLeaves); - memcpy(destinationBuffer, &_internal, sizeof(_internal)); - destinationBuffer += sizeof(_internal); - memcpy(destinationBuffer, &_leaves, sizeof(_leaves)); - destinationBuffer += sizeof(_leaves); - memcpy(destinationBuffer, &_internalSkippedDistance, sizeof(_internalSkippedDistance)); - destinationBuffer += sizeof(_internalSkippedDistance); - memcpy(destinationBuffer, &_leavesSkippedDistance, sizeof(_leavesSkippedDistance)); - destinationBuffer += sizeof(_leavesSkippedDistance); - memcpy(destinationBuffer, &_internalSkippedOutOfView, sizeof(_internalSkippedOutOfView)); - destinationBuffer += sizeof(_internalSkippedOutOfView); - memcpy(destinationBuffer, &_leavesSkippedOutOfView, sizeof(_leavesSkippedOutOfView)); - destinationBuffer += sizeof(_leavesSkippedOutOfView); - memcpy(destinationBuffer, &_internalSkippedWasInView, sizeof(_internalSkippedWasInView)); - destinationBuffer += sizeof(_internalSkippedWasInView); - memcpy(destinationBuffer, &_leavesSkippedWasInView, sizeof(_leavesSkippedWasInView)); - destinationBuffer += sizeof(_leavesSkippedWasInView); - memcpy(destinationBuffer, &_internalSkippedNoChange, sizeof(_internalSkippedNoChange)); - destinationBuffer += sizeof(_internalSkippedNoChange); - memcpy(destinationBuffer, &_leavesSkippedNoChange, sizeof(_leavesSkippedNoChange)); - destinationBuffer += sizeof(_leavesSkippedNoChange); - memcpy(destinationBuffer, &_internalSkippedOccluded, sizeof(_internalSkippedOccluded)); - destinationBuffer += sizeof(_internalSkippedOccluded); - memcpy(destinationBuffer, &_leavesSkippedOccluded, sizeof(_leavesSkippedOccluded)); - destinationBuffer += sizeof(_leavesSkippedOccluded); - memcpy(destinationBuffer, &_internalColorSent, sizeof(_internalColorSent)); - destinationBuffer += sizeof(_internalColorSent); - memcpy(destinationBuffer, &_leavesColorSent, sizeof(_leavesColorSent)); - destinationBuffer += sizeof(_leavesColorSent); - memcpy(destinationBuffer, &_internalDidntFit, sizeof(_internalDidntFit)); - destinationBuffer += sizeof(_internalDidntFit); - memcpy(destinationBuffer, &_leavesDidntFit, sizeof(_leavesDidntFit)); - destinationBuffer += sizeof(_leavesDidntFit); - memcpy(destinationBuffer, &_colorBitsWritten, sizeof(_colorBitsWritten)); - destinationBuffer += sizeof(_colorBitsWritten); - memcpy(destinationBuffer, &_existsBitsWritten, sizeof(_existsBitsWritten)); - destinationBuffer += sizeof(_existsBitsWritten); - memcpy(destinationBuffer, &_existsInPacketBitsWritten, sizeof(_existsInPacketBitsWritten)); - destinationBuffer += sizeof(_existsInPacketBitsWritten); - memcpy(destinationBuffer, &_treesRemoved, sizeof(_treesRemoved)); - destinationBuffer += sizeof(_treesRemoved); - - // add the root jurisdiction - if (_jurisdictionRoot) { - // copy the - int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_jurisdictionRoot)); - memcpy(destinationBuffer, &bytes, sizeof(bytes)); - destinationBuffer += sizeof(bytes); - memcpy(destinationBuffer, _jurisdictionRoot, bytes); - destinationBuffer += bytes; - - // if and only if there's a root jurisdiction, also include the end nodes - int endNodeCount = _jurisdictionEndNodes.size(); - - memcpy(destinationBuffer, &endNodeCount, sizeof(endNodeCount)); - destinationBuffer += sizeof(endNodeCount); - - for (int i=0; i < endNodeCount; i++) { - unsigned char* endNodeCode = _jurisdictionEndNodes[i]; - int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); - memcpy(destinationBuffer, &bytes, sizeof(bytes)); - destinationBuffer += sizeof(bytes); - memcpy(destinationBuffer, endNodeCode, bytes); - destinationBuffer += bytes; - } - } else { - int bytes = 0; - memcpy(destinationBuffer, &bytes, sizeof(bytes)); - destinationBuffer += sizeof(bytes); - } - - return destinationBuffer - bufferStart; // includes header! -} - -int VoxelSceneStats::unpackFromMessage(unsigned char* sourceBuffer, int availableBytes) { - unsigned char* startPosition = sourceBuffer; - - // increment to push past the packet header - int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer); - sourceBuffer += numBytesPacketHeader; - - memcpy(&_start, sourceBuffer, sizeof(_start)); - sourceBuffer += sizeof(_start); - memcpy(&_end, sourceBuffer, sizeof(_end)); - sourceBuffer += sizeof(_end); - memcpy(&_elapsed, sourceBuffer, sizeof(_elapsed)); - sourceBuffer += sizeof(_elapsed); - memcpy(&_totalEncodeTime, sourceBuffer, sizeof(_totalEncodeTime)); - sourceBuffer += sizeof(_totalEncodeTime); - - memcpy(&_isFullScene, sourceBuffer, sizeof(_isFullScene)); - sourceBuffer += sizeof(_isFullScene); - - if (_isFullScene) { - _lastFullElapsed = _elapsed; - _lastFullTotalEncodeTime = _totalEncodeTime; - } - - memcpy(&_isMoving, sourceBuffer, sizeof(_isMoving)); - sourceBuffer += sizeof(_isMoving); - memcpy(&_packets, sourceBuffer, sizeof(_packets)); - sourceBuffer += sizeof(_packets); - memcpy(&_bytes, sourceBuffer, sizeof(_bytes)); - sourceBuffer += sizeof(_bytes); - - memcpy(&_totalInternal, sourceBuffer, sizeof(_totalInternal)); - sourceBuffer += sizeof(_totalInternal); - memcpy(&_totalLeaves, sourceBuffer, sizeof(_totalLeaves)); - sourceBuffer += sizeof(_totalLeaves); - _totalVoxels = _totalInternal + _totalLeaves; - - memcpy(&_internal, sourceBuffer, sizeof(_internal)); - sourceBuffer += sizeof(_internal); - memcpy(&_leaves, sourceBuffer, sizeof(_leaves)); - sourceBuffer += sizeof(_leaves); - _traversed = _internal + _leaves; - - memcpy(&_internalSkippedDistance, sourceBuffer, sizeof(_internalSkippedDistance)); - sourceBuffer += sizeof(_internalSkippedDistance); - memcpy(&_leavesSkippedDistance, sourceBuffer, sizeof(_leavesSkippedDistance)); - sourceBuffer += sizeof(_leavesSkippedDistance); - _skippedDistance = _internalSkippedDistance + _leavesSkippedDistance; - - memcpy(&_internalSkippedOutOfView, sourceBuffer, sizeof(_internalSkippedOutOfView)); - sourceBuffer += sizeof(_internalSkippedOutOfView); - memcpy(&_leavesSkippedOutOfView, sourceBuffer, sizeof(_leavesSkippedOutOfView)); - sourceBuffer += sizeof(_leavesSkippedOutOfView); - _skippedOutOfView = _internalSkippedOutOfView + _leavesSkippedOutOfView; - - memcpy(&_internalSkippedWasInView, sourceBuffer, sizeof(_internalSkippedWasInView)); - sourceBuffer += sizeof(_internalSkippedWasInView); - memcpy(&_leavesSkippedWasInView, sourceBuffer, sizeof(_leavesSkippedWasInView)); - sourceBuffer += sizeof(_leavesSkippedWasInView); - _skippedWasInView = _internalSkippedWasInView + _leavesSkippedWasInView; - - memcpy(&_internalSkippedNoChange, sourceBuffer, sizeof(_internalSkippedNoChange)); - sourceBuffer += sizeof(_internalSkippedNoChange); - memcpy(&_leavesSkippedNoChange, sourceBuffer, sizeof(_leavesSkippedNoChange)); - sourceBuffer += sizeof(_leavesSkippedNoChange); - _skippedNoChange = _internalSkippedNoChange + _leavesSkippedNoChange; - - memcpy(&_internalSkippedOccluded, sourceBuffer, sizeof(_internalSkippedOccluded)); - sourceBuffer += sizeof(_internalSkippedOccluded); - memcpy(&_leavesSkippedOccluded, sourceBuffer, sizeof(_leavesSkippedOccluded)); - sourceBuffer += sizeof(_leavesSkippedOccluded); - _skippedOccluded = _internalSkippedOccluded + _leavesSkippedOccluded; - - memcpy(&_internalColorSent, sourceBuffer, sizeof(_internalColorSent)); - sourceBuffer += sizeof(_internalColorSent); - memcpy(&_leavesColorSent, sourceBuffer, sizeof(_leavesColorSent)); - sourceBuffer += sizeof(_leavesColorSent); - _colorSent = _internalColorSent + _leavesColorSent; - - memcpy(&_internalDidntFit, sourceBuffer, sizeof(_internalDidntFit)); - sourceBuffer += sizeof(_internalDidntFit); - memcpy(&_leavesDidntFit, sourceBuffer, sizeof(_leavesDidntFit)); - sourceBuffer += sizeof(_leavesDidntFit); - _didntFit = _internalDidntFit + _leavesDidntFit; - - memcpy(&_colorBitsWritten, sourceBuffer, sizeof(_colorBitsWritten)); - sourceBuffer += sizeof(_colorBitsWritten); - memcpy(&_existsBitsWritten, sourceBuffer, sizeof(_existsBitsWritten)); - sourceBuffer += sizeof(_existsBitsWritten); - memcpy(&_existsInPacketBitsWritten, sourceBuffer, sizeof(_existsInPacketBitsWritten)); - sourceBuffer += sizeof(_existsInPacketBitsWritten); - memcpy(&_treesRemoved, sourceBuffer, sizeof(_treesRemoved)); - sourceBuffer += sizeof(_treesRemoved); - - // before allocating new juridiction, clean up existing ones - if (_jurisdictionRoot) { - delete[] _jurisdictionRoot; - _jurisdictionRoot = NULL; - } - - // clear existing endNodes before copying new ones... - for (int i=0; i < _jurisdictionEndNodes.size(); i++) { - if (_jurisdictionEndNodes[i]) { - delete[] _jurisdictionEndNodes[i]; - } - } - _jurisdictionEndNodes.clear(); - - // read the root jurisdiction - int bytes = 0; - memcpy(&bytes, sourceBuffer, sizeof(bytes)); - sourceBuffer += sizeof(bytes); - - if (bytes == 0) { - _jurisdictionRoot = NULL; - _jurisdictionEndNodes.clear(); - } else { - _jurisdictionRoot = new unsigned char[bytes]; - memcpy(_jurisdictionRoot, sourceBuffer, bytes); - sourceBuffer += bytes; - // if and only if there's a root jurisdiction, also include the end nodes - _jurisdictionEndNodes.clear(); - int endNodeCount = 0; - memcpy(&endNodeCount, sourceBuffer, sizeof(endNodeCount)); - sourceBuffer += sizeof(endNodeCount); - for (int i=0; i < endNodeCount; i++) { - int bytes = 0; - memcpy(&bytes, sourceBuffer, sizeof(bytes)); - sourceBuffer += sizeof(bytes); - unsigned char* endNodeCode = new unsigned char[bytes]; - memcpy(endNodeCode, sourceBuffer, bytes); - sourceBuffer += bytes; - _jurisdictionEndNodes.push_back(endNodeCode); - } - } - - // running averages - _elapsedAverage.updateAverage((float)_elapsed); - unsigned long total = _existsInPacketBitsWritten + _colorSent; - float calculatedBPV = total == 0 ? 0 : (_bytes * 8) / total; - _bitsPerVoxelAverage.updateAverage(calculatedBPV); - - - return sourceBuffer - startPosition; // includes header! -} - - -void VoxelSceneStats::printDebugDetails() { - qDebug("\n------------------------------\n"); - qDebug("VoxelSceneStats:\n"); - qDebug(" start : %llu \n", (long long unsigned int)_start); - qDebug(" end : %llu \n", (long long unsigned int)_end); - qDebug(" elapsed : %llu \n", (long long unsigned int)_elapsed); - qDebug(" encoding : %llu \n", (long long unsigned int)_totalEncodeTime); - qDebug("\n"); - qDebug(" full scene: %s\n", debug::valueOf(_isFullScene)); - qDebug(" moving: %s\n", debug::valueOf(_isMoving)); - qDebug("\n"); - qDebug(" packets: %d\n", _packets); - qDebug(" bytes : %ld\n", _bytes); - qDebug("\n"); - qDebug(" total voxels : %lu\n", _totalVoxels ); - qDebug(" internal : %lu\n", _totalInternal ); - qDebug(" leaves : %lu\n", _totalLeaves ); - qDebug(" traversed : %lu\n", _traversed ); - qDebug(" internal : %lu\n", _internal ); - qDebug(" leaves : %lu\n", _leaves ); - qDebug(" skipped distance : %lu\n", _skippedDistance ); - qDebug(" internal : %lu\n", _internalSkippedDistance ); - qDebug(" leaves : %lu\n", _leavesSkippedDistance ); - qDebug(" skipped out of view : %lu\n", _skippedOutOfView ); - qDebug(" internal : %lu\n", _internalSkippedOutOfView ); - qDebug(" leaves : %lu\n", _leavesSkippedOutOfView ); - qDebug(" skipped was in view : %lu\n", _skippedWasInView ); - qDebug(" internal : %lu\n", _internalSkippedWasInView ); - qDebug(" leaves : %lu\n", _leavesSkippedWasInView ); - qDebug(" skipped no change : %lu\n", _skippedNoChange ); - qDebug(" internal : %lu\n", _internalSkippedNoChange ); - qDebug(" leaves : %lu\n", _leavesSkippedNoChange ); - qDebug(" skipped occluded : %lu\n", _skippedOccluded ); - qDebug(" internal : %lu\n", _internalSkippedOccluded ); - qDebug(" leaves : %lu\n", _leavesSkippedOccluded ); - - qDebug("\n"); - qDebug(" color sent : %lu\n", _colorSent ); - qDebug(" internal : %lu\n", _internalColorSent ); - qDebug(" leaves : %lu\n", _leavesColorSent ); - qDebug(" Didn't Fit : %lu\n", _didntFit ); - qDebug(" internal : %lu\n", _internalDidntFit ); - qDebug(" leaves : %lu\n", _leavesDidntFit ); - qDebug(" color bits : %lu\n", _colorBitsWritten ); - qDebug(" exists bits : %lu\n", _existsBitsWritten ); - qDebug(" in packet bit : %lu\n", _existsInPacketBitsWritten); - qDebug(" trees removed : %lu\n", _treesRemoved ); -} - -VoxelSceneStats::ItemInfo VoxelSceneStats::_ITEMS[] = { - { "Elapsed" , GREENISH , 2 , "Elapsed,fps" }, - { "Encode" , YELLOWISH , 2 , "Time,fps" }, - { "Network" , GREYISH , 3 , "Packets,Bytes,KBPS" }, - { "Voxels on Server" , GREENISH , 3 , "Total,Internal,Leaves" }, - { "Voxels Sent" , YELLOWISH , 5 , "Total,Bits/Voxel,Avg Bits/Voxel,Internal,Leaves" }, - { "Colors Sent" , GREYISH , 3 , "Total,Internal,Leaves" }, - { "Bitmasks Sent" , GREENISH , 3 , "Colors,Exists,In Packets" }, - { "Traversed" , YELLOWISH , 3 , "Total,Internal,Leaves" }, - { "Skipped - Total" , GREYISH , 3 , "Total,Internal,Leaves" }, - { "Skipped - Distance" , GREENISH , 3 , "Total,Internal,Leaves" }, - { "Skipped - Out of View", YELLOWISH , 3 , "Total,Internal,Leaves" }, - { "Skipped - Was in View", GREYISH , 3 , "Total,Internal,Leaves" }, - { "Skipped - No Change" , GREENISH , 3 , "Total,Internal,Leaves" }, - { "Skipped - Occluded" , YELLOWISH , 3 , "Total,Internal,Leaves" }, - { "Didn't fit in packet" , GREYISH , 4 , "Total,Internal,Leaves,Removed" }, - { "Mode" , GREENISH , 4 , "Moving,Stationary,Partial,Full" }, -}; - -const char* VoxelSceneStats::getItemValue(Item item) { - const uint64_t USECS_PER_SECOND = 1000 * 1000; - int calcFPS, calcAverageFPS, calculatedKBPS; - switch(item) { - case ITEM_ELAPSED: { - calcFPS = (float)USECS_PER_SECOND / (float)_elapsed; - float elapsedAverage = _elapsedAverage.getAverage(); - calcAverageFPS = (float)USECS_PER_SECOND / (float)elapsedAverage; - - sprintf(_itemValueBuffer, "%llu usecs (%d fps) Average: %.0f usecs (%d fps)", - (long long unsigned int)_elapsed, calcFPS, elapsedAverage, calcAverageFPS); - break; - } - case ITEM_ENCODE: - calcFPS = (float)USECS_PER_SECOND / (float)_totalEncodeTime; - sprintf(_itemValueBuffer, "%llu usecs (%d fps)", (long long unsigned int)_totalEncodeTime, calcFPS); - break; - case ITEM_PACKETS: { - float elapsedSecs = ((float)_elapsed / (float)USECS_PER_SECOND); - calculatedKBPS = elapsedSecs == 0 ? 0 : ((_bytes * 8) / elapsedSecs) / 1000; - sprintf(_itemValueBuffer, "%d packets %lu bytes (%d kbps)", _packets, _bytes, calculatedKBPS); - break; - } - case ITEM_VOXELS_SERVER: { - sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", - _totalVoxels, _totalInternal, _totalLeaves); - break; - } - case ITEM_VOXELS: { - unsigned long total = _existsInPacketBitsWritten + _colorSent; - float calculatedBPV = total == 0 ? 0 : (_bytes * 8) / total; - float averageBPV = _bitsPerVoxelAverage.getAverage(); - sprintf(_itemValueBuffer, "%lu (%.2f bits/voxel Average: %.2f bits/voxel) %lu internal %lu leaves", - total, calculatedBPV, averageBPV, _existsInPacketBitsWritten, _colorSent); - break; - } - case ITEM_TRAVERSED: { - sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", - _traversed, _internal, _leaves); - break; - } - case ITEM_SKIPPED: { - unsigned long total = _skippedDistance + _skippedOutOfView + - _skippedWasInView + _skippedNoChange + _skippedOccluded; - - unsigned long internal = _internalSkippedDistance + _internalSkippedOutOfView + - _internalSkippedWasInView + _internalSkippedNoChange + _internalSkippedOccluded; - - unsigned long leaves = _leavesSkippedDistance + _leavesSkippedOutOfView + - _leavesSkippedWasInView + _leavesSkippedNoChange + _leavesSkippedOccluded; - - sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", - total, internal, leaves); - break; - } - case ITEM_SKIPPED_DISTANCE: { - sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", - _skippedDistance, _internalSkippedDistance, _leavesSkippedDistance); - break; - } - case ITEM_SKIPPED_OUT_OF_VIEW: { - sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", - _skippedOutOfView, _internalSkippedOutOfView, _leavesSkippedOutOfView); - break; - } - case ITEM_SKIPPED_WAS_IN_VIEW: { - sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", - _skippedWasInView, _internalSkippedWasInView, _leavesSkippedWasInView); - break; - } - case ITEM_SKIPPED_NO_CHANGE: { - sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", - _skippedNoChange, _internalSkippedNoChange, _leavesSkippedNoChange); - break; - } - case ITEM_SKIPPED_OCCLUDED: { - sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", - _skippedOccluded, _internalSkippedOccluded, _leavesSkippedOccluded); - break; - } - case ITEM_COLORS: { - sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", - _colorSent, _internalColorSent, _leavesColorSent); - break; - } - case ITEM_DIDNT_FIT: { - sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves (removed: %lu)", - _didntFit, _internalDidntFit, _leavesDidntFit, _treesRemoved); - break; - } - case ITEM_BITS: { - sprintf(_itemValueBuffer, "colors: %lu, exists: %lu, in packets: %lu", - _colorBitsWritten, _existsBitsWritten, _existsInPacketBitsWritten); - break; - } - case ITEM_MODE: { - sprintf(_itemValueBuffer, "%s - %s", (_isFullScene ? "Full Scene" : "Partial Scene"), - (_isMoving ? "Moving" : "Stationary")); - break; - } - default: - sprintf(_itemValueBuffer, ""); - break; - } - return _itemValueBuffer; -} - -void VoxelSceneStats::trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength, bool wasStatsPacket) { - _incomingPacket++; - _incomingBytes += messageLength; - if (!wasStatsPacket) { - _incomingWastedBytes += (MAX_PACKET_SIZE - messageLength); - } - - int numBytesPacketHeader = numBytesForPacketHeader(messageData); - unsigned char* dataAt = messageData + numBytesPacketHeader; - - //VOXEL_PACKET_FLAGS flags = (*(VOXEL_PACKET_FLAGS*)(dataAt)); - dataAt += sizeof(VOXEL_PACKET_FLAGS); - VOXEL_PACKET_SEQUENCE sequence = (*(VOXEL_PACKET_SEQUENCE*)dataAt); - dataAt += sizeof(VOXEL_PACKET_SEQUENCE); - - VOXEL_PACKET_SENT_TIME sentAt = (*(VOXEL_PACKET_SENT_TIME*)dataAt); - dataAt += sizeof(VOXEL_PACKET_SENT_TIME); - - //bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT); - //bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT); - - VOXEL_PACKET_SENT_TIME arrivedAt = usecTimestampNow(); - int flightTime = arrivedAt - sentAt; - const int USECS_PER_MSEC = 1000; - float flightTimeMsecs = flightTime / USECS_PER_MSEC; - _incomingFlightTimeAverage.updateAverage(flightTimeMsecs); - - - // detect out of order packets - if (sequence < _incomingLastSequence) { - _incomingOutOfOrder++; - } - - // detect likely lost packets - VOXEL_PACKET_SEQUENCE expected = _incomingLastSequence+1; - if (sequence > expected) { - _incomingLikelyLost++; - } - - _incomingLastSequence = sequence; -} +// currently an alias for OctreeSceneStats diff --git a/libraries/voxels/src/VoxelSceneStats.h b/libraries/voxels/src/VoxelSceneStats.h index 526ccffadb..bea563563c 100644 --- a/libraries/voxels/src/VoxelSceneStats.h +++ b/libraries/voxels/src/VoxelSceneStats.h @@ -10,262 +10,14 @@ #ifndef __hifi__VoxelSceneStats__ #define __hifi__VoxelSceneStats__ -#include #include -#include "JurisdictionMap.h" - -#define GREENISH 0x40ff40d0 -#define YELLOWISH 0xffef40c0 -#define GREYISH 0xd0d0d0a0 - -class VoxelNode; +#include /// Collects statistics for calculating and sending a scene from a voxel server to an interface client -class VoxelSceneStats { -public: - VoxelSceneStats(); - ~VoxelSceneStats(); - void reset(); +class VoxelSceneStats : public OctreeSceneStats { + + // currently an alias for OctreeSceneStats - VoxelSceneStats(const VoxelSceneStats& other); // copy constructor - VoxelSceneStats& operator= (const VoxelSceneStats& other); // copy assignment - - /// Call when beginning the computation of a scene. Initializes internal structures - void sceneStarted(bool fullScene, bool moving, VoxelNode* root, JurisdictionMap* jurisdictionMap); - bool getIsSceneStarted() const { return _isStarted; } - - /// Call when the computation of a scene is completed. Finalizes internal structures - void sceneCompleted(); - - void printDebugDetails(); - - /// Track that a packet was sent as part of the scene. - void packetSent(int bytes); - - /// Tracks the beginning of an encode pass during scene calculation. - void encodeStarted(); - - /// Tracks the ending of an encode pass during scene calculation. - void encodeStopped(); - - /// Track that a node was traversed as part of computation of a scene. - void traversed(const VoxelNode* node); - - /// Track that a node was skipped as part of computation of a scene due to being beyond the LOD distance. - void skippedDistance(const VoxelNode* node); - - /// Track that a node was skipped as part of computation of a scene due to being out of view. - void skippedOutOfView(const VoxelNode* node); - - /// Track that a node was skipped as part of computation of a scene due to previously being in view while in delta sending - void skippedWasInView(const VoxelNode* node); - - /// Track that a node was skipped as part of computation of a scene due to not having changed since last full scene sent - void skippedNoChange(const VoxelNode* node); - - /// Track that a node was skipped as part of computation of a scene due to being occluded - void skippedOccluded(const VoxelNode* node); - - /// Track that a node's color was was sent as part of computation of a scene - void colorSent(const VoxelNode* node); - - /// Track that a node was due to be sent, but didn't fit in the packet and was moved to next packet - void didntFit(const VoxelNode* node); - - /// Track that the color bitmask was was sent as part of computation of a scene - void colorBitsWritten(); - - /// Track that the exists in tree bitmask was was sent as part of computation of a scene - void existsBitsWritten(); - - /// Track that the exists in packet bitmask was was sent as part of computation of a scene - void existsInPacketBitsWritten(); - - /// Fix up tracking statistics in case where bitmasks were removed for some reason - void childBitsRemoved(bool includesExistsBits, bool includesColors); - - /// Pack the details of the statistics into a buffer for sending as a network packet - int packIntoMessage(unsigned char* destinationBuffer, int availableBytes); - - /// Unpack the details of the statistics from a buffer typically received as a network packet - int unpackFromMessage(unsigned char* sourceBuffer, int availableBytes); - - /// Indicates that a scene has been completed and the statistics are ready to be sent - bool isReadyToSend() const { return _isReadyToSend; } - - /// Mark that the scene statistics have been sent - void markAsSent() { _isReadyToSend = false; } - - unsigned char* getStatsMessage() { return &_statsMessage[0]; } - int getStatsMessageLength() const { return _statsMessageLength; } - - /// List of various items tracked by VoxelSceneStats which can be accessed via getItemInfo() and getItemValue() - enum Item { - ITEM_ELAPSED, - ITEM_ENCODE, - ITEM_PACKETS, - ITEM_VOXELS_SERVER, - ITEM_VOXELS, - ITEM_COLORS, - ITEM_BITS, - ITEM_TRAVERSED, - ITEM_SKIPPED, - ITEM_SKIPPED_DISTANCE, - ITEM_SKIPPED_OUT_OF_VIEW, - ITEM_SKIPPED_WAS_IN_VIEW, - ITEM_SKIPPED_NO_CHANGE, - ITEM_SKIPPED_OCCLUDED, - ITEM_DIDNT_FIT, - ITEM_MODE, - ITEM_COUNT - }; - - /// Meta information about each stats item - struct ItemInfo { - char const* const caption; - unsigned colorRGBA; - int detailsCount; - const char* detailsLabels; - }; - - /// Returns details about items tracked by VoxelSceneStats - /// \param Item item The item from the stats you're interested in. - ItemInfo& getItemInfo(Item item) { return _ITEMS[item]; } - - /// Returns a UI formatted value of an item tracked by VoxelSceneStats - /// \param Item item The item from the stats you're interested in. - const char* getItemValue(Item item); - - /// Returns OctCode for root node of the jurisdiction of this particular voxel server - unsigned char* getJurisdictionRoot() const { return _jurisdictionRoot; } - - /// Returns list of OctCodes for end nodes of the jurisdiction of this particular voxel server - const std::vector& getJurisdictionEndNodes() const { return _jurisdictionEndNodes; } - - bool isMoving() const { return _isMoving; }; - unsigned long getTotalVoxels() const { return _totalVoxels; } - unsigned long getTotalInternal() const { return _totalInternal; } - unsigned long getTotalLeaves() const { return _totalLeaves; } - unsigned long getTotalEncodeTime() const { return _totalEncodeTime; } - unsigned long getElapsedTime() const { return _elapsed; } - - unsigned long getLastFullTotalEncodeTime() const { return _lastFullTotalEncodeTime; } - unsigned long getLastFullElapsedTime() const { return _lastFullElapsed; } - - // Used in client implementations to track individual voxel packets - void trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength, bool wasStatsPacket); - - unsigned int getIncomingPackets() const { return _incomingPacket; } - unsigned long getIncomingBytes() const { return _incomingBytes; } - unsigned long getIncomingWastedBytes() const { return _incomingWastedBytes; } - unsigned int getIncomingOutOfOrder() const { return _incomingOutOfOrder; } - unsigned int getIncomingLikelyLost() const { return _incomingLikelyLost; } - float getIncomingFlightTimeAverage() { return _incomingFlightTimeAverage.getAverage(); } - -private: - - void copyFromOther(const VoxelSceneStats& other); - - bool _isReadyToSend; - unsigned char _statsMessage[MAX_PACKET_SIZE]; - int _statsMessageLength; - - // scene timing data in usecs - bool _isStarted; - uint64_t _start; - uint64_t _end; - uint64_t _elapsed; - uint64_t _lastFullElapsed; - - SimpleMovingAverage _elapsedAverage; - SimpleMovingAverage _bitsPerVoxelAverage; - - uint64_t _totalEncodeTime; - uint64_t _lastFullTotalEncodeTime; - uint64_t _encodeStart; - - // scene voxel related data - unsigned long _totalVoxels; - unsigned long _totalInternal; - unsigned long _totalLeaves; - - unsigned long _traversed; - unsigned long _internal; - unsigned long _leaves; - - unsigned long _skippedDistance; - unsigned long _internalSkippedDistance; - unsigned long _leavesSkippedDistance; - - unsigned long _skippedOutOfView; - unsigned long _internalSkippedOutOfView; - unsigned long _leavesSkippedOutOfView; - - unsigned long _skippedWasInView; - unsigned long _internalSkippedWasInView; - unsigned long _leavesSkippedWasInView; - - unsigned long _skippedNoChange; - unsigned long _internalSkippedNoChange; - unsigned long _leavesSkippedNoChange; - - unsigned long _skippedOccluded; - unsigned long _internalSkippedOccluded; - unsigned long _leavesSkippedOccluded; - - unsigned long _colorSent; - unsigned long _internalColorSent; - unsigned long _leavesColorSent; - - unsigned long _didntFit; - unsigned long _internalDidntFit; - unsigned long _leavesDidntFit; - - unsigned long _colorBitsWritten; - unsigned long _existsBitsWritten; - unsigned long _existsInPacketBitsWritten; - unsigned long _treesRemoved; - - // Accounting Notes: - // - // 1) number of voxels sent can be calculated as _colorSent + _colorBitsWritten. This works because each internal - // node in a packet will have a _colorBitsWritten included for it and each "leaf" in the packet will have a - // _colorSent written for it. Note that these "leaf" nodes in the packets may not be actual leaves in the full - // tree, because LOD may cause us to send an average color for an internal node instead of recursing deeper to - // the leaves. - // - // 2) the stats balance if: (working assumption) - // if _colorSent > 0 - // _traversed = all skipped + _colorSent + _colorBitsWritten - // else - // _traversed = all skipped + _colorSent + _colorBitsWritten + _treesRemoved - // - - // scene network related data - unsigned int _packets; - unsigned long _bytes; - unsigned int _passes; - - // incoming packets stats - unsigned int _incomingPacket; - unsigned long _incomingBytes; - unsigned long _incomingWastedBytes; - unsigned int _incomingLastSequence; - unsigned int _incomingOutOfOrder; - unsigned int _incomingLikelyLost; - SimpleMovingAverage _incomingFlightTimeAverage; - - // features related items - bool _isMoving; - bool _isFullScene; - - - static ItemInfo _ITEMS[]; - static int const MAX_ITEM_VALUE_LENGTH = 128; - char _itemValueBuffer[MAX_ITEM_VALUE_LENGTH]; - - unsigned char* _jurisdictionRoot; - std::vector _jurisdictionEndNodes; }; /// Map between node IDs and their reported VoxelSceneStats. Typically used by classes that need to know which nodes sent diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index 450f784765..172a9b2ef2 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -6,760 +6,49 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // -#ifdef _WIN32 -#define _USE_MATH_DEFINES -#endif - -#include -#include -#include -#include // to load voxels from file - #include -#include +//#include #include #include -#include "CoverageMap.h" -#include "GeometryUtil.h" -#include "OctalCode.h" -#include "PacketHeaders.h" -#include "SharedUtil.h" -#include "Tags.h" -#include "ViewFrustum.h" -#include "VoxelConstants.h" -#include "VoxelNodeBag.h" + #include "VoxelTree.h" -#include +#include "Tags.h" -float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) { - return voxelSizeScale / powf(2, renderLevel); +// Voxel Specific operations.... + +VoxelTree::VoxelTree(bool shouldReaverage) : Octree(shouldReaverage) { + _rootNode = createNewElement(); } -VoxelTree::VoxelTree(bool shouldReaverage) : - voxelsCreated(0), - voxelsColored(0), - voxelsBytesRead(0), - voxelsCreatedStats(100), - voxelsColoredStats(100), - voxelsBytesReadStats(100), - _isDirty(true), - _shouldReaverage(shouldReaverage), - _stopImport(false) { - rootNode = new VoxelNode(); - - pthread_mutex_init(&_encodeSetLock, NULL); - pthread_mutex_init(&_deleteSetLock, NULL); - pthread_mutex_init(&_deletePendingSetLock, NULL); +VoxelTreeElement* VoxelTree::createNewElement(unsigned char * octalCode) const { + VoxelSystem* voxelSystem = NULL; + if (_rootNode) { + voxelSystem = ((VoxelTreeElement*)_rootNode)->getVoxelSystem(); + } + VoxelTreeElement* newElement = new VoxelTreeElement(octalCode); + newElement->setVoxelSystem(voxelSystem); + return newElement; } -VoxelTree::~VoxelTree() { - // delete the children of the root node - // this recursively deletes the tree - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - delete rootNode->getChildAtIndex(i); - } - - pthread_mutex_destroy(&_encodeSetLock); - pthread_mutex_destroy(&_deleteSetLock); - pthread_mutex_destroy(&_deletePendingSetLock); -} - -// Recurses voxel tree calling the RecurseVoxelTreeOperation function for each node. -// stops recursion if operation function returns false. -void VoxelTree::recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData) { - recurseNodeWithOperation(rootNode, operation, extraData); -} - -// Recurses voxel node with an operation function -void VoxelTree::recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData, - int recursionCount) { - if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - qDebug() << "VoxelTree::recurseNodeWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n"; - return; - } - - if (operation(node, extraData)) { - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* child = node->getChildAtIndex(i); - if (child) { - recurseNodeWithOperation(child, operation, extraData, recursionCount+1); - } - } - } -} - -// Recurses voxel tree calling the RecurseVoxelTreeOperation function for each node. -// stops recursion if operation function returns false. -void VoxelTree::recurseTreeWithOperationDistanceSorted(RecurseVoxelTreeOperation operation, - const glm::vec3& point, void* extraData) { - - recurseNodeWithOperationDistanceSorted(rootNode, operation, point, extraData); -} - -// Recurses voxel node with an operation function -void VoxelTree::recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation, - const glm::vec3& point, void* extraData, int recursionCount) { - - if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - qDebug() << "VoxelTree::recurseNodeWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n"; - return; - } - - if (operation(node, extraData)) { - // determine the distance sorted order of our children - VoxelNode* sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; - float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - int currentCount = 0; - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* childNode = node->getChildAtIndex(i); - if (childNode) { - // chance to optimize, doesn't need to be actual distance!! Could be distance squared - float distanceSquared = childNode->distanceSquareToPoint(point); - //qDebug("recurseNodeWithOperationDistanceSorted() CHECKING child[%d] point=%f,%f center=%f,%f distance=%f...\n", i, point.x, point.y, center.x, center.y, distance); - //childNode->printDebugDetails(""); - currentCount = insertIntoSortedArrays((void*)childNode, distanceSquared, i, - (void**)&sortedChildren, (float*)&distancesToChildren, - (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); - } - } - - for (int i = 0; i < currentCount; i++) { - VoxelNode* childNode = sortedChildren[i]; - if (childNode) { - //qDebug("recurseNodeWithOperationDistanceSorted() PROCESSING child[%d] distance=%f...\n", i, distancesToChildren[i]); - //childNode->printDebugDetails(""); - recurseNodeWithOperationDistanceSorted(childNode, operation, point, extraData); - } - } - } -} - - -VoxelNode* VoxelTree::nodeForOctalCode(VoxelNode* ancestorNode, - const unsigned char* needleCode, VoxelNode** parentOfFoundNode) const { - // find the appropriate branch index based on this ancestorNode - if (*needleCode > 0) { - int branchForNeedle = branchIndexWithDescendant(ancestorNode->getOctalCode(), needleCode); - VoxelNode* childNode = ancestorNode->getChildAtIndex(branchForNeedle); - - if (childNode) { - if (*childNode->getOctalCode() == *needleCode) { - - // If the caller asked for the parent, then give them that too... - if (parentOfFoundNode) { - *parentOfFoundNode = ancestorNode; - } - // the fact that the number of sections is equivalent does not always guarantee - // that this is the same node, however due to the recursive traversal - // we know that this is our node - return childNode; - } else { - // we need to go deeper - return nodeForOctalCode(childNode, needleCode, parentOfFoundNode); - } - } - } - - // we've been given a code we don't have a node for - // return this node as the last created parent - return ancestorNode; -} - -// returns the node created! -VoxelNode* VoxelTree::createMissingNode(VoxelNode* lastParentNode, const unsigned char* 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 - // 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++) { - lastParentNode->addChildAtIndex(i); - 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); - } - - // This works because we know we traversed down the same tree so if the length is the same, then the whole code is the same - if (*lastParentNode->getChildAtIndex(indexOfNewChild)->getOctalCode() == *codeToReach) { - return lastParentNode->getChildAtIndex(indexOfNewChild); - } else { - return createMissingNode(lastParentNode->getChildAtIndex(indexOfNewChild), codeToReach); - } -} - -int VoxelTree::readNodeData(VoxelNode* destinationNode, const unsigned char* nodeData, int bytesLeftToRead, - ReadBitstreamToTreeParams& args) { - // 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 = sizeof(colorInPacketMask); - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - // check the colors mask to see if we have a child to color in - if (oneAtBit(colorInPacketMask, i)) { - // create the child if it doesn't exist - if (!destinationNode->getChildAtIndex(i)) { - destinationNode->addChildAtIndex(i); - if (destinationNode->isDirty()) { - _isDirty = true; - _nodesChangedFromBitstream++; - } - voxelsCreated++; - voxelsCreatedStats.updateAverage(1); - } - - // pull the color for this child - nodeColor newColor = { 128, 128, 128, 1}; - if (args.includeColor) { - memcpy(newColor, nodeData + bytesRead, 3); - bytesRead += 3; - } - VoxelNode* childNodeAt = destinationNode->getChildAtIndex(i); - bool nodeWasDirty = false; - bool nodeIsDirty = false; - if (childNodeAt) { - nodeWasDirty = childNodeAt->isDirty(); - childNodeAt->setColor(newColor); - childNodeAt->setSourceUUID(args.sourceUUID); - - // if we had a local version of the node already, it's possible that we have it in the VBO but - // with the same color data, so this won't count as a change. To address this we check the following - if (!childNodeAt->isDirty() && !childNodeAt->isKnownBufferIndex() && childNodeAt->getShouldRender()) { - childNodeAt->setDirtyBit(); // force dirty! - } - - nodeIsDirty = childNodeAt->isDirty(); - } - if (nodeIsDirty) { - _isDirty = true; - } - if (!nodeWasDirty && nodeIsDirty) { - _nodesChangedFromBitstream++; - } - this->voxelsColored++; - this->voxelsColoredStats.updateAverage(1); - } - } - - // give this destination node the child mask from the packet - unsigned char childrenInTreeMask = args.includeExistsBits ? *(nodeData + bytesRead) : ALL_CHILDREN_ASSUMED_TO_EXIST; - unsigned char childMask = *(nodeData + bytesRead + (args.includeExistsBits ? sizeof(childrenInTreeMask) : 0)); - - int childIndex = 0; - bytesRead += args.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 - - if (oneAtBit(childMask, childIndex)) { - if (!destinationNode->getChildAtIndex(childIndex)) { - // add a child at that index, if it doesn't exist - bool nodeWasDirty = destinationNode->isDirty(); - destinationNode->addChildAtIndex(childIndex); - bool nodeIsDirty = destinationNode->isDirty(); - if (nodeIsDirty) { - _isDirty = true; - } - if (!nodeWasDirty && nodeIsDirty) { - _nodesChangedFromBitstream++; - } - this->voxelsCreated++; - this->voxelsCreatedStats.updateAverage(this->voxelsCreated); - } - - // tell the child to read the subsequent data - bytesRead += readNodeData(destinationNode->getChildAtIndex(childIndex), - nodeData + bytesRead, bytesLeftToRead - bytesRead, args); - } - childIndex++; - } - - if (args.includeExistsBits) { - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - // now also check the childrenInTreeMask, if the mask is missing the bit, then it means we need to delete this child - // subtree/node, because it shouldn't actually exist in the tree. - if (!oneAtBit(childrenInTreeMask, i) && destinationNode->getChildAtIndex(i)) { - destinationNode->safeDeepDeleteChildAtIndex(i); - _isDirty = true; // by definition! - } - } - } - return bytesRead; -} - -void VoxelTree::readBitstreamToTree(const unsigned char * bitstream, unsigned long int bufferSizeBytes, - ReadBitstreamToTreeParams& args) { - int bytesRead = 0; - const unsigned char* bitstreamAt = bitstream; - - // If destination node is not included, set it to root - if (!args.destinationNode) { - args.destinationNode = rootNode; - } - - _nodesChangedFromBitstream = 0; - - // Keep looping through the buffer calling readNodeData() this allows us to pack multiple root-relative Octal codes - // into a single network packet. readNodeData() basically goes down a tree from the root, and fills things in from there - // if there are more bytes after that, it's assumed to be another root relative tree - - while (bitstreamAt < bitstream + bufferSizeBytes) { - VoxelNode* bitstreamRootNode = nodeForOctalCode(args.destinationNode, (unsigned char *)bitstreamAt, NULL); - if (*bitstreamAt != *bitstreamRootNode->getOctalCode()) { - // if the octal code returned is not on the same level as - // the code being searched for, we have VoxelNodes to create - - // Note: we need to create this node relative to root, because we're assuming that the bitstream for the initial - // octal code is always relative to root! - bitstreamRootNode = createMissingNode(args.destinationNode, (unsigned char*) bitstreamAt); - if (bitstreamRootNode->isDirty()) { - _isDirty = true; - _nodesChangedFromBitstream++; - } - } - - int octalCodeBytes = bytesRequiredForCodeLength(*bitstreamAt); - int theseBytesRead = 0; - theseBytesRead += octalCodeBytes; - - theseBytesRead += readNodeData(bitstreamRootNode, bitstreamAt + octalCodeBytes, - bufferSizeBytes - (bytesRead + octalCodeBytes), args); - - // skip bitstream to new startPoint - bitstreamAt += theseBytesRead; - bytesRead += theseBytesRead; - - if (args.wantImportProgress) { - emit importProgress((100 * (bitstreamAt - bitstream)) / bufferSizeBytes); - } - } - - this->voxelsBytesRead += bufferSizeBytes; - this->voxelsBytesReadStats.updateAverage(bufferSizeBytes); -} void VoxelTree::deleteVoxelAt(float x, float y, float z, float s) { - unsigned char* octalCode = pointToVoxel(x,y,z,s,0,0,0); - deleteVoxelCodeFromTree(octalCode); - delete[] octalCode; // cleanup memory + deleteOctreeElementAt(x, y, z, s); } -class DeleteVoxelCodeFromTreeArgs { -public: - bool collapseEmptyTrees; - const unsigned char* codeBuffer; - int lengthOfCode; - bool deleteLastChild; - bool pathChanged; -}; - -// Note: uses the codeColorBuffer format, but the color's are ignored, because -// this only finds and deletes the node from the tree. -void VoxelTree::deleteVoxelCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees) { - // recurse the tree while decoding the codeBuffer, once you find the node in question, recurse - // back and implement color reaveraging, and marking of lastChanged - DeleteVoxelCodeFromTreeArgs args; - args.collapseEmptyTrees = collapseEmptyTrees; - args.codeBuffer = codeBuffer; - args.lengthOfCode = numberOfThreeBitSectionsInCode(codeBuffer); - args.deleteLastChild = false; - args.pathChanged = false; - - VoxelNode* node = rootNode; - - // We can't encode and delete nodes at the same time, so we guard against deleting any node that is actively - // being encoded. And we stick that code on our pendingDelete list. - if (isEncoding(codeBuffer)) { - queueForLaterDelete(codeBuffer); - } else { - startDeleting(codeBuffer); - deleteVoxelCodeFromTreeRecursion(node, &args); - doneDeleting(codeBuffer); - } -} - -void VoxelTree::deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraData) { - DeleteVoxelCodeFromTreeArgs* args = (DeleteVoxelCodeFromTreeArgs*)extraData; - - int lengthOfNodeCode = numberOfThreeBitSectionsInCode(node->getOctalCode()); - - // Since we traverse the tree in code order, we know that if our code - // matches, then we've reached our target node. - if (lengthOfNodeCode == args->lengthOfCode) { - // we've reached our target, depending on how we're called we may be able to operate on it - // it here, we need to recurse up, and delete it there. So we handle these cases the same to keep - // the logic consistent. - args->deleteLastChild = true; - return; - } - - // Ok, we know we haven't reached our target node yet, so keep looking - int childIndex = branchIndexWithDescendant(node->getOctalCode(), args->codeBuffer); - VoxelNode* childNode = node->getChildAtIndex(childIndex); - - // If there is no child at the target location, and the current parent node is a colored leaf, - // then it means we were asked to delete a child out of a larger leaf voxel. - // We support this by breaking up the parent voxel into smaller pieces. - if (!childNode && node->isLeaf() && node->isColored()) { - // we need to break up ancestors until we get to the right level - VoxelNode* ancestorNode = node; - while (true) { - int index = branchIndexWithDescendant(ancestorNode->getOctalCode(), args->codeBuffer); - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (i != index) { - ancestorNode->addChildAtIndex(i); - if (node->isColored()) { - ancestorNode->getChildAtIndex(i)->setColor(node->getColor()); - } - } - } - int lengthOfAncestorNode = numberOfThreeBitSectionsInCode(ancestorNode->getOctalCode()); - - // If we've reached the parent of the target, then stop breaking up children - if (lengthOfAncestorNode == (args->lengthOfCode - 1)) { - break; - } - ancestorNode->addChildAtIndex(index); - ancestorNode = ancestorNode->getChildAtIndex(index); - if (node->isColored()) { - ancestorNode->setColor(node->getColor()); - } - } - _isDirty = true; - args->pathChanged = true; - - // ends recursion, unwinds up stack - return; - } - - // if we don't have a child and we reach this point, then we actually know that the parent - // isn't a colored leaf, and the child branch doesn't exist, so there's nothing to do below and - // we can safely return, ending the recursion and unwinding - if (!childNode) { - //qDebug("new___deleteVoxelCodeFromTree() child branch doesn't exist, but parent is not a leaf, just unwind\n"); - return; - } - - // If we got this far then we have a child for the branch we're looking for, but we're not there yet - // recurse till we get there - deleteVoxelCodeFromTreeRecursion(childNode, args); - - // If the lower level determined it needs to be deleted, then we should delete now. - if (args->deleteLastChild) { - node->deleteChildAtIndex(childIndex); // note: this will track dirtiness and lastChanged for this node - - // track our tree dirtiness - _isDirty = true; - - // track that path has changed - args->pathChanged = true; - - // If we're in collapseEmptyTrees mode, and this was the last child of this node, then we also want - // to delete this node. This will collapse the empty tree above us. - if (args->collapseEmptyTrees && node->getChildCount() == 0) { - // Can't delete the root this way. - if (node == rootNode) { - args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything - } - } else { - args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything - } - } - - // If the lower level did some work, then we need to let this node know, so it can - // do any bookkeeping it wants to, like color re-averaging, time stamp marking, etc - if (args->pathChanged) { - node->handleSubtreeChanged(this); - } -} - -void VoxelTree::eraseAllVoxels() { - // XXXBHG Hack attack - is there a better way to erase the voxel tree? - delete rootNode; // this will recurse and delete all children - VoxelSystem* voxelSystem = rootNode->getVoxelSystem(); - rootNode = new VoxelNode(); - rootNode->setVoxelSystem(voxelSystem); - _isDirty = true; -} - -class ReadCodeColorBufferToTreeArgs { -public: - const unsigned char* codeColorBuffer; - int lengthOfCode; - bool destructive; - bool pathChanged; -}; - -void VoxelTree::readCodeColorBufferToTree(const unsigned char* codeColorBuffer, bool destructive) { - ReadCodeColorBufferToTreeArgs args; - args.codeColorBuffer = codeColorBuffer; - args.lengthOfCode = numberOfThreeBitSectionsInCode(codeColorBuffer); - args.destructive = destructive; - args.pathChanged = false; - - - VoxelNode* node = rootNode; - - readCodeColorBufferToTreeRecursion(node, &args); -} - - -void VoxelTree::readCodeColorBufferToTreeRecursion(VoxelNode* node, void* extraData) { - ReadCodeColorBufferToTreeArgs* args = (ReadCodeColorBufferToTreeArgs*)extraData; - - int lengthOfNodeCode = numberOfThreeBitSectionsInCode(node->getOctalCode()); - - // Since we traverse the tree in code order, we know that if our code - // matches, then we've reached our target node. - if (lengthOfNodeCode == args->lengthOfCode) { - // we've reached our target -- we might have found our node, but that node might have children. - // in this case, we only allow you to set the color if you explicitly asked for a destructive - // write. - if (!node->isLeaf() && args->destructive) { - // if it does exist, make sure it has no children - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - node->deleteChildAtIndex(i); - } - } else { - if (!node->isLeaf()) { - qDebug("WARNING! operation would require deleting children, add Voxel ignored!\n "); - } - } - - // If we get here, then it means, we either had a true leaf to begin with, or we were in - // destructive mode and we deleted all the child trees. So we can color. - if (node->isLeaf()) { - // give this node its color - int octalCodeBytes = bytesRequiredForCodeLength(args->lengthOfCode); - - nodeColor newColor; - memcpy(newColor, args->codeColorBuffer + octalCodeBytes, SIZE_OF_COLOR_DATA); - newColor[SIZE_OF_COLOR_DATA] = 1; - node->setColor(newColor); - - // It's possible we just reset the node to it's exact same color, in - // which case we don't consider this to be dirty... - if (node->isDirty()) { - // track our tree dirtiness - _isDirty = true; - // track that path has changed - args->pathChanged = true; - } - } - return; - } - - // Ok, we know we haven't reached our target node yet, so keep looking - int childIndex = branchIndexWithDescendant(node->getOctalCode(), args->codeColorBuffer); - VoxelNode* childNode = node->getChildAtIndex(childIndex); - - // If the branch we need to traverse does not exist, then create it on the way down... - if (!childNode) { - childNode = node->addChildAtIndex(childIndex); - } - - // recurse... - readCodeColorBufferToTreeRecursion(childNode, args); - - // Unwinding... - - // If the lower level did some work, then we need to let this node know, so it can - // do any bookkeeping it wants to, like color re-averaging, time stamp marking, etc - if (args->pathChanged) { - node->handleSubtreeChanged(this); - } -} - -void VoxelTree::processRemoveVoxelBitstream(const unsigned char* bitstream, int bufferSizeBytes) { - //unsigned short int itemNumber = (*((unsigned short int*)&bitstream[sizeof(PACKET_HEADER)])); - - int numBytesPacketHeader = numBytesForPacketHeader(bitstream); - unsigned short int sequence = (*((unsigned short int*)(bitstream + numBytesPacketHeader))); - uint64_t sentAt = (*((uint64_t*)(bitstream + numBytesPacketHeader + sizeof(sequence)))); - - int atByte = numBytesPacketHeader + sizeof(sequence) + sizeof(sentAt); - - unsigned char* voxelCode = (unsigned char*)&bitstream[atByte]; - while (atByte < bufferSizeBytes) { - int maxSize = bufferSizeBytes - atByte; - int codeLength = numberOfThreeBitSectionsInCode(voxelCode, maxSize); - - if (codeLength == OVERFLOWED_OCTCODE_BUFFER) { - printf("WARNING! Got remove voxel bitstream that would overflow buffer in numberOfThreeBitSectionsInCode(), "); - printf("bailing processing of packet!\n"); - break; - } - int voxelDataSize = bytesRequiredForCodeLength(codeLength) + SIZE_OF_COLOR_DATA; - - if (atByte + voxelDataSize <= bufferSizeBytes) { - deleteVoxelCodeFromTree(voxelCode, COLLAPSE_EMPTY_TREE); - voxelCode += voxelDataSize; - atByte += voxelDataSize; - } else { - printf("WARNING! Got remove voxel bitstream that would overflow buffer, bailing processing!\n"); - break; - } - } -} - -void VoxelTree::printTreeForDebugging(VoxelNode *startNode) { - int colorMask = 0; - - // create the color mask - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (startNode->getChildAtIndex(i) && startNode->getChildAtIndex(i)->isColored()) { - colorMask += (1 << (7 - i)); - } - } - - qDebug("color mask: "); - outputBits(colorMask); - - // output the colors we have - for (int j = 0; j < NUMBER_OF_CHILDREN; j++) { - if (startNode->getChildAtIndex(j) && startNode->getChildAtIndex(j)->isColored()) { - qDebug("color %d : ",j); - for (int c = 0; c < 3; c++) { - outputBits(startNode->getChildAtIndex(j)->getTrueColor()[c],false); - } - startNode->getChildAtIndex(j)->printDebugDetails(""); - } - } - - unsigned char childMask = 0; - - for (int k = 0; k < NUMBER_OF_CHILDREN; k++) { - if (startNode->getChildAtIndex(k)) { - childMask += (1 << (7 - k)); - } - } - - qDebug("child mask: "); - outputBits(childMask); - - if (childMask > 0) { - // ask children to recursively output their trees - // if they aren't a leaf - for (int l = 0; l < NUMBER_OF_CHILDREN; l++) { - if (startNode->getChildAtIndex(l)) { - printTreeForDebugging(startNode->getChildAtIndex(l)); - } - } - } -} - -// Note: this is an expensive call. Don't call it unless you really need to reaverage the entire tree (from startNode) -void VoxelTree::reaverageVoxelColors(VoxelNode* startNode) { - // if our tree is a reaveraging tree, then we do this, otherwise we don't do anything - if (_shouldReaverage) { - static int recursionCount; - if (startNode == rootNode) { - recursionCount = 0; - } else { - recursionCount++; - } - if (recursionCount > UNREASONABLY_DEEP_RECURSION) { - qDebug("VoxelTree::reaverageVoxelColors()... bailing out of UNREASONABLY_DEEP_RECURSION\n"); - recursionCount--; - return; - } - - bool hasChildren = false; - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (startNode->getChildAtIndex(i)) { - reaverageVoxelColors(startNode->getChildAtIndex(i)); - hasChildren = true; - } - } - - // collapseIdenticalLeaves() returns true if it collapses the leaves - // in which case we don't need to set the average color - if (hasChildren && !startNode->collapseIdenticalLeaves()) { - startNode->setColorFromAverageOfChildren(); - } - recursionCount--; - } -} - -void VoxelTree::loadVoxelsFile(const char* fileName, bool wantColorRandomizer) { - int vCount = 0; - - std::ifstream file(fileName, std::ios::in|std::ios::binary); - - char octets; - unsigned int lengthInBytes; - - int totalBytesRead = 0; - if(file.is_open()) { - qDebug("loading file...\n"); - bool bail = false; - while (!file.eof() && !bail) { - file.get(octets); - totalBytesRead++; - lengthInBytes = bytesRequiredForCodeLength(octets) - 1; - unsigned char * voxelData = new unsigned char[lengthInBytes + 1 + 3]; - voxelData[0]=octets; - char byte; - - for (size_t i = 0; i < lengthInBytes; i++) { - file.get(byte); - totalBytesRead++; - voxelData[i+1] = byte; - } - // read color data - char colorRead; - unsigned char red,green,blue; - file.get(colorRead); - red = (unsigned char)colorRead; - file.get(colorRead); - green = (unsigned char)colorRead; - file.get(colorRead); - blue = (unsigned char)colorRead; - - qDebug("voxel color from file red:%d, green:%d, blue:%d \n",red,green,blue); - vCount++; - - int colorRandomizer = wantColorRandomizer ? randIntInRange (-5, 5) : 0; - voxelData[lengthInBytes+1] = std::max(0,std::min(255,red + colorRandomizer)); - voxelData[lengthInBytes+2] = std::max(0,std::min(255,green + colorRandomizer)); - voxelData[lengthInBytes+3] = std::max(0,std::min(255,blue + colorRandomizer)); - qDebug("voxel color after rand red:%d, green:%d, blue:%d\n", - voxelData[lengthInBytes+1], voxelData[lengthInBytes+2], voxelData[lengthInBytes+3]); - - //printVoxelCode(voxelData); - this->readCodeColorBufferToTree(voxelData); - delete voxelData; - } - file.close(); - } -} - -VoxelNode* VoxelTree::getVoxelAt(float x, float y, float z, float s) const { - unsigned char* octalCode = pointToVoxel(x,y,z,s,0,0,0); - VoxelNode* node = nodeForOctalCode(rootNode, octalCode, NULL); - if (*node->getOctalCode() != *octalCode) { - node = NULL; - } - delete[] octalCode; // cleanup memory -#ifdef HAS_AUDIT_CHILDREN - if (node) { - node->auditChildren("VoxelTree::getVoxelAt()"); - } -#endif // def HAS_AUDIT_CHILDREN - return node; +VoxelTreeElement* VoxelTree::getVoxelAt(float x, float y, float z, float s) const { + return (VoxelTreeElement*)getOctreeElementAt(x, y, z, s); } void VoxelTree::createVoxel(float x, float y, float z, float s, unsigned char red, unsigned char green, unsigned char blue, bool destructive) { unsigned char* voxelData = pointToVoxel(x,y,z,s,red,green,blue); + + int length = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(voxelData)) + BYTES_PER_COLOR; + printf("createVoxel()..."); + outputBufferBits(voxelData,length); + this->readCodeColorBufferToTree(voxelData, destructive); delete[] voxelData; } @@ -909,785 +198,205 @@ void VoxelTree::createSphere(float radius, float xc, float yc, float zc, float v } } -// combines the ray cast arguments into a single object -class RayArgs { + +class NodeChunkArgs { public: - glm::vec3 origin; - glm::vec3 direction; - VoxelNode*& node; - float& distance; - BoxFace& face; - bool found; + VoxelTree* thisVoxelTree; + glm::vec3 nudgeVec; + VoxelEditPacketSender* voxelEditSenderPtr; + int childOrder[NUMBER_OF_CHILDREN]; }; -bool findRayIntersectionOp(VoxelNode* node, void* extraData) { - RayArgs* args = static_cast(extraData); - AABox box = node->getAABox(); - float distance; - BoxFace face; - if (!box.findRayIntersection(args->origin, args->direction, distance, face)) { - return false; +float findNewLeafSize(const glm::vec3& nudgeAmount, float leafSize) { + // we want the smallest non-zero and non-negative new leafSize + float newLeafSizeX = fabs(fmod(nudgeAmount.x, leafSize)); + float newLeafSizeY = fabs(fmod(nudgeAmount.y, leafSize)); + float newLeafSizeZ = fabs(fmod(nudgeAmount.z, leafSize)); + + float newLeafSize = leafSize; + if (newLeafSizeX) { + newLeafSize = fmin(newLeafSize, newLeafSizeX); } - if (!node->isLeaf()) { - return true; // recurse on children + if (newLeafSizeY) { + newLeafSize = fmin(newLeafSize, newLeafSizeY); } - distance *= TREE_SCALE; - if (node->isColored() && (!args->found || distance < args->distance)) { - args->node = node; - args->distance = distance; - args->face = face; - args->found = true; + if (newLeafSizeZ) { + newLeafSize = fmin(newLeafSize, newLeafSizeZ); } - return false; + return newLeafSize; } -bool VoxelTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - VoxelNode*& node, float& distance, BoxFace& face) { - RayArgs args = { origin / (float)TREE_SCALE, direction, node, distance, face }; - recurseTreeWithOperation(findRayIntersectionOp, &args); - return args.found; -} +void reorderChildrenForNudge(void* extraData) { + NodeChunkArgs* args = (NodeChunkArgs*)extraData; + glm::vec3 nudgeVec = args->nudgeVec; + int lastToNudgeVote[NUMBER_OF_CHILDREN] = { 0 }; -class SphereArgs { -public: - glm::vec3 center; - float radius; - glm::vec3& penetration; - bool found; -}; + const int POSITIVE_X_ORDERING[4] = {0, 1, 2, 3}; + const int NEGATIVE_X_ORDERING[4] = {4, 5, 6, 7}; + const int POSITIVE_Y_ORDERING[4] = {0, 1, 4, 5}; + const int NEGATIVE_Y_ORDERING[4] = {2, 3, 6, 7}; + const int POSITIVE_Z_ORDERING[4] = {0, 2, 4, 6}; + const int NEGATIVE_Z_ORDERING[4] = {1, 3, 5, 7}; -bool findSpherePenetrationOp(VoxelNode* node, void* extraData) { - SphereArgs* args = static_cast(extraData); - - // coarse check against bounds - const AABox& box = node->getAABox(); - if (!box.expandedContains(args->center, args->radius)) { - return false; - } - if (!node->isLeaf()) { - return true; // recurse on children - } - if (node->isColored()) { - glm::vec3 nodePenetration; - if (box.findSpherePenetration(args->center, args->radius, nodePenetration)) { - args->penetration = addPenetrations(args->penetration, nodePenetration * (float)TREE_SCALE); - args->found = true; + if (nudgeVec.x > 0) { + for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) { + lastToNudgeVote[POSITIVE_X_ORDERING[i]]++; + } + } else if (nudgeVec.x < 0) { + for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) { + lastToNudgeVote[NEGATIVE_X_ORDERING[i]]++; } } - return false; -} - -bool VoxelTree::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) { - SphereArgs args = { center / (float)TREE_SCALE, radius / TREE_SCALE, penetration }; - penetration = glm::vec3(0.0f, 0.0f, 0.0f); - recurseTreeWithOperation(findSpherePenetrationOp, &args); - return args.found; -} - -class CapsuleArgs { -public: - glm::vec3 start; - glm::vec3 end; - float radius; - glm::vec3& penetration; - bool found; -}; - -bool findCapsulePenetrationOp(VoxelNode* node, void* extraData) { - CapsuleArgs* args = static_cast(extraData); - - // coarse check against bounds - const AABox& box = node->getAABox(); - if (!box.expandedIntersectsSegment(args->start, args->end, args->radius)) { - return false; - } - if (!node->isLeaf()) { - return true; // recurse on children - } - if (node->isColored()) { - glm::vec3 nodePenetration; - if (box.findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) { - args->penetration = addPenetrations(args->penetration, nodePenetration * (float)TREE_SCALE); - args->found = true; + if (nudgeVec.y > 0) { + for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) { + lastToNudgeVote[POSITIVE_Y_ORDERING[i]]++; + } + } else if (nudgeVec.y < 0) { + for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) { + lastToNudgeVote[NEGATIVE_Y_ORDERING[i]]++; } } - return false; -} - -bool VoxelTree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) { - CapsuleArgs args = { start / (float)TREE_SCALE, end / (float)TREE_SCALE, radius / TREE_SCALE, penetration }; - penetration = glm::vec3(0.0f, 0.0f, 0.0f); - recurseTreeWithOperation(findCapsulePenetrationOp, &args); - return args.found; -} - -int VoxelTree::encodeTreeBitstream(VoxelNode* node, - VoxelPacketData* packetData, VoxelNodeBag& bag, - EncodeBitstreamParams& params) { - - // How many bytes have we written so far at this level; - int bytesWritten = 0; - - // you can't call this without a valid node - if (!node) { - qDebug("WARNING! encodeTreeBitstream() called with node=NULL\n"); - params.stopReason = EncodeBitstreamParams::NULL_NODE; - return bytesWritten; - } - - startEncoding(node); - - // If we're at a node that is out of view, then we can return, because no nodes below us will be in view! - if (params.viewFrustum && !node->isInView(*params.viewFrustum)) { - doneEncoding(node); - params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; - return bytesWritten; - } - - // write the octal code - bool roomForOctalCode = false; // assume the worst - int codeLength; - if (params.chopLevels) { - unsigned char* newCode = chopOctalCode(node->getOctalCode(), params.chopLevels); - roomForOctalCode = packetData->startSubTree(newCode); - - if (newCode) { - delete newCode; - } else { - codeLength = 1; + if (nudgeVec.z > 0) { + for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) { + lastToNudgeVote[POSITIVE_Z_ORDERING[i]]++; } - } else { - roomForOctalCode = packetData->startSubTree(node->getOctalCode()); - codeLength = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(node->getOctalCode())); - } - - // If the octalcode couldn't fit, then we can return, because no nodes below us will fit... - if (!roomForOctalCode) { - doneEncoding(node); - bag.insert(node); // add the node back to the bag so it will eventually get included - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - return bytesWritten; - } - - bytesWritten += codeLength; // keep track of byte count - - int currentEncodeLevel = 0; - - // record some stats, this is the one node that we won't record below in the recursion function, so we need to - // track it here - if (params.stats) { - params.stats->traversed(node); - } - - int childBytesWritten = encodeTreeBitstreamRecursion(node, packetData, bag, params, currentEncodeLevel); - - // if childBytesWritten == 1 then something went wrong... that's not possible - assert(childBytesWritten != 1); - - // if includeColor and childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some reason - // couldn't be written... so reset them here... This isn't true for the non-color included case - if (params.includeColor && childBytesWritten == 2) { - childBytesWritten = 0; - //params.stopReason = EncodeBitstreamParams::UNKNOWN; // possibly should be DIDNT_FIT... - } - - // if we wrote child bytes, then return our result of all bytes written - if (childBytesWritten) { - bytesWritten += childBytesWritten; - } else { - // otherwise... if we didn't write any child bytes, then pretend like we also didn't write our octal code - bytesWritten = 0; - //params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - if (bytesWritten == 0) { - packetData->discardSubTree(); - } else { - packetData->endSubTree(); - } - - doneEncoding(node); - - return bytesWritten; -} - -int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, - VoxelPacketData* packetData, VoxelNodeBag& bag, - EncodeBitstreamParams& params, int& currentEncodeLevel) const { - // How many bytes have we written so far at this level; - int bytesAtThisLevel = 0; - - // you can't call this without a valid node - if (!node) { - qDebug("WARNING! encodeTreeBitstreamRecursion() called with node=NULL\n"); - params.stopReason = EncodeBitstreamParams::NULL_NODE; - return bytesAtThisLevel; - } - - // Keep track of how deep we've encoded. - currentEncodeLevel++; - - params.maxLevelReached = std::max(currentEncodeLevel,params.maxLevelReached); - - // If we've reached our max Search Level, then stop searching. - if (currentEncodeLevel >= params.maxEncodeLevel) { - params.stopReason = EncodeBitstreamParams::TOO_DEEP; - return bytesAtThisLevel; - } - - // If we've been provided a jurisdiction map, then we need to honor it. - if (params.jurisdictionMap) { - // here's how it works... if we're currently above our root jurisdiction, then we proceed normally. - // but once we're in our own jurisdiction, then we need to make sure we're not below it. - if (JurisdictionMap::BELOW == params.jurisdictionMap->isMyJurisdiction(node->getOctalCode(), CHECK_NODE_ONLY)) { - params.stopReason = EncodeBitstreamParams::OUT_OF_JURISDICTION; - return bytesAtThisLevel; - } - } - - // caller can pass NULL as viewFrustum if they want everything - if (params.viewFrustum) { - float distance = node->distanceToCamera(*params.viewFrustum); - float boundaryDistance = boundaryDistanceForRenderLevel(node->getLevel() + params.boundaryLevelAdjust, - params.voxelSizeScale); - - // If we're too far away for our render level, then just return - if (distance >= boundaryDistance) { - if (params.stats) { - params.stats->skippedDistance(node); - } - params.stopReason = EncodeBitstreamParams::LOD_SKIP; - return bytesAtThisLevel; - } - - // If we're at a node that is out of view, then we can return, because no nodes below us will be in view! - // although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if - // we're out of view - if (!node->isInView(*params.viewFrustum)) { - if (params.stats) { - params.stats->skippedOutOfView(node); - } - params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; - return bytesAtThisLevel; - } - - // Ok, we are in view, but if we're in delta mode, then we also want to make sure we weren't already in view - // because we don't send nodes from the previously know in view frustum. - bool wasInView = false; - - if (params.deltaViewFrustum && params.lastViewFrustum) { - ViewFrustum::location location = node->inFrustum(*params.lastViewFrustum); - - // If we're a leaf, then either intersect or inside is considered "formerly in view" - if (node->isLeaf()) { - wasInView = location != ViewFrustum::OUTSIDE; - } else { - wasInView = location == ViewFrustum::INSIDE; - } - - // If we were in view, double check that we didn't switch LOD visibility... namely, the was in view doesn't - // tell us if it was so small we wouldn't have rendered it. Which may be the case. And we may have moved closer - // to it, and so therefore it may now be visible from an LOD perspective, in which case we don't consider it - // as "was in view"... - if (wasInView) { - float distance = node->distanceToCamera(*params.lastViewFrustum); - float boundaryDistance = boundaryDistanceForRenderLevel(node->getLevel() + params.boundaryLevelAdjust, - params.voxelSizeScale); - if (distance >= boundaryDistance) { - // This would have been invisible... but now should be visible (we wouldn't be here otherwise)... - wasInView = false; - } - } - } - - // If we were previously in the view, then we normally will return out of here and stop recursing. But - // if we're in deltaViewFrustum mode, and this node has changed since it was last sent, then we do - // need to send it. - if (wasInView && !(params.deltaViewFrustum && node->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))) { - if (params.stats) { - params.stats->skippedWasInView(node); - } - params.stopReason = EncodeBitstreamParams::WAS_IN_VIEW; - return bytesAtThisLevel; - } - - // If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed, - // then we can also bail early and save bits - if (!params.forceSendScene && !params.deltaViewFrustum && - !node->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE)) { - if (params.stats) { - params.stats->skippedNoChange(node); - } - params.stopReason = EncodeBitstreamParams::NO_CHANGE; - return bytesAtThisLevel; - } - - // If the user also asked for occlusion culling, check if this node is occluded, but only if it's not a leaf. - // leaf occlusion is handled down below when we check child nodes - if (params.wantOcclusionCulling && !node->isLeaf()) { - AABox voxelBox = node->getAABox(); - voxelBox.scale(TREE_SCALE); - VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon(params.viewFrustum->getProjectedPolygon(voxelBox)); - - // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion - // culling and proceed as normal - if (voxelPolygon->getAllInView()) { - CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, false); - delete voxelPolygon; // cleanup - if (result == OCCLUDED) { - if (params.stats) { - params.stats->skippedOccluded(node); - } - params.stopReason = EncodeBitstreamParams::OCCLUDED; - return bytesAtThisLevel; - } - } else { - // If this shadow wasn't "all in view" then we ignored it for occlusion culling, but - // we do need to clean up memory and proceed as normal... - delete voxelPolygon; - } + } else if (nudgeVec.z < 0) { + for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) { + lastToNudgeVote[NEGATIVE_Z_ORDERING[i]]++; } } - bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more! + int nUncountedVotes = NUMBER_OF_CHILDREN; - // At any given point in writing the bitstream, the largest minimum we might need to flesh out the current level - // is 1 byte for child colors + 3*NUMBER_OF_CHILDREN bytes for the actual colors + 1 byte for child trees. There could be sub trees - // below this point, which might take many more bytes, but that's ok, because we can always mark our subtrees as - // not existing and stop the packet at this point, then start up with a new packet for the remaining sub trees. - unsigned char childrenExistInTreeBits = 0; - unsigned char childrenExistInPacketBits = 0; - unsigned char childrenColoredBits = 0; - const int BYTES_PER_COLOR = 3; - - // Make our local buffer large enough to handle writing at this level in case we need to. - LevelDetails thisLevelKey = packetData->startLevel(); - - int inViewCount = 0; - int inViewNotLeafCount = 0; - int inViewWithColorCount = 0; - - VoxelNode* sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; - float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - int currentCount = 0; - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* childNode = node->getChildAtIndex(i); - - // if the caller wants to include childExistsBits, then include them even if not in view, if however, - // we're in a portion of the tree that's not our responsibility, then we assume the child nodes exist - // even if they don't in our local tree - bool notMyJurisdiction = false; - if (params.jurisdictionMap) { - notMyJurisdiction = (JurisdictionMap::WITHIN != params.jurisdictionMap->isMyJurisdiction(node->getOctalCode(), i)); - } - if (params.includeExistsBits) { - // If the child is known to exist, OR, it's not my jurisdiction, then we mark the bit as existing - if (childNode || notMyJurisdiction) { - childrenExistInTreeBits += (1 << (7 - i)); - } - } - - if (params.wantOcclusionCulling) { - if (childNode) { - float distance = params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; - - currentCount = insertIntoSortedArrays((void*)childNode, distance, i, - (void**)&sortedChildren, (float*)&distancesToChildren, - (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); - } - } else { - sortedChildren[i] = childNode; - indexOfChildren[i] = i; - distancesToChildren[i] = 0.0f; - currentCount++; - } - - // track stats - // must check childNode here, because it could be we got here with no childNode - if (params.stats && childNode) { - params.stats->traversed(childNode); - } - - } - - // for each child node in Distance sorted order..., check to see if they exist, are colored, and in view, and if so - // add them to our distance ordered array of children - for (int i = 0; i < currentCount; i++) { - VoxelNode* childNode = sortedChildren[i]; - int originalIndex = indexOfChildren[i]; - - bool childIsInView = (childNode && (!params.viewFrustum || childNode->isInView(*params.viewFrustum))); - - if (!childIsInView) { - // must check childNode here, because it could be we got here because there was no childNode - if (params.stats && childNode) { - params.stats->skippedOutOfView(childNode); - } - } else { - // Before we determine consider this further, let's see if it's in our LOD scope... - float distance = distancesToChildren[i]; // params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; - float boundaryDistance = !params.viewFrustum ? 1 : - boundaryDistanceForRenderLevel(childNode->getLevel() + params.boundaryLevelAdjust, - params.voxelSizeScale); - - if (!(distance < boundaryDistance)) { - // don't need to check childNode here, because we can't get here with no childNode - if (params.stats) { - params.stats->skippedDistance(childNode); - } - } else { - inViewCount++; - - // track children in view as existing and not a leaf, if they're a leaf, - // we don't care about recursing deeper on them, and we don't consider their - // subtree to exist - if (!(childNode && childNode->isLeaf())) { - childrenExistInPacketBits += (1 << (7 - originalIndex)); - inViewNotLeafCount++; - } - - bool childIsOccluded = false; // assume it's not occluded - - // If the user also asked for occlusion culling, check if this node is occluded - if (params.wantOcclusionCulling && childNode->isLeaf()) { - // Don't check occlusion here, just add them to our distance ordered array... - - AABox voxelBox = childNode->getAABox(); - voxelBox.scale(TREE_SCALE); - VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon( - params.viewFrustum->getProjectedPolygon(voxelBox)); - - // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion - // culling and proceed as normal - if (voxelPolygon->getAllInView()) { - CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, true); - - // In all cases where the shadow wasn't stored, we need to free our own memory. - // In the case where it is stored, the CoverageMap will free memory for us later. - if (result != STORED) { - delete voxelPolygon; - } - - // If while attempting to add this voxel's shadow, we determined it was occluded, then - // we don't need to process it further and we can exit early. - if (result == OCCLUDED) { - childIsOccluded = true; - } - } else { - delete voxelPolygon; - } - } // wants occlusion culling & isLeaf() - - - bool shouldRender = !params.viewFrustum - ? true - : childNode->calculateShouldRender(params.viewFrustum, - params.voxelSizeScale, params.boundaryLevelAdjust); - - // track some stats - if (params.stats) { - // don't need to check childNode here, because we can't get here with no childNode - if (!shouldRender && childNode->isLeaf()) { - params.stats->skippedDistance(childNode); - } - // don't need to check childNode here, because we can't get here with no childNode - if (childIsOccluded) { - params.stats->skippedOccluded(childNode); - } - } - - // track children with actual color, only if the child wasn't previously in view! - if (shouldRender && !childIsOccluded) { - bool childWasInView = false; - - if (childNode && params.deltaViewFrustum && params.lastViewFrustum) { - ViewFrustum::location location = childNode->inFrustum(*params.lastViewFrustum); - - // If we're a leaf, then either intersect or inside is considered "formerly in view" - if (childNode->isLeaf()) { - childWasInView = location != ViewFrustum::OUTSIDE; - } else { - childWasInView = location == ViewFrustum::INSIDE; - } - } - - // If our child wasn't in view (or we're ignoring wasInView) then we add it to our sending items. - // Or if we were previously in the view, but this node has changed since it was last sent, then we do - // need to send it. - if (!childWasInView || - (params.deltaViewFrustum && - childNode->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))){ - - childrenColoredBits += (1 << (7 - originalIndex)); - inViewWithColorCount++; - } else { - // otherwise just track stats of the items we discarded - // don't need to check childNode here, because we can't get here with no childNode - if (params.stats) { - if (childWasInView) { - params.stats->skippedWasInView(childNode); - } else { - params.stats->skippedNoChange(childNode); - } - } - - } - } - } - } - } - - bool continueThisLevel = true; - continueThisLevel = packetData->appendBitMask(childrenColoredBits); - - if (continueThisLevel) { - bytesAtThisLevel += sizeof(childrenColoredBits); // keep track of byte count - if (params.stats) { - params.stats->colorBitsWritten(); - } - } - - // write the color data... - if (continueThisLevel && params.includeColor) { + while (nUncountedVotes > 0) { + int maxNumVotes = 0; + int maxVoteIndex = -1; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (oneAtBit(childrenColoredBits, i)) { - VoxelNode* childNode = node->getChildAtIndex(i); - continueThisLevel = packetData->appendColor(childNode->getColor()); - - if (!continueThisLevel) { - break; // no point in continuing - } - - bytesAtThisLevel += BYTES_PER_COLOR; // keep track of byte count for color - - // don't need to check childNode here, because we can't get here with no childNode - if (params.stats) { - params.stats->colorSent(childNode); - } - + if (lastToNudgeVote[i] > maxNumVotes) { + maxNumVotes = lastToNudgeVote[i]; + maxVoteIndex = i; + } else if (lastToNudgeVote[i] == maxNumVotes && maxVoteIndex == -1) { + maxVoteIndex = i; } } + lastToNudgeVote[maxVoteIndex] = -1; + args->childOrder[nUncountedVotes - 1] = maxVoteIndex; + nUncountedVotes--; } - - // if the caller wants to include childExistsBits, then include them even if not in view, put them before the - // childrenExistInPacketBits, so that the lower code can properly repair the packet exists bits - if (continueThisLevel && params.includeExistsBits) { - continueThisLevel = packetData->appendBitMask(childrenExistInTreeBits); - if (continueThisLevel) { - bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count - if (params.stats) { - params.stats->existsBitsWritten(); - } - } - } - - // write the child exist bits - if (continueThisLevel) { - continueThisLevel = packetData->appendBitMask(childrenExistInPacketBits); - if (continueThisLevel) { - bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count - if (params.stats) { - params.stats->existsInPacketBitsWritten(); - } - } - } - - // We only need to keep digging, if there is at least one child that is inView, and not a leaf. - keepDiggingDeeper = (inViewNotLeafCount > 0); - - if (continueThisLevel && keepDiggingDeeper) { - // at this point, we need to iterate the children who are in view, even if not colored - // and we need to determine if there's a deeper tree below them that we care about. - // - // Since this recursive function assumes we're already writing, we know we've already written our - // childrenExistInPacketBits. But... we don't really know how big the child tree will be. And we don't know if - // we'll have room in our buffer to actually write all these child trees. What we kinda would like to do is - // write our childExistsBits as a place holder. Then let each potential tree have a go at it. If they - // write something, we keep them in the bits, if they don't, we take them out. - // - // we know the last thing we wrote to the packet was our childrenExistInPacketBits. Let's remember where that was! - int childExistsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenExistInPacketBits)); - - // we are also going to recurse these child trees in "distance" sorted order, but we need to pack them in the - // final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes, - // and then later reshuffle these sections of our output buffer back into normal order. This allows us to make - // a single recursive pass in distance sorted order, but retain standard order in our encoded packet - int recursiveSliceSizes[NUMBER_OF_CHILDREN]; - const unsigned char* recursiveSliceStarts[NUMBER_OF_CHILDREN]; - int firstRecursiveSliceOffset = packetData->getUncompressedByteOffset(); - int allSlicesSize = 0; - - // for each child node in Distance sorted order..., check to see if they exist, are colored, and in view, and if so - // add them to our distance ordered array of children - for (int indexByDistance = 0; indexByDistance < currentCount; indexByDistance++) { - VoxelNode* childNode = sortedChildren[indexByDistance]; - int originalIndex = indexOfChildren[indexByDistance]; - - if (oneAtBit(childrenExistInPacketBits, originalIndex)) { - - int thisLevel = currentEncodeLevel; - // remember this for reshuffling - recursiveSliceStarts[originalIndex] = packetData->getUncompressedData() + packetData->getUncompressedSize(); - - int childTreeBytesOut = 0; - - // XXXBHG - Note, this seems like the correct logic here, if we included the color in this packet, then - // the LOD logic determined that the child nodes would not be visible... and if so, we shouldn't recurse - // them further. But... for some time now the code has included and recursed into these child nodes, which - // would likely still send the child content, even though the client wouldn't render it. This change is - // a major savings (~30%) and it seems to work correctly. But I want us to discuss as a group when we do - // a voxel protocol review. - // - // This only applies in the view frustum case, in other cases, like file save and copy/past where - // no viewFrustum was requested, we still want to recurse the child tree. - if (!params.viewFrustum || !oneAtBit(childrenColoredBits, originalIndex)) { - childTreeBytesOut = encodeTreeBitstreamRecursion(childNode, packetData, bag, params, thisLevel); - } - - // remember this for reshuffling - recursiveSliceSizes[originalIndex] = childTreeBytesOut; - allSlicesSize += childTreeBytesOut; - - // if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space, - // basically, the children below don't contain any info. - - // if the child tree wrote 1 byte??? something must have gone wrong... because it must have at least the color - // byte and the child exist byte. - // - assert(childTreeBytesOut != 1); - - // if the child tree wrote just 2 bytes, then it means: it had no colors and no child nodes, because... - // if it had colors it would write 1 byte for the color mask, - // and at least a color's worth of bytes for the node of colors. - // if it had child trees (with something in them) then it would have the 1 byte for child mask - // and some number of bytes of lower children... - // so, if the child returns 2 bytes out, we can actually consider that an empty tree also!! - // - // we can make this act like no bytes out, by just resetting the bytes out in this case - if (params.includeColor && !params.includeExistsBits && childTreeBytesOut == 2) { - childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees - } - // We used to try to collapse trees that didn't contain any data, but this does appear to create a problem - // in detecting node deletion. So, I've commented this out but left it in here as a warning to anyone else - // about not attempting to add this optimization back in, without solving the node deletion case. - // We need to send these bitMasks in case the exists in tree bitmask is indicating the deletion of a tree - //if (params.includeColor && params.includeExistsBits && childTreeBytesOut == 3) { - // childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees - //} - - bytesAtThisLevel += childTreeBytesOut; - - // If we had previously started writing, and if the child DIDN'T write any bytes, - // then we want to remove their bit from the childExistsPlaceHolder bitmask - if (childTreeBytesOut == 0) { - // remove this child's bit... - childrenExistInPacketBits -= (1 << (7 - originalIndex)); - - // repair the child exists mask - continueThisLevel = packetData->updatePriorBitMask(childExistsPlaceHolder, childrenExistInPacketBits); - - // If this is the last of the child exists bits, then we're actually be rolling out the entire tree - if (params.stats && childrenExistInPacketBits == 0) { - params.stats->childBitsRemoved(params.includeExistsBits, params.includeColor); - } - - if (!continueThisLevel) { - break; // can't continue... - } - - // Note: no need to move the pointer, cause we already stored this - } // end if (childTreeBytesOut == 0) - } // end if (oneAtBit(childrenExistInPacketBits, originalIndex)) - } // end for - - // reshuffle here... - if (continueThisLevel && params.wantOcclusionCulling) { - unsigned char tempReshuffleBuffer[MAX_VOXEL_UNCOMRESSED_PACKET_SIZE]; - - unsigned char* tempBufferTo = &tempReshuffleBuffer[0]; // this is our temporary destination - - // iterate through our childrenExistInPacketBits, these will be the sections of the packet that we copied subTree - // details into. Unfortunately, they're in distance sorted order, not original index order. we need to put them - // back into original distance order - for (int originalIndex = 0; originalIndex < NUMBER_OF_CHILDREN; originalIndex++) { - if (oneAtBit(childrenExistInPacketBits, originalIndex)) { - int thisSliceSize = recursiveSliceSizes[originalIndex]; - const unsigned char* thisSliceStarts = recursiveSliceStarts[originalIndex]; - - memcpy(tempBufferTo, thisSliceStarts, thisSliceSize); - tempBufferTo += thisSliceSize; - } - } - - // now that all slices are back in the correct order, copy them to the correct output buffer - continueThisLevel = packetData->updatePriorBytes(firstRecursiveSliceOffset, &tempReshuffleBuffer[0], allSlicesSize); - } - } // end keepDiggingDeeper - - // At this point all our BitMasks are complete... so let's output them to see how they compare... - /** - printf("This Level's BitMasks: childInTree:"); - outputBits(childrenExistInTreeBits, false, true); - printf(" childInPacket:"); - outputBits(childrenExistInPacketBits, false, true); - printf(" childrenColored:"); - outputBits(childrenColoredBits, false, true); - printf("\n"); - **/ - - // if we were unable to fit this level in our packet, then rewind and add it to the node bag for - // sending later... - if (continueThisLevel) { - continueThisLevel = packetData->endLevel(thisLevelKey); - } else { - packetData->discardLevel(thisLevelKey); - } - - if (!continueThisLevel) { - bag.insert(node); - - // don't need to check node here, because we can't get here with no node - if (params.stats) { - params.stats->didntFit(node); - } - - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - bytesAtThisLevel = 0; // didn't fit - } - - return bytesAtThisLevel; } -bool VoxelTree::readFromSVOFile(const char* fileName) { - std::ifstream file(fileName, std::ios::in|std::ios::binary|std::ios::ate); - if(file.is_open()) { - emit importSize(1.0f, 1.0f, 1.0f); - emit importProgress(0); +bool VoxelTree::nudgeCheck(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; + if (voxel->isLeaf()) { + // we have reached the deepest level of elements/voxels + // now there are two scenarios + // 1) this element's size is <= the minNudgeAmount + // in which case we will simply call nudgeLeaf on this leaf + // 2) this element's size is still not <= the minNudgeAmount + // in which case we need to break this leaf down until the leaf sizes are <= minNudgeAmount - qDebug("loading file %s...\n", fileName); + NodeChunkArgs* args = (NodeChunkArgs*)extraData; - // get file length.... - unsigned long fileLength = file.tellg(); - file.seekg( 0, std::ios::beg ); + // get octal code of this element + const unsigned char* octalCode = element->getOctalCode(); - // read the entire file into a buffer, WHAT!? Why not. - unsigned char* entireFile = new unsigned char[fileLength]; - file.read((char*)entireFile, fileLength); - bool wantImportProgress = true; - ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0, wantImportProgress); - readBitstreamToTree(entireFile, fileLength, args); - delete[] entireFile; + // get voxel position/size + VoxelPositionSize unNudgedDetails; + voxelDetailsForCode(octalCode, unNudgedDetails); - emit importProgress(100); + // find necessary leaf size + float newLeafSize = findNewLeafSize(args->nudgeVec, unNudgedDetails.s); - file.close(); - return true; + // check to see if this unNudged element can be nudged + if (unNudgedDetails.s <= newLeafSize) { + args->thisVoxelTree->nudgeLeaf(voxel, extraData); + return false; + } else { + // break the current leaf into smaller chunks + args->thisVoxelTree->chunkifyLeaf(voxel); + } } - return false; + return true; } +void VoxelTree::chunkifyLeaf(VoxelTreeElement* element) { + // because this function will continue being called recursively + // we only need to worry about breaking this specific leaf down + if (!element->isColored()) { + return; + } + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + element->addChildAtIndex(i); + element->getChildAtIndex(i)->setColor(element->getColor()); + } +} + +// This function is called to nudge the leaves of a tree, given that the +// nudge amount is >= to the leaf scale. +void VoxelTree::nudgeLeaf(VoxelTreeElement* element, void* extraData) { + NodeChunkArgs* args = (NodeChunkArgs*)extraData; + + // get octal code of this element + const unsigned char* octalCode = element->getOctalCode(); + + // get voxel position/size + VoxelPositionSize unNudgedDetails; + voxelDetailsForCode(octalCode, unNudgedDetails); + + VoxelDetail voxelDetails; + voxelDetails.x = unNudgedDetails.x; + voxelDetails.y = unNudgedDetails.y; + voxelDetails.z = unNudgedDetails.z; + voxelDetails.s = unNudgedDetails.s; + voxelDetails.red = element->getColor()[RED_INDEX]; + voxelDetails.green = element->getColor()[GREEN_INDEX]; + voxelDetails.blue = element->getColor()[BLUE_INDEX]; + glm::vec3 nudge = args->nudgeVec; + + // delete the old element + args->voxelEditSenderPtr->sendVoxelEditMessage(PACKET_TYPE_ERASE_VOXEL, voxelDetails); + + // nudge the old element + voxelDetails.x += nudge.x; + voxelDetails.y += nudge.y; + voxelDetails.z += nudge.z; + + // create a new voxel in its stead + args->voxelEditSenderPtr->sendVoxelEditMessage(PACKET_TYPE_SET_VOXEL_DESTRUCTIVE, voxelDetails); +} + +// Recurses voxel element with an operation function +void VoxelTree::recurseNodeForNudge(VoxelTreeElement* element, RecurseOctreeOperation operation, void* extraData) { + NodeChunkArgs* args = (NodeChunkArgs*)extraData; + if (operation(element, extraData)) { + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + // XXXBHG cleanup!! + VoxelTreeElement* child = (VoxelTreeElement*)element->getChildAtIndex(args->childOrder[i]); + if (child) { + recurseNodeForNudge(child, operation, extraData); + } + } + } +} + +void VoxelTree::nudgeSubTree(VoxelTreeElement* elementToNudge, const glm::vec3& nudgeAmount, VoxelEditPacketSender& voxelEditSender) { + if (nudgeAmount == glm::vec3(0, 0, 0)) { + return; + } + + NodeChunkArgs args; + args.thisVoxelTree = this; + args.nudgeVec = nudgeAmount; + args.voxelEditSenderPtr = &voxelEditSender; + reorderChildrenForNudge(&args); + + recurseNodeForNudge(elementToNudge, nudgeCheck, &args); +} + + + + + bool VoxelTree::readFromSquareARGB32Pixels(const char* filename) { emit importProgress(0); int minAlpha = INT_MAX; @@ -1846,372 +555,85 @@ bool VoxelTree::readFromSchematicFile(const char *fileName) { return true; } -void VoxelTree::writeToSVOFile(const char* fileName, VoxelNode* node) { - - std::ofstream file(fileName, std::ios::out|std::ios::binary); - - if(file.is_open()) { - qDebug("saving to file %s...\n", fileName); - - VoxelNodeBag nodeBag; - // If we were given a specific node, start from there, otherwise start from root - if (node) { - nodeBag.insert(node); - } else { - nodeBag.insert(rootNode); - } - - static VoxelPacketData packetData; - int bytesWritten = 0; - bool lastPacketWritten = false; - - while (!nodeBag.isEmpty()) { - VoxelNode* subTree = nodeBag.extract(); - - lockForRead(); // do tree locking down here so that we have shorter slices and less thread contention - EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS); - bytesWritten = encodeTreeBitstream(subTree, &packetData, nodeBag, params); - unlock(); - - // if bytesWritten == 0, then it means that the subTree couldn't fit, and so we should reset the packet - // and reinsert the node in our bag and try again... - if (bytesWritten == 0) { - if (packetData.hasContent()) { - file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize()); - lastPacketWritten = true; - } - packetData.reset(); // is there a better way to do this? could we fit more? - nodeBag.insert(subTree); - } else { - lastPacketWritten = false; - } - } - - if (!lastPacketWritten) { - file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize()); - } - - } - file.close(); -} - -unsigned long VoxelTree::getVoxelCount() { - unsigned long nodeCount = 0; - recurseTreeWithOperation(countVoxelsOperation, &nodeCount); - return nodeCount; -} - -bool VoxelTree::countVoxelsOperation(VoxelNode* node, void* extraData) { - (*(unsigned long*)extraData)++; - return true; // keep going -} - -void VoxelTree::copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinationTree, bool rebaseToRoot) { - VoxelNodeBag nodeBag; - nodeBag.insert(startNode); - int chopLevels = 0; - if (rebaseToRoot) { - chopLevels = numberOfThreeBitSectionsInCode(startNode->getOctalCode()); - } - - static VoxelPacketData packetData; - int bytesWritten = 0; - - while (!nodeBag.isEmpty()) { - VoxelNode* subTree = nodeBag.extract(); - packetData.reset(); // reset the packet between usage - // ask our tree to write a bitsteam - EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS, chopLevels); - bytesWritten = encodeTreeBitstream(subTree, &packetData, nodeBag, params); - // ask destination tree to read the bitstream - ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS); - destinationTree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); - } - - VoxelNode* destinationStartNode; - if (rebaseToRoot) { - destinationStartNode = destinationTree->rootNode; - } else { - destinationStartNode = nodeForOctalCode(destinationTree->rootNode, startNode->getOctalCode(), NULL); - } - destinationStartNode->setColor(startNode->getColor()); -} - -void VoxelTree::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode) { - VoxelNodeBag nodeBag; - // If we were given a specific node, start from there, otherwise start from root - nodeBag.insert(sourceTree->rootNode); - - static VoxelPacketData packetData; - int bytesWritten = 0; - - while (!nodeBag.isEmpty()) { - VoxelNode* subTree = nodeBag.extract(); - - packetData.reset(); // reset between usage - - // ask our tree to write a bitsteam - EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS); - bytesWritten = sourceTree->encodeTreeBitstream(subTree, &packetData, nodeBag, params); - - // ask destination tree to read the bitstream - bool wantImportProgress = true; - ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, destinationNode, 0, wantImportProgress); - readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); - } -} - -void dumpSetContents(const char* name, std::set set) { - printf("set %s has %ld elements\n", name, set.size()); - /* - for (std::set::iterator i = set.begin(); i != set.end(); ++i) { - printOctalCode(*i); - } - */ -} - -void VoxelTree::startEncoding(VoxelNode* node) { - pthread_mutex_lock(&_encodeSetLock); - _codesBeingEncoded.insert(node->getOctalCode()); - pthread_mutex_unlock(&_encodeSetLock); -} - -void VoxelTree::doneEncoding(VoxelNode* node) { - pthread_mutex_lock(&_encodeSetLock); - _codesBeingEncoded.erase(node->getOctalCode()); - pthread_mutex_unlock(&_encodeSetLock); - - // if we have any pending delete codes, then delete them now. - emptyDeleteQueue(); -} - -void VoxelTree::startDeleting(const unsigned char* code) { - pthread_mutex_lock(&_deleteSetLock); - _codesBeingDeleted.insert(code); - pthread_mutex_unlock(&_deleteSetLock); -} - -void VoxelTree::doneDeleting(const unsigned char* code) { - pthread_mutex_lock(&_deleteSetLock); - _codesBeingDeleted.erase(code); - pthread_mutex_unlock(&_deleteSetLock); -} - -bool VoxelTree::isEncoding(const unsigned char* codeBuffer) { - pthread_mutex_lock(&_encodeSetLock); - bool isEncoding = (_codesBeingEncoded.find(codeBuffer) != _codesBeingEncoded.end()); - pthread_mutex_unlock(&_encodeSetLock); - return isEncoding; -} - -void VoxelTree::queueForLaterDelete(const unsigned char* codeBuffer) { - pthread_mutex_lock(&_deletePendingSetLock); - _codesPendingDelete.insert(codeBuffer); - pthread_mutex_unlock(&_deletePendingSetLock); -} - -void VoxelTree::emptyDeleteQueue() { - pthread_mutex_lock(&_deletePendingSetLock); - for (std::set::iterator i = _codesPendingDelete.begin(); i != _codesPendingDelete.end(); ++i) { - const unsigned char* codeToDelete = *i; - _codesBeingDeleted.erase(codeToDelete); - deleteVoxelCodeFromTree(codeToDelete, COLLAPSE_EMPTY_TREE); - } - pthread_mutex_unlock(&_deletePendingSetLock); -} - -void VoxelTree::cancelImport() { - _stopImport = true; -} - -class NodeChunkArgs { +class ReadCodeColorBufferToTreeArgs { public: - VoxelTree* thisVoxelTree; - glm::vec3 nudgeVec; - VoxelEditPacketSender* voxelEditSenderPtr; - int childOrder[NUMBER_OF_CHILDREN]; + const unsigned char* codeColorBuffer; + int lengthOfCode; + bool destructive; + bool pathChanged; }; -float findNewLeafSize(const glm::vec3& nudgeAmount, float leafSize) { - // we want the smallest non-zero and non-negative new leafSize - float newLeafSizeX = fabs(fmod(nudgeAmount.x, leafSize)); - float newLeafSizeY = fabs(fmod(nudgeAmount.y, leafSize)); - float newLeafSizeZ = fabs(fmod(nudgeAmount.z, leafSize)); - - float newLeafSize = leafSize; - if (newLeafSizeX) { - newLeafSize = fmin(newLeafSize, newLeafSizeX); - } - if (newLeafSizeY) { - newLeafSize = fmin(newLeafSize, newLeafSizeY); - } - if (newLeafSizeZ) { - newLeafSize = fmin(newLeafSize, newLeafSizeZ); - } - return newLeafSize; +void VoxelTree::readCodeColorBufferToTree(const unsigned char* codeColorBuffer, bool destructive) { + ReadCodeColorBufferToTreeArgs args; + args.codeColorBuffer = codeColorBuffer; + args.lengthOfCode = numberOfThreeBitSectionsInCode(codeColorBuffer); + args.destructive = destructive; + args.pathChanged = false; + VoxelTreeElement* node = getRoot(); + readCodeColorBufferToTreeRecursion(node, args); } -void reorderChildrenForNudge(void* extraData) { - NodeChunkArgs* args = (NodeChunkArgs*)extraData; - glm::vec3 nudgeVec = args->nudgeVec; - int lastToNudgeVote[NUMBER_OF_CHILDREN] = { 0 }; +void VoxelTree::readCodeColorBufferToTreeRecursion(VoxelTreeElement* node, ReadCodeColorBufferToTreeArgs& args) { + int lengthOfNodeCode = numberOfThreeBitSectionsInCode(node->getOctalCode()); - const int POSITIVE_X_ORDERING[4] = {0, 1, 2, 3}; - const int NEGATIVE_X_ORDERING[4] = {4, 5, 6, 7}; - const int POSITIVE_Y_ORDERING[4] = {0, 1, 4, 5}; - const int NEGATIVE_Y_ORDERING[4] = {2, 3, 6, 7}; - const int POSITIVE_Z_ORDERING[4] = {0, 2, 4, 6}; - const int NEGATIVE_Z_ORDERING[4] = {1, 3, 5, 7}; - - if (nudgeVec.x > 0) { - for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) { - lastToNudgeVote[POSITIVE_X_ORDERING[i]]++; - } - } else if (nudgeVec.x < 0) { - for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) { - lastToNudgeVote[NEGATIVE_X_ORDERING[i]]++; - } - } - if (nudgeVec.y > 0) { - for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) { - lastToNudgeVote[POSITIVE_Y_ORDERING[i]]++; - } - } else if (nudgeVec.y < 0) { - for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) { - lastToNudgeVote[NEGATIVE_Y_ORDERING[i]]++; - } - } - if (nudgeVec.z > 0) { - for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) { - lastToNudgeVote[POSITIVE_Z_ORDERING[i]]++; - } - } else if (nudgeVec.z < 0) { - for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) { - lastToNudgeVote[NEGATIVE_Z_ORDERING[i]]++; - } - } - - int nUncountedVotes = NUMBER_OF_CHILDREN; - - while (nUncountedVotes > 0) { - int maxNumVotes = 0; - int maxVoteIndex = -1; - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (lastToNudgeVote[i] > maxNumVotes) { - maxNumVotes = lastToNudgeVote[i]; - maxVoteIndex = i; - } else if (lastToNudgeVote[i] == maxNumVotes && maxVoteIndex == -1) { - maxVoteIndex = i; + // Since we traverse the tree in code order, we know that if our code + // matches, then we've reached our target node. + if (lengthOfNodeCode == args.lengthOfCode) { + // we've reached our target -- we might have found our node, but that node might have children. + // in this case, we only allow you to set the color if you explicitly asked for a destructive + // write. + if (!node->isLeaf() && args.destructive) { + // if it does exist, make sure it has no children + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + node->deleteChildAtIndex(i); } - } - lastToNudgeVote[maxVoteIndex] = -1; - args->childOrder[nUncountedVotes - 1] = maxVoteIndex; - nUncountedVotes--; - } -} - -bool VoxelTree::nudgeCheck(VoxelNode* node, void* extraData) { - if (node->isLeaf()) { - // we have reached the deepest level of nodes/voxels - // now there are two scenarios - // 1) this node's size is <= the minNudgeAmount - // in which case we will simply call nudgeLeaf on this leaf - // 2) this node's size is still not <= the minNudgeAmount - // in which case we need to break this leaf down until the leaf sizes are <= minNudgeAmount - - NodeChunkArgs* args = (NodeChunkArgs*)extraData; - - // get octal code of this node - const unsigned char* octalCode = node->getOctalCode(); - - // get voxel position/size - VoxelPositionSize unNudgedDetails; - voxelDetailsForCode(octalCode, unNudgedDetails); - - // find necessary leaf size - float newLeafSize = findNewLeafSize(args->nudgeVec, unNudgedDetails.s); - - // check to see if this unNudged node can be nudged - if (unNudgedDetails.s <= newLeafSize) { - args->thisVoxelTree->nudgeLeaf(node, extraData); - return false; } else { - // break the current leaf into smaller chunks - args->thisVoxelTree->chunkifyLeaf(node); - } - } - return true; -} - -void VoxelTree::chunkifyLeaf(VoxelNode* node) { - // because this function will continue being called recursively - // we only need to worry about breaking this specific leaf down - if (!node->isColored()) { - return; - } - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - node->addChildAtIndex(i); - node->getChildAtIndex(i)->setColor(node->getColor()); - } -} - -// This function is called to nudge the leaves of a tree, given that the -// nudge amount is >= to the leaf scale. -void VoxelTree::nudgeLeaf(VoxelNode* node, void* extraData) { - NodeChunkArgs* args = (NodeChunkArgs*)extraData; - - // get octal code of this node - const unsigned char* octalCode = node->getOctalCode(); - - // get voxel position/size - VoxelPositionSize unNudgedDetails; - voxelDetailsForCode(octalCode, unNudgedDetails); - - VoxelDetail voxelDetails; - voxelDetails.x = unNudgedDetails.x; - voxelDetails.y = unNudgedDetails.y; - voxelDetails.z = unNudgedDetails.z; - voxelDetails.s = unNudgedDetails.s; - voxelDetails.red = node->getColor()[RED_INDEX]; - voxelDetails.green = node->getColor()[GREEN_INDEX]; - voxelDetails.blue = node->getColor()[BLUE_INDEX]; - glm::vec3 nudge = args->nudgeVec; - - // delete the old node - args->voxelEditSenderPtr->sendVoxelEditMessage(PACKET_TYPE_ERASE_VOXEL, voxelDetails); - - // nudge the old node - voxelDetails.x += nudge.x; - voxelDetails.y += nudge.y; - voxelDetails.z += nudge.z; - - // create a new voxel in its stead - args->voxelEditSenderPtr->sendVoxelEditMessage(PACKET_TYPE_SET_VOXEL_DESTRUCTIVE, voxelDetails); -} - -// Recurses voxel node with an operation function -void VoxelTree::recurseNodeForNudge(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData) { - NodeChunkArgs* args = (NodeChunkArgs*)extraData; - if (operation(node, extraData)) { - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - VoxelNode* child = node->getChildAtIndex(args->childOrder[i]); - if (child) { - recurseNodeForNudge(child, operation, extraData); + if (!node->isLeaf()) { + qDebug("WARNING! operation would require deleting children, add Voxel ignored!\n "); } } - } -} -void VoxelTree::nudgeSubTree(VoxelNode* nodeToNudge, const glm::vec3& nudgeAmount, VoxelEditPacketSender& voxelEditSender) { - if (nudgeAmount == glm::vec3(0, 0, 0)) { + // If we get here, then it means, we either had a true leaf to begin with, or we were in + // destructive mode and we deleted all the child trees. So we can color. + if (node->isLeaf()) { + // give this node its color + int octalCodeBytes = bytesRequiredForCodeLength(args.lengthOfCode); + + nodeColor newColor; + memcpy(newColor, args.codeColorBuffer + octalCodeBytes, SIZE_OF_COLOR_DATA); + newColor[SIZE_OF_COLOR_DATA] = 1; + node->setColor(newColor); + + // It's possible we just reset the node to it's exact same color, in + // which case we don't consider this to be dirty... + if (node->isDirty()) { + // track our tree dirtiness + _isDirty = true; + // track that path has changed + args.pathChanged = true; + } + } return; } - NodeChunkArgs args; - args.thisVoxelTree = this; - args.nudgeVec = nudgeAmount; - args.voxelEditSenderPtr = &voxelEditSender; - reorderChildrenForNudge(&args); + // Ok, we know we haven't reached our target node yet, so keep looking + printOctalCode(args.codeColorBuffer); + int childIndex = branchIndexWithDescendant(node->getOctalCode(), args.codeColorBuffer); + VoxelTreeElement* childNode = node->getChildAtIndex(childIndex); - recurseNodeForNudge(nodeToNudge, nudgeCheck, &args); + // If the branch we need to traverse does not exist, then create it on the way down... + if (!childNode) { + childNode = node->addChildAtIndex(childIndex); + } + + // recurse... + readCodeColorBufferToTreeRecursion(childNode, args); + + // Unwinding... + + // If the lower level did some work, then we need to let this node know, so it can + // do any bookkeeping it wants to, like color re-averaging, time stamp marking, etc + if (args.pathChanged) { + node->handleSubtreeChanged(this); + } } diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index 6289a88d9d..2654663101 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -11,310 +11,64 @@ #include #include +#include +#include +#include +#include +#include -#include "CoverageMap.h" -#include "JurisdictionMap.h" -#include "ViewFrustum.h" -#include "VoxelNode.h" -#include "VoxelNodeBag.h" +#include "VoxelTreeElement.h" #include "VoxelPacketData.h" #include "VoxelSceneStats.h" #include "VoxelEditPacketSender.h" -#include -#include +class ReadCodeColorBufferToTreeArgs; -// Callback function, for recuseTreeWithOperation -typedef bool (*RecurseVoxelTreeOperation)(VoxelNode* node, void* extraData); -typedef enum {GRADIENT, RANDOM, NATURAL} creationMode; - -const bool NO_EXISTS_BITS = false; -const bool WANT_EXISTS_BITS = true; -const bool NO_COLOR = false; -const bool WANT_COLOR = true; -const bool COLLAPSE_EMPTY_TREE = true; -const bool DONT_COLLAPSE = false; -const bool NO_OCCLUSION_CULLING = false; -const bool WANT_OCCLUSION_CULLING = true; - -const int DONT_CHOP = 0; -const int NO_BOUNDARY_ADJUST = 0; -const int LOW_RES_MOVING_ADJUST = 1; -const uint64_t IGNORE_LAST_SENT = 0; - -#define IGNORE_SCENE_STATS NULL -#define IGNORE_VIEW_FRUSTUM NULL -#define IGNORE_COVERAGE_MAP NULL -#define IGNORE_JURISDICTION_MAP NULL - -class EncodeBitstreamParams { -public: - int maxEncodeLevel; - int maxLevelReached; - const ViewFrustum* viewFrustum; - bool includeColor; - bool includeExistsBits; - int chopLevels; - bool deltaViewFrustum; - const ViewFrustum* lastViewFrustum; - bool wantOcclusionCulling; - int boundaryLevelAdjust; - float voxelSizeScale; - uint64_t lastViewFrustumSent; - bool forceSendScene; - VoxelSceneStats* stats; - CoverageMap* map; - JurisdictionMap* jurisdictionMap; - - // output hints from the encode process - typedef enum { - UNKNOWN, - DIDNT_FIT, - NULL_NODE, - TOO_DEEP, - OUT_OF_JURISDICTION, - LOD_SKIP, - OUT_OF_VIEW, - WAS_IN_VIEW, - NO_CHANGE, - OCCLUDED - } reason; - reason stopReason; - - EncodeBitstreamParams( - int maxEncodeLevel = INT_MAX, - const ViewFrustum* viewFrustum = IGNORE_VIEW_FRUSTUM, - bool includeColor = WANT_COLOR, - bool includeExistsBits = WANT_EXISTS_BITS, - int chopLevels = 0, - bool deltaViewFrustum = false, - const ViewFrustum* lastViewFrustum = IGNORE_VIEW_FRUSTUM, - bool wantOcclusionCulling = NO_OCCLUSION_CULLING, - CoverageMap* map = IGNORE_COVERAGE_MAP, - int boundaryLevelAdjust = NO_BOUNDARY_ADJUST, - float voxelSizeScale = DEFAULT_VOXEL_SIZE_SCALE, - uint64_t lastViewFrustumSent = IGNORE_LAST_SENT, - bool forceSendScene = true, - VoxelSceneStats* stats = IGNORE_SCENE_STATS, - JurisdictionMap* jurisdictionMap = IGNORE_JURISDICTION_MAP) : - maxEncodeLevel(maxEncodeLevel), - maxLevelReached(0), - viewFrustum(viewFrustum), - includeColor(includeColor), - includeExistsBits(includeExistsBits), - chopLevels(chopLevels), - deltaViewFrustum(deltaViewFrustum), - lastViewFrustum(lastViewFrustum), - wantOcclusionCulling(wantOcclusionCulling), - boundaryLevelAdjust(boundaryLevelAdjust), - voxelSizeScale(voxelSizeScale), - lastViewFrustumSent(lastViewFrustumSent), - forceSendScene(forceSendScene), - stats(stats), - map(map), - jurisdictionMap(jurisdictionMap), - stopReason(UNKNOWN) - {} - - void displayStopReason() { - printf("StopReason: "); - switch (stopReason) { - default: - case UNKNOWN: printf("UNKNOWN\n"); break; - - case DIDNT_FIT: printf("DIDNT_FIT\n"); break; - case NULL_NODE: printf("NULL_NODE\n"); break; - case TOO_DEEP: printf("TOO_DEEP\n"); break; - case OUT_OF_JURISDICTION: printf("OUT_OF_JURISDICTION\n"); break; - case LOD_SKIP: printf("LOD_SKIP\n"); break; - case OUT_OF_VIEW: printf("OUT_OF_VIEW\n"); break; - case WAS_IN_VIEW: printf("WAS_IN_VIEW\n"); break; - case NO_CHANGE: printf("NO_CHANGE\n"); break; - case OCCLUDED: printf("OCCLUDED\n"); break; - } - } -}; - -class ReadBitstreamToTreeParams { -public: - bool includeColor; - bool includeExistsBits; - VoxelNode* destinationNode; - QUuid sourceUUID; - bool wantImportProgress; - - ReadBitstreamToTreeParams( - bool includeColor = WANT_COLOR, - bool includeExistsBits = WANT_EXISTS_BITS, - VoxelNode* destinationNode = NULL, - QUuid sourceUUID = QUuid(), - bool wantImportProgress = false) : - includeColor(includeColor), - includeExistsBits(includeExistsBits), - destinationNode(destinationNode), - sourceUUID(sourceUUID), - wantImportProgress(wantImportProgress) - {} -}; - -class VoxelTree : public QObject { +class VoxelTree : public Octree { Q_OBJECT public: - // when a voxel is created in the tree (object new'd) - long voxelsCreated; - // when a voxel is colored/set in the tree (object may have already existed) - long voxelsColored; - long voxelsBytesRead; - - SimpleMovingAverage voxelsCreatedStats; - SimpleMovingAverage voxelsColoredStats; - SimpleMovingAverage voxelsBytesReadStats; VoxelTree(bool shouldReaverage = false); - ~VoxelTree(); - - VoxelNode* rootNode; - int leavesWrittenToBitstream; - - void eraseAllVoxels(); - - void processRemoveVoxelBitstream(const unsigned char* bitstream, int bufferSizeBytes); - void readBitstreamToTree(const unsigned char* bitstream, unsigned long int bufferSizeBytes, ReadBitstreamToTreeParams& args); - void readCodeColorBufferToTree(const unsigned char* codeColorBuffer, bool destructive = false); - void deleteVoxelCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE); - void printTreeForDebugging(VoxelNode* startNode); - void reaverageVoxelColors(VoxelNode* startNode); + + virtual VoxelTreeElement* createNewElement(unsigned char * octalCode = NULL) const; + VoxelTreeElement* getRoot() { return (VoxelTreeElement*)_rootNode; } void deleteVoxelAt(float x, float y, float z, float s); - VoxelNode* getVoxelAt(float x, float y, float z, float s) const; + VoxelTreeElement* getVoxelAt(float x, float y, float z, float s) const; void createVoxel(float x, float y, float z, float s, unsigned char red, unsigned char green, unsigned char blue, 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, - creationMode mode, bool destructive = false, bool debug = false); + void createSphere(float radius, float xc, float yc, float zc, float voxelSize, + bool solid, creationMode mode, bool destructive = false, bool debug = false); - void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL); - - void recurseTreeWithOperationDistanceSorted(RecurseVoxelTreeOperation operation, - const glm::vec3& point, void* extraData=NULL); + void nudgeSubTree(VoxelTreeElement* elementToNudge, const glm::vec3& nudgeAmount, VoxelEditPacketSender& voxelEditSender); - int encodeTreeBitstream(VoxelNode* node, VoxelPacketData* packetData, VoxelNodeBag& bag, - EncodeBitstreamParams& params) ; - bool isDirty() const { return _isDirty; } - void clearDirtyBit() { _isDirty = false; } - void setDirtyBit() { _isDirty = true; } - unsigned long int getNodesChangedFromBitstream() const { return _nodesChangedFromBitstream; } - - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - VoxelNode*& node, float& distance, BoxFace& face); - - bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration); - bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration); - - // Note: this assumes the fileFormat is the HIO individual voxels code files - void loadVoxelsFile(const char* fileName, bool wantColorRandomizer); - - // these will read/write files that match the wireformat, excluding the 'V' leading - void writeToSVOFile(const char* filename, VoxelNode* node = NULL); - bool readFromSVOFile(const char* filename); - // reads voxels from square image with alpha as a Y-axis + /// reads voxels from square image with alpha as a Y-axis bool readFromSquareARGB32Pixels(const char *filename); + + /// reads from minecraft file bool readFromSchematicFile(const char* filename); - - // VoxelTree does not currently handle its own locking, caller must use these to lock/unlock - void lockForRead() { lock.lockForRead(); } - void tryLockForRead() { lock.tryLockForRead(); } - void lockForWrite() { lock.lockForWrite(); } - void tryLockForWrite() { lock.tryLockForWrite(); } - void unlock() { lock.unlock(); } - unsigned long getVoxelCount(); - - void copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinationTree, bool rebaseToRoot); - void copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode); - - bool getShouldReaverage() const { return _shouldReaverage; } - - void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, - void* extraData, int recursionCount = 0); - - void recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation, - const glm::vec3& point, void* extraData, int recursionCount = 0); - - void nudgeSubTree(VoxelNode* nodeToNudge, const glm::vec3& nudgeAmount, VoxelEditPacketSender& voxelEditSender); + void readCodeColorBufferToTree(const unsigned char* codeColorBuffer, bool destructive = false); +/** signals: void importSize(float x, float y, float z); void importProgress(int progress); public slots: void cancelImport(); - +**/ private: - void deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraData); - void readCodeColorBufferToTreeRecursion(VoxelNode* node, void* extraData); - - int encodeTreeBitstreamRecursion(VoxelNode* node, - VoxelPacketData* packetData, VoxelNodeBag& bag, - EncodeBitstreamParams& params, int& currentEncodeLevel) const; - - static bool countVoxelsOperation(VoxelNode* node, void* extraData); - - VoxelNode* nodeForOctalCode(VoxelNode* ancestorNode, const unsigned char* needleCode, VoxelNode** parentOfFoundNode) const; - VoxelNode* createMissingNode(VoxelNode* lastParentNode, const unsigned char* codeToReach); - int readNodeData(VoxelNode *destinationNode, const unsigned char* nodeData, - int bufferSizeBytes, ReadBitstreamToTreeParams& args); - - bool _isDirty; - unsigned long int _nodesChangedFromBitstream; - bool _shouldReaverage; - bool _stopImport; - - /// Octal Codes of any subtrees currently being encoded. While any of these codes is being encoded, ancestors and - /// descendants of them can not be deleted. - std::set _codesBeingEncoded; - /// mutex lock to protect the encoding set - pthread_mutex_t _encodeSetLock; - - /// Called to indicate that a VoxelNode is in the process of being encoded. - void startEncoding(VoxelNode* node); - /// Called to indicate that a VoxelNode is done being encoded. - void doneEncoding(VoxelNode* node); - /// Is the Octal Code currently being deleted? - bool isEncoding(const unsigned char* codeBuffer); - - /// Octal Codes of any subtrees currently being deleted. While any of these codes is being deleted, ancestors and - /// descendants of them can not be encoded. - std::set _codesBeingDeleted; - /// mutex lock to protect the deleting set - pthread_mutex_t _deleteSetLock; - - /// Called to indicate that an octal code is in the process of being deleted. - void startDeleting(const unsigned char* code); - /// Called to indicate that an octal code is done being deleted. - void doneDeleting(const unsigned char* code); - /// Octal Codes that were attempted to be deleted but couldn't be because they were actively being encoded, and were - /// instead queued for later delete - std::set _codesPendingDelete; - /// mutex lock to protect the deleting set - pthread_mutex_t _deletePendingSetLock; - - /// Adds an Octal Code to the set of codes that needs to be deleted - void queueForLaterDelete(const unsigned char* codeBuffer); - /// flushes out any Octal Codes that had to be queued - void emptyDeleteQueue(); - // helper functions for nudgeSubTree - void recurseNodeForNudge(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData); - static bool nudgeCheck(VoxelNode* node, void* extraData); - void nudgeLeaf(VoxelNode* node, void* extraData); - void chunkifyLeaf(VoxelNode* node); - - QReadWriteLock lock; + void recurseNodeForNudge(VoxelTreeElement* element, RecurseOctreeOperation operation, void* extraData); + static bool nudgeCheck(OctreeElement* element, void* extraData); + void nudgeLeaf(VoxelTreeElement* element, void* extraData); + void chunkifyLeaf(VoxelTreeElement* element); + void readCodeColorBufferToTreeRecursion(VoxelTreeElement* node, ReadCodeColorBufferToTreeArgs& args); }; -float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale); - #endif /* defined(__hifi__VoxelTree__) */ diff --git a/libraries/voxels/src/VoxelTreeElement.cpp b/libraries/voxels/src/VoxelTreeElement.cpp new file mode 100644 index 0000000000..0dda7cc874 --- /dev/null +++ b/libraries/voxels/src/VoxelTreeElement.cpp @@ -0,0 +1,225 @@ +// +// VoxelTreeElement.cpp +// hifi +// +// Created by Stephen Birarda on 3/13/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#include +#include +#include + +#include "VoxelConstants.h" +#include "VoxelTreeElement.h" +#include "VoxelTree.h" + +VoxelTreeElement::VoxelTreeElement(unsigned char* octalCode) : OctreeElement(octalCode) { + // probably need to do all the color init here.... +}; + + +void VoxelTreeElement::init(unsigned char* octalCode) { + OctreeElement::init(octalCode); + _falseColored = false; // assume true color + _currentColor[0] = _currentColor[1] = _currentColor[2] = _currentColor[3] = 0; + _trueColor[0] = _trueColor[1] = _trueColor[2] = _trueColor[3] = 0; + _density = 0.0f; +} + +bool VoxelTreeElement::requiresSplit() const { + return isLeaf() && isColored(); +} + +void VoxelTreeElement::splitChildren() { + if (requiresSplit()) { + const nodeColor& ourColor = getColor(); + + // for colored leaves, we must add *all* the children + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + addChildAtIndex(i)->setColor(ourColor); + } + } +} + +bool VoxelTreeElement::appendElementData(OctreePacketData* packetData) const { + return packetData->appendColor(getColor()); +} + + +int VoxelTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args) { + const int BYTES_PER_COLOR = 3; + + // pull the color for this child + nodeColor newColor = { 128, 128, 128, 1}; + if (args.includeColor) { + memcpy(newColor, data, BYTES_PER_COLOR); + } + setColor(newColor); + return BYTES_PER_COLOR; +} + + +const uint8_t INDEX_FOR_NULL = 0; +uint8_t VoxelTreeElement::_nextIndex = INDEX_FOR_NULL + 1; // start at 1, 0 is reserved for NULL +std::map VoxelTreeElement::_mapVoxelSystemPointersToIndex; +std::map VoxelTreeElement::_mapIndexToVoxelSystemPointers; + +VoxelSystem* VoxelTreeElement::getVoxelSystem() const { + if (_voxelSystemIndex > INDEX_FOR_NULL) { + if (_mapIndexToVoxelSystemPointers.end() != _mapIndexToVoxelSystemPointers.find(_voxelSystemIndex)) { + + VoxelSystem* voxelSystem = _mapIndexToVoxelSystemPointers[_voxelSystemIndex]; + return voxelSystem; + } + } + return NULL; +} + +void VoxelTreeElement::setVoxelSystem(VoxelSystem* voxelSystem) { + if (voxelSystem == NULL) { + _voxelSystemIndex = INDEX_FOR_NULL; + } else { + uint8_t index; + if (_mapVoxelSystemPointersToIndex.end() != _mapVoxelSystemPointersToIndex.find(voxelSystem)) { + index = _mapVoxelSystemPointersToIndex[voxelSystem]; + } else { + index = _nextIndex; + _nextIndex++; + _mapVoxelSystemPointersToIndex[voxelSystem] = index; + _mapIndexToVoxelSystemPointers[index] = voxelSystem; + } + _voxelSystemIndex = index; + } +} + + +void VoxelTreeElement::setFalseColor(colorPart red, colorPart green, colorPart blue) { + if (_falseColored != true || _currentColor[0] != red || _currentColor[1] != green || _currentColor[2] != blue) { + _falseColored=true; + _currentColor[0] = red; + _currentColor[1] = green; + _currentColor[2] = blue; + _currentColor[3] = 1; // XXXBHG - False colors are always considered set + _isDirty = true; + markWithChangedTime(); + } +} + +void VoxelTreeElement::setFalseColored(bool isFalseColored) { + if (_falseColored != isFalseColored) { + // if we were false colored, and are no longer false colored, then swap back + if (_falseColored && !isFalseColored) { + memcpy(&_currentColor,&_trueColor,sizeof(nodeColor)); + } + _falseColored = isFalseColored; + _isDirty = true; + _density = 1.0f; // If color set, assume leaf, re-averaging will update density if needed. + markWithChangedTime(); + } +}; + + +void VoxelTreeElement::setColor(const nodeColor& color) { + if (_trueColor[0] != color[0] || _trueColor[1] != color[1] || _trueColor[2] != color[2]) { + memcpy(&_trueColor,&color,sizeof(nodeColor)); + if (!_falseColored) { + memcpy(&_currentColor,&color,sizeof(nodeColor)); + } + _isDirty = true; + _density = 1.0f; // If color set, assume leaf, re-averaging will update density if needed. + markWithChangedTime(); + } +} + + + +// will average the child colors... +void VoxelTreeElement::calculateAverageFromChildren() { + int colorArray[4] = {0,0,0,0}; + float density = 0.0f; + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + VoxelTreeElement* childAt = getChildAtIndex(i); + if (childAt && childAt->isColored()) { + for (int j = 0; j < 3; j++) { + colorArray[j] += childAt->getTrueColor()[j]; // color averaging should always be based on true colors + } + colorArray[3]++; + } + if (childAt) { + density += childAt->getDensity(); + } + } + density /= (float) NUMBER_OF_CHILDREN; + // + // The VISIBLE_ABOVE_DENSITY sets the density of matter above which an averaged color voxel will + // be set. It is an important physical constant in our universe. A number below 0.5 will cause + // things to get 'fatter' at a distance, because upward averaging will make larger voxels out of + // less data, which is (probably) going to be preferable because it gives a sense that there is + // something out there to go investigate. A number above 0.5 would cause the world to become + // more 'empty' at a distance. Exactly 0.5 would match the physical world, at least for materials + // that are not shiny and have equivalent ambient reflectance. + // + const float VISIBLE_ABOVE_DENSITY = 0.10f; + nodeColor newColor = { 0, 0, 0, 0}; + if (density > VISIBLE_ABOVE_DENSITY) { + // The density of material in the space of the voxel sets whether it is actually colored + for (int c = 0; c < 3; c++) { + // set the average color value + newColor[c] = colorArray[c] / colorArray[3]; + } + // set the alpha to 1 to indicate that this isn't transparent + newColor[3] = 1; + } + // Set the color from the average of the child colors, and update the density + setColor(newColor); + setDensity(density); +} + +// will detect if children are leaves AND the same color +// and in that case will delete the children and make this node +// a leaf, returns TRUE if all the leaves are collapsed into a +// single node +bool VoxelTreeElement::collapseChildren() { + // scan children, verify that they are ALL present and accounted for + bool allChildrenMatch = true; // assume the best (ottimista) + int red,green,blue; + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + VoxelTreeElement* childAt = getChildAtIndex(i); + // if no child, child isn't a leaf, or child doesn't have a color + if (!childAt || !childAt->isLeaf() || !childAt->isColored()) { + allChildrenMatch=false; + //qDebug("SADNESS child missing or not colored! i=%d\n",i); + break; + } else { + if (i==0) { + red = childAt->getColor()[0]; + green = childAt->getColor()[1]; + blue = childAt->getColor()[2]; + } else if (red != childAt->getColor()[0] || + green != childAt->getColor()[1] || blue != childAt->getColor()[2]) { + allChildrenMatch=false; + break; + } + } + } + + + if (allChildrenMatch) { + //qDebug("allChildrenMatch: pruning tree\n"); + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + OctreeElement* childAt = getChildAtIndex(i); + delete childAt; // delete all the child nodes + setChildAtIndex(i, NULL); // set it to NULL + } + nodeColor collapsedColor; + collapsedColor[0]=red; + collapsedColor[1]=green; + collapsedColor[2]=blue; + collapsedColor[3]=1; // color is set + setColor(collapsedColor); + } + return allChildrenMatch; +} + diff --git a/libraries/voxels/src/VoxelTreeElement.h b/libraries/voxels/src/VoxelTreeElement.h new file mode 100644 index 0000000000..eda76202e5 --- /dev/null +++ b/libraries/voxels/src/VoxelTreeElement.h @@ -0,0 +1,86 @@ +// +// VoxelTreeElement.h +// hifi +// +// Created by Stephen Birarda on 3/13/13. +// +// + +#ifndef __hifi__VoxelTreeElement__ +#define __hifi__VoxelTreeElement__ + +//#define HAS_AUDIT_CHILDREN +//#define SIMPLE_CHILD_ARRAY +#define SIMPLE_EXTERNAL_CHILDREN + +#include + +#include +#include + +#include "AABox.h" +#include "ViewFrustum.h" +#include "VoxelConstants.h" + +class VoxelTree; +class VoxelTreeElement; +class VoxelSystem; + +class VoxelTreeElement : public OctreeElement { + friend class VoxelTree; // to allow createElement to new us... + + VoxelTreeElement(unsigned char* octalCode = NULL); + +public: + virtual void init(unsigned char* octalCode); + + virtual bool hasContent() const { return isColored(); } + virtual void splitChildren(); + virtual bool requiresSplit() const; + virtual bool appendElementData(OctreePacketData* packetData) const; + virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); + virtual void calculateAverageFromChildren(); + virtual bool collapseChildren(); + + + glBufferIndex getBufferIndex() const { return _glBufferIndex; } + bool isKnownBufferIndex() const { return !_unknownBufferIndex; } + void setBufferIndex(glBufferIndex index) { _glBufferIndex = index; _unknownBufferIndex =(index == GLBUFFER_INDEX_UNKNOWN);} + VoxelSystem* getVoxelSystem() const; + void setVoxelSystem(VoxelSystem* voxelSystem); + + bool isColored() const { return _trueColor[3] == 1; } + + void setFalseColor(colorPart red, colorPart green, colorPart blue); + void setFalseColored(bool isFalseColored); + bool getFalseColored() { return _falseColored; } + void setColor(const nodeColor& color); + const nodeColor& getTrueColor() const { return _trueColor; } + const nodeColor& getColor() const { return _currentColor; } + + void setDensity(float density) { _density = density; } + float getDensity() const { return _density; } + + // type safe versions of OctreeElement methods + VoxelTreeElement* getChildAtIndex(int childIndex) { return (VoxelTreeElement*)OctreeElement::getChildAtIndex(childIndex); } + VoxelTreeElement* addChildAtIndex(int childIndex) { return (VoxelTreeElement*)OctreeElement::addChildAtIndex(childIndex); } + +protected: + + uint32_t _glBufferIndex : 24, /// Client only, vbo index for this voxel if being rendered, 3 bytes + _voxelSystemIndex : 8; /// Client only, index to the VoxelSystem rendering this voxel, 1 bytes + + // Support for _voxelSystemIndex, we use these static member variables to track the VoxelSystems that are + // in use by various voxel nodes. We map the VoxelSystem pointers into an 1 byte key, this limits us to at + // most 255 voxel systems in use at a time within the client. Which is far more than we need. + static uint8_t _nextIndex; + static std::map _mapVoxelSystemPointersToIndex; + static std::map _mapIndexToVoxelSystemPointers; + + float _density; /// Client and server, If leaf: density = 1, if internal node: 0-1 density of voxels inside, 4 bytes + + nodeColor _trueColor; /// Client and server, true color of this voxel, 4 bytes + nodeColor _currentColor; /// Client only, false color of this voxel, 4 bytes +}; + +#endif /* defined(__hifi__VoxelTreeElement__) */ \ No newline at end of file diff --git a/voxel-edit/CMakeLists.txt b/voxel-edit/CMakeLists.txt index e025099dc5..d41d929085 100644 --- a/voxel-edit/CMakeLists.txt +++ b/voxel-edit/CMakeLists.txt @@ -20,6 +20,9 @@ setup_hifi_project(${TARGET_NAME} TRUE) include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) +# link in the hifi octree library +link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) + # link in the hifi voxels library link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR}) diff --git a/voxel-edit/src/main.cpp b/voxel-edit/src/main.cpp index 5f67a46163..fee37ee887 100644 --- a/voxel-edit/src/main.cpp +++ b/voxel-edit/src/main.cpp @@ -13,10 +13,9 @@ #include #include -VoxelTree myTree; int _nodeCount=0; -bool countVoxelsOperation(VoxelNode* node, void* extraData) { +bool countVoxelsOperation(VoxelTreeElement* node, void* extraData) { if (node->isColored()){ _nodeCount++; } @@ -38,7 +37,7 @@ void voxelTutorial(VoxelTree * tree) { tree->createVoxel(0, 0, 0, voxelSize, 255, 255 ,255); // Here's an example of how to test if a voxel exists - VoxelNode* node = tree->getVoxelAt(0, 0, 0, voxelSize); + VoxelTreeElement* node = tree->getVoxelAt(0, 0, 0, voxelSize); if (node) { // and how to access it's color printf("corner point 0,0,0 exists... color is (%d,%d,%d) \n", @@ -104,9 +103,9 @@ void processSplitSVOFile(const char* splitSVOFile,const char* splitJurisdictionR // Delete the voxel for the EndNode from the temporary tree, so we can // import our endNode content into it... - endNodeTree.deleteVoxelCodeFromTree(endNodeCode, COLLAPSE_EMPTY_TREE); + endNodeTree.deleteOctalCodeFromTree(endNodeCode, COLLAPSE_EMPTY_TREE); - VoxelNode* endNode = rootSVO.getVoxelAt(endNodeDetails.x, + VoxelTreeElement* endNode = rootSVO.getVoxelAt(endNodeDetails.x, endNodeDetails.y, endNodeDetails.z, endNodeDetails.s); @@ -118,7 +117,7 @@ void processSplitSVOFile(const char* splitSVOFile,const char* splitJurisdictionR endNodeTree.writeToSVOFile(outputFileName); // Delete the voxel for the EndNode from the root tree... - rootSVO.deleteVoxelCodeFromTree(endNodeCode, COLLAPSE_EMPTY_TREE); + rootSVO.deleteOctalCodeFromTree(endNodeCode, COLLAPSE_EMPTY_TREE); // create a small voxel in center of each EndNode, this will is a hack // to work around a bug in voxel server that will send Voxel not exists @@ -149,7 +148,8 @@ public: }; -bool copyAndFillOperation(VoxelNode* node, void* extraData) { +bool copyAndFillOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; copyAndFillArgs* args = (copyAndFillArgs*)extraData; char outputMessage[128]; @@ -157,15 +157,15 @@ bool copyAndFillOperation(VoxelNode* node, void* extraData) { int percentDone = (100*args->inCount/args->originalCount); // For each leaf node... - if (node->isLeaf()) { + if (voxel->isLeaf()) { // create a copy of the leaf in the copy destination - float x = node->getCorner().x; - float y = node->getCorner().y; - float z = node->getCorner().z; - float s = node->getScale(); - unsigned char red = node->getTrueColor()[RED_INDEX]; - unsigned char green = node->getTrueColor()[GREEN_INDEX]; - unsigned char blue = node->getTrueColor()[BLUE_INDEX]; + float x = voxel->getCorner().x; + float y = voxel->getCorner().y; + float z = voxel->getCorner().z; + float s = voxel->getScale(); + unsigned char red = voxel->getTrueColor()[RED_INDEX]; + unsigned char green = voxel->getTrueColor()[GREEN_INDEX]; + unsigned char blue = voxel->getTrueColor()[BLUE_INDEX]; bool destructive = true; args->destinationTree->createVoxel(x, y, z, s, red, green, blue, destructive); @@ -204,16 +204,16 @@ void processFillSVOFile(const char* fillSVOFile) { VoxelTree filledSVO(true); // reaveraging originalSVO.readFromSVOFile(fillSVOFile); - qDebug("Nodes after loading %lu nodes\n", originalSVO.getVoxelCount()); - originalSVO.reaverageVoxelColors(originalSVO.rootNode); + qDebug("Nodes after loading %lu nodes\n", originalSVO.getOctreeElementsCount()); + originalSVO.reaverageOctreeElements(); qDebug("Original Voxels reAveraged\n"); - qDebug("Nodes after reaveraging %lu nodes\n", originalSVO.getVoxelCount()); + qDebug("Nodes after reaveraging %lu nodes\n", originalSVO.getOctreeElementsCount()); copyAndFillArgs args; args.destinationTree = &filledSVO; args.inCount = 0; args.outCount = 0; - args.originalCount = originalSVO.getVoxelCount(); + args.originalCount = originalSVO.getOctreeElementsCount(); printf("Begin processing...\n"); originalSVO.recurseTreeWithOperation(copyAndFillOperation, &args); @@ -222,10 +222,10 @@ void processFillSVOFile(const char* fillSVOFile) { qDebug("Original input nodes used for filling %lu nodes\n", args.originalCount); qDebug("Input nodes traversed during filling %lu nodes\n", args.inCount); qDebug("Nodes created during filling %lu nodes\n", args.outCount); - qDebug("Nodes after filling %lu nodes\n", filledSVO.getVoxelCount()); + qDebug("Nodes after filling %lu nodes\n", filledSVO.getOctreeElementsCount()); - filledSVO.reaverageVoxelColors(filledSVO.rootNode); - qDebug("Nodes after reaveraging %lu nodes\n", filledSVO.getVoxelCount()); + filledSVO.reaverageOctreeElements(); + qDebug("Nodes after reaveraging %lu nodes\n", filledSVO.getOctreeElementsCount()); sprintf(outputFileName, "filled%s", fillSVOFile); printf("outputFile: %s\n", outputFileName); @@ -234,11 +234,17 @@ void processFillSVOFile(const char* fillSVOFile) { printf("exiting now\n"); } +void unitTest(VoxelTree * tree); + int main(int argc, const char * argv[]) { + VoxelTree myTree; + qInstallMessageHandler(sharedMessageHandler); + unitTest(&myTree); + const char* GET_OCTCODE = "--getOctCode"; const char* octcodeParams = getCmdOption(argc, argv, GET_OCTCODE); @@ -347,17 +353,17 @@ int main(int argc, const char * argv[]) addSurfaceScene(&myTree); } - unsigned long nodeCount = myTree.getVoxelCount(); + unsigned long nodeCount = myTree.getOctreeElementsCount(); printf("Nodes after adding scenes: %ld nodes\n", nodeCount); myTree.writeToSVOFile("voxels.svo"); - - } + } return 0; } void unitTest(VoxelTree * tree) { printf("unit tests...\n"); + unsigned long nodeCount; // We want our corner voxels to be about 1/2 meter high, and our TREE_SCALE is in meters, so... float voxelSize = 0.5f; @@ -365,59 +371,114 @@ void unitTest(VoxelTree * tree) { // Here's an example of how to create a voxel. printf("creating corner points...\n"); tree->createVoxel(0, 0, 0, voxelSize, 255, 255 ,255); + printf("Nodes at line %d... %ld nodes\n", __LINE__, tree->getOctreeElementsCount()); + // Here's an example of how to test if a voxel exists - VoxelNode* node = tree->getVoxelAt(0, 0, 0, voxelSize); + VoxelTreeElement* node = tree->getVoxelAt(0, 0, 0, voxelSize); if (node) { // and how to access it's color - printf("corner point 0,0,0 exists... color is (%d,%d,%d) \n", + printf("CORRECT - corner point 0,0,0 exists... color is (%d,%d,%d) \n", node->getColor()[0], node->getColor()[1], node->getColor()[2]); } + printf("Nodes at line %d... %ld nodes\n", __LINE__, tree->getOctreeElementsCount()); + // here's an example of how to delete a voxel printf("attempting to delete corner point 0,0,0\n"); tree->deleteVoxelAt(0, 0, 0, voxelSize); + printf("Nodes at line %d... %ld nodes\n", __LINE__, tree->getOctreeElementsCount()); + // Test to see that the delete worked... it should be FALSE... if (tree->getVoxelAt(0, 0, 0, voxelSize)) { - printf("corner point 0,0,0 exists...\n"); + printf("FAIL corner point 0,0,0 exists...\n"); } else { - printf("corner point 0,0,0 does not exists...\n"); + printf("CORRECT corner point 0,0,0 does not exists...\n"); } - + + printf("Nodes at line %d... %ld nodes\n", __LINE__, tree->getOctreeElementsCount()); + tree->createVoxel(0, 0, 0, voxelSize, 255, 255 ,255); if (tree->getVoxelAt(0, 0, 0, voxelSize)) { - printf("corner point 0,0,0 exists...\n"); + printf("CORRECT - corner point 0,0,0 exists... color is (%d,%d,%d) \n", + node->getColor()[0], node->getColor()[1], node->getColor()[2]); } else { - printf("corner point 0,0,0 does not exists...\n"); + printf("FAIL corner point 0,0,0 does not exists...\n"); } - tree->createVoxel(voxelSize, 0, 0, voxelSize, 255, 255 ,255); + printf("Nodes at line %d... %ld nodes\n", __LINE__, tree->getOctreeElementsCount()); + + tree->createVoxel(voxelSize, 0, 0, voxelSize, 255, 255 ,0); if (tree->getVoxelAt(voxelSize, 0, 0, voxelSize)) { - printf("corner point voxelSize,0,0 exists...\n"); + printf("CORRECT - corner point voxelSize,0,0 exists... color is (%d,%d,%d) \n", + node->getColor()[0], node->getColor()[1], node->getColor()[2]); } else { - printf("corner point voxelSize,0,0 does not exists...\n"); + printf("FAIL corner point voxelSize,0,0 does not exists...\n"); } - tree->createVoxel(0, 0, voxelSize, voxelSize, 255, 255 ,255); + printf("Nodes at line %d... %ld nodes\n", __LINE__, tree->getOctreeElementsCount()); + + tree->createVoxel(0, 0, voxelSize, voxelSize, 255, 0 ,0); if (tree->getVoxelAt(0, 0, voxelSize, voxelSize)) { - printf("corner point 0, 0, voxelSize exists...\n"); + printf("CORRECT - corner point 0, 0, voxelSize exists... color is (%d,%d,%d) \n", + node->getColor()[0], node->getColor()[1], node->getColor()[2]); } else { - printf("corner point 0, 0, voxelSize does not exists...\n"); + printf("FAILED corner point 0, 0, voxelSize does not exists...\n"); } - tree->createVoxel(voxelSize, 0, voxelSize, voxelSize, 255, 255 ,255); + printf("Nodes at line %d... %ld nodes\n", __LINE__, tree->getOctreeElementsCount()); + + tree->createVoxel(voxelSize, 0, voxelSize, voxelSize, 0, 0 ,255); if (tree->getVoxelAt(voxelSize, 0, voxelSize, voxelSize)) { - printf("corner point voxelSize, 0, voxelSize exists...\n"); + printf("CORRECT - corner point voxelSize, 0, voxelSize exists... color is (%d,%d,%d) \n", + node->getColor()[0], node->getColor()[1], node->getColor()[2]); } else { printf("corner point voxelSize, 0, voxelSize does not exists...\n"); } + printf("Nodes at line %d... %ld nodes\n", __LINE__, tree->getOctreeElementsCount()); + printf("check root voxel exists...\n"); if (tree->getVoxelAt(0,0,0,1.0)) { printf("of course it does\n"); } else { printf("WTH!?!\n"); } + + nodeCount = tree->getOctreeElementsCount(); + printf("Nodes before writing file: %ld nodes\n", nodeCount); + + tree->writeToSVOFile("voxels.svo"); + + printf("erasing the tree...\n"); + tree->eraseAllOctreeElements(); + + printf("check root voxel exists...\n"); + if (tree->getVoxelAt(0,0,0,1.0)) { + printf("of course it does\n"); + } else { + printf("WTH!?!\n"); + } + + // this should not exist... we just deleted it... + if (tree->getVoxelAt(voxelSize, 0, voxelSize, voxelSize)) { + printf("corner point voxelSize, 0, voxelSize exists...\n"); + } else { + printf("corner point voxelSize, 0, voxelSize does not exists...\n"); + } + + tree->readFromSVOFile("voxels.svo"); + + // this should exist... we just loaded it... + if (tree->getVoxelAt(voxelSize, 0, voxelSize, voxelSize)) { + printf("corner point voxelSize, 0, voxelSize exists...\n"); + } else { + printf("corner point voxelSize, 0, voxelSize does not exists...\n"); + } + + nodeCount = tree->getOctreeElementsCount(); + printf("Nodes after loading file: %ld nodes\n", nodeCount); + }