mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-15 13:58:47 +02:00
first cut at splitting out octree base classes
This commit is contained in:
parent
44f92fb47c
commit
9faef65ccc
87 changed files with 7659 additions and 4885 deletions
animation-server
assignment-client
interface
libraries
avatars
octree
CMakeLists.txt
src
AABox.cppAABox.hCoverageMap.cppCoverageMap.hCoverageMapV2.cppCoverageMapV2.hJurisdictionListener.cppJurisdictionListener.hJurisdictionMap.cppJurisdictionMap.hJurisdictionSender.cppJurisdictionSender.hOctree.cppOctree.hOctreeConstants.hOctreeElement.cppOctreeElement.hOctreeElementBag.cppOctreeElementBag.hOctreePacketData.cppOctreePacketData.hOctreeProjectedPolygon.cppOctreeProjectedPolygon.hOctreeQuery.cppOctreeQuery.hOctreeSceneStats.cppOctreeSceneStats.hPlane.cppPlane.hViewFrustum.cppViewFrustum.h
particle-server
particles
shared/src
voxel-server-library
voxels
voxel-edit
|
@ -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})
|
||||
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<FBXMesh*>(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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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})
|
||||
|
|
27
libraries/octree/CMakeLists.txt
Normal file
27
libraries/octree/CMakeLists.txt
Normal file
|
@ -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})
|
|
@ -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
|
|
@ -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 <glm/glm.hpp>
|
||||
#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:
|
|
@ -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
|
|
@ -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 <glm/glm.hpp>
|
||||
|
||||
#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();
|
|
@ -12,9 +12,9 @@
|
|||
#include <QDebug>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
#include <OctalCode.h>
|
||||
|
||||
#include "JurisdictionMap.h"
|
||||
#include "VoxelNode.h"
|
||||
|
||||
|
||||
// standard assignment
|
|
@ -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__
|
1512
libraries/octree/src/Octree.cpp
Normal file
1512
libraries/octree/src/Octree.cpp
Normal file
File diff suppressed because it is too large
Load diff
313
libraries/octree/src/Octree.h
Normal file
313
libraries/octree/src/Octree.h
Normal file
|
@ -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 <set>
|
||||
#include <SimpleMovingAverage.h>
|
||||
|
||||
//#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 <QObject>
|
||||
#include <QReadWriteLock>
|
||||
|
||||
// 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<const unsigned char*> _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<const unsigned char*> _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<const unsigned char*> _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__) */
|
47
libraries/octree/src/OctreeConstants.h
Normal file
47
libraries/octree/src/OctreeConstants.h
Normal file
|
@ -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 <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <OctalCode.h>
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
// 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
|
|
@ -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<VoxelSystem*, uint8_t> VoxelNode::_mapVoxelSystemPointersToIndex;
|
||||
std::map<uint8_t, VoxelSystem*> 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<QString, uint16_t> VoxelNode::_mapSourceUUIDsToKeys;
|
||||
std::map<uint16_t, QString> VoxelNode::_mapKeysToSourceUUIDs;
|
||||
uint16_t OctreeElement::_nextUUIDKey = KEY_FOR_NULL + 1; // start at 1, 0 is reserved for NULL
|
||||
std::map<QString, uint16_t> OctreeElement::_mapSourceUUIDsToKeys;
|
||||
std::map<uint16_t, QString> 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<VoxelNodeDeleteHook*> VoxelNode::_deleteHooks;
|
||||
QReadWriteLock OctreeElement::_deleteHooksLock;
|
||||
std::vector<OctreeElementDeleteHook*> 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<VoxelNodeUpdateHook*> VoxelNode::_updateHooks;
|
||||
std::vector<OctreeElementUpdateHook*> 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);
|
||||
}
|
||||
}
|
|
@ -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 <SharedUtil.h>
|
||||
#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<VoxelSystem*, uint8_t> _mapVoxelSystemPointersToIndex;
|
||||
static std::map<uint8_t, VoxelSystem*> _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<VoxelNodeDeleteHook*> _deleteHooks;
|
||||
static std::vector<OctreeElementDeleteHook*> _deleteHooks;
|
||||
|
||||
//static QReadWriteLock _updateHooksLock;
|
||||
static std::vector<VoxelNodeUpdateHook*> _updateHooks;
|
||||
static std::vector<OctreeElementUpdateHook*> _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__) */
|
||||
#endif /* defined(__hifi__OctreeElement__) */
|
|
@ -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 <OctalCode.h>
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
47
libraries/octree/src/OctreeElementBag.h
Normal file
47
libraries/octree/src/OctreeElementBag.h
Normal file
|
@ -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__) */
|
325
libraries/octree/src/OctreePacketData.cpp
Normal file
325
libraries/octree/src/OctreePacketData.cpp
Normal file
|
@ -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 <PerfStat.h>
|
||||
#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");
|
||||
}
|
182
libraries/octree/src/OctreePacketData.h
Normal file
182
libraries/octree/src/OctreePacketData.h
Normal file
|
@ -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 <SharedUtil.h>
|
||||
#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__) */
|
|
@ -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
|
|
@ -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; }
|
152
libraries/octree/src/OctreeQuery.cpp
Normal file
152
libraries/octree/src/OctreeQuery.cpp
Normal file
|
@ -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 <cstdio>
|
||||
#include <cstring>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <UUID.h>
|
||||
#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;
|
||||
}
|
||||
|
115
libraries/octree/src/OctreeQuery.h
Normal file
115
libraries/octree/src/OctreeQuery.h
Normal file
|
@ -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 <string>
|
||||
#include <inttypes.h>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUuid>
|
||||
#include <QtCore/QVariantMap>
|
||||
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
||||
#include <NodeData.h>
|
||||
|
||||
// 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__) */
|
842
libraries/octree/src/OctreeSceneStats.cpp
Normal file
842
libraries/octree/src/OctreeSceneStats.cpp
Normal file
|
@ -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 <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
276
libraries/octree/src/OctreeSceneStats.h
Normal file
276
libraries/octree/src/OctreeSceneStats.h
Normal file
|
@ -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 <stdint.h>
|
||||
#include <NodeList.h>
|
||||
#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<unsigned char*>& 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<unsigned char*> _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<QUuid, OctreeSceneStats> NodeToOctreeSceneStats;
|
||||
typedef std::map<QUuid, OctreeSceneStats>::iterator NodeToOctreeSceneStatsIterator;
|
||||
|
||||
#endif /* defined(__hifi__OctreeSceneStats__) */
|
|
@ -16,11 +16,11 @@
|
|||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#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
|
|
@ -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:
|
42
libraries/particle-server/CMakeLists.txt.disabled
Normal file
42
libraries/particle-server/CMakeLists.txt.disabled
Normal file
|
@ -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})
|
||||
|
86
libraries/particle-server/src/ParticlePersistThread.cpp
Normal file
86
libraries/particle-server/src/ParticlePersistThread.cpp
Normal file
|
@ -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 <QDebug>
|
||||
#include <NodeList.h>
|
||||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#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
|
||||
}
|
44
libraries/particle-server/src/ParticlePersistThread.h
Normal file
44
libraries/particle-server/src/ParticlePersistThread.h
Normal file
|
@ -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 <GenericThread.h>
|
||||
#include <NetworkPacket.h>
|
||||
#include <ParticleTree.h>
|
||||
|
||||
/// 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__
|
276
libraries/particle-server/src/ParticleReceiverData.cpp
Normal file
276
libraries/particle-server/src/ParticleReceiverData.cpp
Normal file
|
@ -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 <cstring>
|
||||
#include <cstdio>
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
123
libraries/particle-server/src/ParticleReceiverData.h
Normal file
123
libraries/particle-server/src/ParticleReceiverData.h
Normal file
|
@ -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 <iostream>
|
||||
#include <NodeData.h>
|
||||
#include <ParticlePacketData.h>
|
||||
#include <ParticleQuery.h>
|
||||
|
||||
#include <CoverageMap.h>
|
||||
#include <ParticleConstants.h>
|
||||
#include <ParticleNodeBag.h>
|
||||
#include <ParticleSceneStats.h>
|
||||
|
||||
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__) */
|
551
libraries/particle-server/src/ParticleSendThread.cpp
Normal file
551
libraries/particle-server/src/ParticleSendThread.cpp
Normal file
|
@ -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 <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
47
libraries/particle-server/src/ParticleSendThread.h
Normal file
47
libraries/particle-server/src/ParticleSendThread.h
Normal file
|
@ -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 <GenericThread.h>
|
||||
#include <NetworkPacket.h>
|
||||
#include <ParticleTree.h>
|
||||
#include <ParticleNodeBag.h>
|
||||
#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__
|
802
libraries/particle-server/src/ParticleServer.cpp
Normal file
802
libraries/particle-server/src/ParticleServer.cpp
Normal file
|
@ -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 <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <time.h>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <Logging.h>
|
||||
#include <OctalCode.h>
|
||||
#include <NodeList.h>
|
||||
#include <NodeTypes.h>
|
||||
//#include <ParticleTree.h>
|
||||
#include "ParticleReceiverData.h"
|
||||
#include <SharedUtil.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SceneUtils.h>
|
||||
#include <PerfStat.h>
|
||||
#include <JurisdictionSender.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Syssocket.h"
|
||||
#include "Systime.h"
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#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", "<html><doc>\r\n");
|
||||
mg_printf(connection, "%s", "<pre>\r\n");
|
||||
mg_printf(connection, "%s", "<b>Your Particle Server is running... <a href='/'>[RELOAD]</a></b>\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", "<b>Configuration:</b>\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", "<b>Current Nodes in scene:</b>\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", "<b>Particle Packet Statistics...</b>\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", "<b>Particle Edit Statistics... <a href='/resetStats'>[RESET]</a></b>\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", "<b>Current Memory Usage Statistics</b>\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", "</pre>\r\n");
|
||||
|
||||
mg_printf(connection, "%s", "</doc></html>");
|
||||
|
||||
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<const char**>(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;
|
||||
}
|
||||
}
|
||||
};
|
91
libraries/particle-server/src/ParticleServer.h
Normal file
91
libraries/particle-server/src/ParticleServer.h
Normal file
|
@ -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 <QStringList>
|
||||
#include <QDateTime>
|
||||
#include <QtCore/QCoreApplication>
|
||||
|
||||
#include <Assignment.h>
|
||||
|
||||
#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__
|
28
libraries/particle-server/src/ParticleServerConsts.h
Normal file
28
libraries/particle-server/src/ParticleServerConsts.h
Normal file
|
@ -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 <SharedUtil.h>
|
||||
#include <NodeList.h> // for MAX_PACKET_SIZE
|
||||
#include <JurisdictionSender.h>
|
||||
#include <ParticleTree.h>
|
||||
|
||||
#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__
|
222
libraries/particle-server/src/ParticleServerPacketProcessor.cpp
Normal file
222
libraries/particle-server/src/ParticleServerPacketProcessor.cpp
Normal file
|
@ -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 <PacketHeaders.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
@ -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 <map>
|
||||
|
||||
#include <ReceivedPacketProcessor.h>
|
||||
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<QUuid, SingleSenderStats> NodeToSenderStatsMap;
|
||||
typedef std::map<QUuid, SingleSenderStats>::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__
|
28
libraries/particles/CMakeLists.txt
Normal file
28
libraries/particles/CMakeLists.txt
Normal file
|
@ -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})
|
1
libraries/particles/src/ParticleTree.cpp
Normal file
1
libraries/particles/src/ParticleTree.cpp
Normal file
|
@ -0,0 +1 @@
|
|||
// ParticleTree.cpp
|
|
@ -9,8 +9,7 @@
|
|||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "SharedUtil.h"
|
||||
#include "GeometryUtil.h"
|
||||
|
||||
glm::vec3 computeVectorFromPointToSegment(const glm::vec3& point, const glm::vec3& start, const glm::vec3& end) {
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -24,7 +24,12 @@
|
|||
#include <sys/time.h>
|
||||
#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,
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
#include <CoverageMap.h>
|
||||
#include <VoxelConstants.h>
|
||||
#include <VoxelNodeBag.h>
|
||||
#include <OctreeElementBag.h>
|
||||
#include <VoxelSceneStats.h>
|
||||
|
||||
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; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#include <GenericThread.h>
|
||||
#include <NetworkPacket.h>
|
||||
#include <VoxelTree.h>
|
||||
#include <VoxelNodeBag.h>
|
||||
#include <OctreeElementBag.h>
|
||||
#include "VoxelNodeData.h"
|
||||
#include "VoxelServer.h"
|
||||
|
||||
|
|
|
@ -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", "<b>Current Memory Usage Statistics</b>\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");
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -18,24 +18,10 @@
|
|||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <OctreeConstants.h>
|
||||
|
||||
// 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
|
|
@ -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__) */
|
|
@ -9,317 +9,8 @@
|
|||
#include <PerfStat.h>
|
||||
#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) {
|
||||
};
|
||||
|
|
|
@ -20,8 +20,11 @@
|
|||
#define __hifi__VoxelPacketData__
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include <OctreePacketData.h>
|
||||
|
||||
#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__) */
|
|
@ -6,147 +6,9 @@
|
|||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <UUID.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,102 +9,14 @@
|
|||
#ifndef __hifi__VoxelQuery__
|
||||
#define __hifi__VoxelQuery__
|
||||
|
||||
#include <string>
|
||||
#include <inttypes.h>
|
||||
#include <vector>
|
||||
#include <OctreeQuery.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
class VoxelQuery : public OctreeQuery {
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUuid>
|
||||
#include <QtCore/QVariantMap>
|
||||
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
||||
#include <NodeData.h>
|
||||
|
||||
// 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
|
||||
|
|
|
@ -7,836 +7,8 @@
|
|||
//
|
||||
//
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#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
|
||||
|
||||
|
|
|
@ -10,262 +10,14 @@
|
|||
#ifndef __hifi__VoxelSceneStats__
|
||||
#define __hifi__VoxelSceneStats__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <NodeList.h>
|
||||
#include "JurisdictionMap.h"
|
||||
|
||||
#define GREENISH 0x40ff40d0
|
||||
#define YELLOWISH 0xffef40c0
|
||||
#define GREYISH 0xd0d0d0a0
|
||||
|
||||
class VoxelNode;
|
||||
#include <OctreeSceneStats.h>
|
||||
|
||||
/// 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<unsigned char*>& 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<unsigned char*> _jurisdictionEndNodes;
|
||||
};
|
||||
|
||||
/// Map between node IDs and their reported VoxelSceneStats. Typically used by classes that need to know which nodes sent
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,310 +11,64 @@
|
|||
|
||||
#include <set>
|
||||
#include <SimpleMovingAverage.h>
|
||||
#include <OctreeElementBag.h>
|
||||
#include <Octree.h>
|
||||
#include <CoverageMap.h>
|
||||
#include <JurisdictionMap.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
#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 <QObject>
|
||||
#include <QReadWriteLock>
|
||||
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<const unsigned char*> _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<const unsigned char*> _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<const unsigned char*> _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__) */
|
||||
|
|
225
libraries/voxels/src/VoxelTreeElement.cpp
Normal file
225
libraries/voxels/src/VoxelTreeElement.cpp
Normal file
|
@ -0,0 +1,225 @@
|
|||
//
|
||||
// VoxelTreeElement.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Stephen Birarda on 3/13/13.
|
||||
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <NodeList.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
#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<VoxelSystem*, uint8_t> VoxelTreeElement::_mapVoxelSystemPointersToIndex;
|
||||
std::map<uint8_t, VoxelSystem*> 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;
|
||||
}
|
||||
|
86
libraries/voxels/src/VoxelTreeElement.h
Normal file
86
libraries/voxels/src/VoxelTreeElement.h
Normal file
|
@ -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 <QReadWriteLock>
|
||||
|
||||
#include <OctreeElement.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#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<VoxelSystem*, uint8_t> _mapVoxelSystemPointersToIndex;
|
||||
static std::map<uint8_t, VoxelSystem*> _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__) */
|
|
@ -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})
|
||||
|
||||
|
|
|
@ -13,10 +13,9 @@
|
|||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
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);
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue