3
0
Fork 0
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:
ZappoMan 2013-12-03 17:32:02 -08:00
parent 44f92fb47c
commit 9faef65ccc
87 changed files with 7659 additions and 4885 deletions
animation-server
assignment-client
interface
libraries
voxel-edit

View file

@ -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})

View file

@ -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})

View file

@ -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})

View file

@ -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();
}

View file

@ -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();

View file

@ -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");

View file

@ -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();
}

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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));

View file

@ -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);

View file

@ -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());

View file

@ -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})

View 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})

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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();

View file

@ -12,9 +12,9 @@
#include <QDebug>
#include <PacketHeaders.h>
#include <OctalCode.h>
#include "JurisdictionMap.h"
#include "VoxelNode.h"
// standard assignment

View file

@ -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__

File diff suppressed because it is too large Load diff

View 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__) */

View 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

View file

@ -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);
}
}

View file

@ -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__) */

View file

@ -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()
}

View 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__) */

View 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");
}

View 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__) */

View file

@ -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

View file

@ -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; }

View 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;
}

View 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__) */

View 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;
}

View 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__) */

View file

@ -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

View file

@ -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:

View 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})

View 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
}

View 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__

View 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);
}
}
}
}

View 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__) */

View 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;
}

View 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__

View 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;
}
}
};

View 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__

View 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__

View 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;
}

View file

@ -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__

View 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})

View file

@ -0,0 +1 @@
// ParticleTree.cpp

View file

@ -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) {

View file

@ -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;

View file

@ -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,

View file

@ -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})

View file

@ -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);
}

View file

@ -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; }

View file

@ -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

View file

@ -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...

View file

@ -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"

View file

@ -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");

View file

@ -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.

View file

@ -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)

View file

@ -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

View file

@ -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__) */

View file

@ -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) {
};

View file

@ -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__) */

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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__) */

View 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;
}

View 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__) */

View file

@ -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})

View file

@ -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);
}