diff --git a/interface/src/Avatar.cpp b/interface/src/Avatar.cpp index dc1b6d99f8..08aa86779f 100644 --- a/interface/src/Avatar.cpp +++ b/interface/src/Avatar.cpp @@ -139,6 +139,7 @@ Avatar::Avatar(bool isMine) { _interactingOther = NULL; //_canReachToOtherAvatar = false; _handHoldingPosition = glm::vec3( 0.0, 0.0, 0.0 ); + _distanceToNearestAvatar = std::numeric_limits::max(); initializeSkeleton(); @@ -227,6 +228,7 @@ Avatar::Avatar(const Avatar &otherAvatar) { _head.lastLoudness = otherAvatar._head.lastLoudness; _head.browAudioLift = otherAvatar._head.browAudioLift; _head.noise = otherAvatar._head.noise; + _distanceToNearestAvatar = otherAvatar._distanceToNearestAvatar; initializeSkeleton(); @@ -324,6 +326,7 @@ bool Avatar::getIsNearInteractingOther() { void Avatar::simulate(float deltaTime) { + //keep this - I'm still using it to test things.... /* @@ -355,7 +358,10 @@ _head.leanForward = 0.02 * sin( tt * 0.8 ); // if the avatar being simulated is mine, then loop through // all the other avatars for potential interactions... if ( _isMine ) - { + { + // Reset detector for nearest avatar + _distanceToNearestAvatar = std::numeric_limits::max(); + AgentList* agentList = AgentList::getInstance(); for (AgentList::iterator agent = agentList->begin(); agent != agentList->end(); agent++) { if (agent->getLinkedData() != NULL && agent->getType() == AGENT_TYPE_AVATAR) { @@ -369,7 +375,9 @@ _head.leanForward = 0.02 * sin( tt * 0.8 ); v -= otherAvatar->getBonePosition( AVATAR_BONE_RIGHT_SHOULDER ); float distance = glm::length( v ); - if ( distance < _maxArmLength + _maxArmLength ) { + if (distance < _distanceToNearestAvatar) { _distanceToNearestAvatar = distance; } + + if (distance < _maxArmLength + _maxArmLength) { _interactingOther = otherAvatar; _avatarTouch.setAbleToReachOtherAvatar(true); @@ -479,6 +487,15 @@ _head.leanForward = 0.02 * sin( tt * 0.8 ); // decay velocity _velocity *= ( 1.0 - LIN_VEL_DECAY * deltaTime ); + + // If someone is near, damp velocity as a function of closeness + const float AVATAR_BRAKING_RANGE = 1.2f; + const float AVATAR_BRAKING_STRENGTH = 25.f; + if (_isMine && (_distanceToNearestAvatar < AVATAR_BRAKING_RANGE)) { + _velocity *= + (1.f - deltaTime * AVATAR_BRAKING_STRENGTH * + (AVATAR_BRAKING_RANGE - _distanceToNearestAvatar)); + } // update head information updateHead(deltaTime); @@ -1359,7 +1376,9 @@ void Avatar::SetNewHeadTarget(float pitch, float yaw) { _head.yawTarget = yaw; } +// // Process UDP interface data from Android transmitter or Google Glass +// void Avatar::processTransmitterData(unsigned char* packetData, int numBytes) { // Read a packet from a transmitter app, process the data float @@ -1370,30 +1389,43 @@ void Avatar::processTransmitterData(unsigned char* packetData, int numBytes) { rot1, rot2, rot3, rot4; // Rotation of device: // rot1 = roll, ranges from -1 to 1, 0 = flat on table // rot2 = pitch, ranges from -1 to 1, 0 = flat on table - // rot3 = yaw, ranges from -1 to 1 + // rot3 = yaw, ranges from -1 to 1 + char device[100]; // Device ID - const bool IS_GLASS = false; // Whether to assume this is a Google glass transmitting + enum deviceTypes { DEVICE_GLASS, DEVICE_ANDROID, DEVICE_IPHONE, DEVICE_UNKNOWN }; - sscanf((char *)packetData, "tacc %f %f %f gra %f %f %f gyr %f %f %f lin %f %f %f rot %f %f %f %f", + sscanf((char *)packetData, + "tacc %f %f %f gra %f %f %f gyr %f %f %f lin %f %f %f rot %f %f %f %f dna \"%s", &accX, &accY, &accZ, &graX, &graY, &graZ, &gyrX, &gyrY, &gyrZ, &linX, &linY, &linZ, - &rot1, &rot2, &rot3, &rot4); + &rot1, &rot2, &rot3, &rot4, (char *)&device); + + // decode transmitter device type + deviceTypes deviceType = DEVICE_UNKNOWN; + if (strcmp(device, "ADR")) { + deviceType = DEVICE_ANDROID; + } else { + deviceType = DEVICE_GLASS; + } if (_transmitterPackets++ == 0) { // If first packet received, note time, turn head spring return OFF, get start rotation gettimeofday(&_transmitterTimer, NULL); - if (IS_GLASS) { + if (deviceType == DEVICE_GLASS) { setHeadReturnToCenter(true); setHeadSpringScale(10.f); printLog("Using Google Glass to drive head, springs ON.\n"); } else { setHeadReturnToCenter(false); - printLog("Using Transmitter to drive head, springs OFF.\n"); + printLog("Using Transmitter %s to drive head, springs OFF.\n", device); } + //printLog("Packet: [%s]\n", packetData); + //printLog("Version: %s\n", device); + _transmitterInitialReading = glm::vec3( rot3, rot2, rot1 ); @@ -1419,7 +1451,7 @@ void Avatar::processTransmitterData(unsigned char* packetData, int numBytes) { if (eulerAngles.x < -180.f) { eulerAngles.x += 360.f; } glm::vec3 angularVelocity; - if (!IS_GLASS) { + if (deviceType != DEVICE_GLASS) { angularVelocity = glm::vec3(glm::degrees(gyrZ), glm::degrees(-gyrX), glm::degrees(gyrY)); setHeadFromGyros( &eulerAngles, &angularVelocity, (_transmitterHz == 0.f) ? 0.f : 1.f / _transmitterHz, 1.0); @@ -1430,7 +1462,6 @@ void Avatar::processTransmitterData(unsigned char* packetData, int numBytes) { (_transmitterHz == 0.f) ? 0.f : 1.f / _transmitterHz, 1000.0); } - } void Avatar::setHeadFromGyros(glm::vec3* eulerAngles, glm::vec3* angularVelocity, float deltaTime, float smoothingTime) { @@ -1477,8 +1508,13 @@ glm::vec3 Avatar::getGravity(glm::vec3 pos) { // For now, we'll test this with a simple global lookup, but soon we will add getting this // from the domain/voxelserver (or something similar) // - if (glm::length(pos) < 5.f) { - // If near the origin sphere, turn gravity ON + if ((pos.x > 0.f) && + (pos.x < 10.f) && + (pos.z > 0.f) && + (pos.z < 10.f) && + (pos.y > 0.f) && + (pos.y < 3.f)) { + // If above ground plane, turn gravity on return glm::vec3(0.f, -1.f, 0.f); } else { // If flying in space, turn gravity OFF diff --git a/interface/src/Avatar.h b/interface/src/Avatar.h index a0009f66af..10d24a41ae 100644 --- a/interface/src/Avatar.h +++ b/interface/src/Avatar.h @@ -243,6 +243,7 @@ private: AvatarTouch _avatarTouch; bool _displayingHead; // should be false if in first-person view bool _returnHeadToCenter; + float _distanceToNearestAvatar; // How close is the nearest avatar? // private methods... void initializeSkeleton(); diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index ef51f9b72a..138ae7aef4 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -213,42 +213,28 @@ void drawvec3(int x, int y, float scale, float rotate, float thick, int mono, gl } -void drawGroundPlaneGrid( float size, int resolution ) +void drawGroundPlaneGrid(float size) { - glColor3f( 0.4f, 0.5f, 0.3f ); + glColor3f( 0.4f, 0.5f, 0.3f ); glLineWidth(2.0); - float gridSize = 10.0; - int gridResolution = 20; - - for (int g=0; greadBitstreamToTree(voxelData, numBytes - 1); + if (_renderWarningsOn && _tree->getNodesChangedFromBitstream()) { + printLog("readBitstreamToTree()... getNodesChangedFromBitstream=%ld _tree->isDirty()=%s \n", + _tree->getNodesChangedFromBitstream(), (_tree->isDirty() ? "yes" : "no") ); + } + + double end = usecTimestampNow(); + double elapsedmsec = (end - start)/1000.0; + if (_renderWarningsOn && elapsedmsec > 1) { + if (elapsedmsec > 1000) { + double elapsedsec = (end - start)/1000000.0; + printLog("WARNING! readBitstreamToTree() took %lf seconds\n",elapsedsec); + } else { + printLog("WARNING! readBitstreamToTree() took %lf milliseconds\n",elapsedmsec); + } + } + } break; case PACKET_HEADER_ERASE_VOXEL: // ask the tree to read the "remove" bitstream @@ -135,17 +153,48 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) { } void VoxelSystem::setupNewVoxelsForDrawing() { - _voxelsUpdated = newTreeToArrays(_tree->rootNode); + double start = usecTimestampNow(); + + double sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished); + + if (sinceLastTime <= std::max(_setupNewVoxelsForDrawingLastElapsed,SIXTY_FPS_IN_MILLISECONDS)) { + return; // bail early, it hasn't been long enough since the last time we ran + } + + if (_tree->isDirty()) { + _callsToTreesToArrays++; + _voxelsUpdated = newTreeToArrays(_tree->rootNode); + _tree->clearDirtyBit(); // after we pull the trees into the array, we can consider the tree clean + } else { + _voxelsUpdated = 0; + } if (_voxelsUpdated) { _voxelsDirty=true; } - // copy the newly written data to the arrays designated for reading - copyWrittenDataToReadArrays(); + if (_voxelsDirty) { + // copy the newly written data to the arrays designated for reading + copyWrittenDataToReadArrays(); + } + + double end = usecTimestampNow(); + double elapsedmsec = (end - start)/1000.0; + if (_renderWarningsOn && elapsedmsec > 1) { + if (elapsedmsec > 1000) { + double elapsedsec = (end - start)/1000000.0; + printLog("WARNING! newTreeToArrays() took %lf seconds %ld voxels updated\n", elapsedsec, _voxelsUpdated); + } else { + printLog("WARNING! newTreeToArrays() took %lf milliseconds %ld voxels updated\n", elapsedmsec, _voxelsUpdated); + } + } + + _setupNewVoxelsForDrawingLastFinished = end; + _setupNewVoxelsForDrawingLastElapsed = elapsedmsec; } void VoxelSystem::copyWrittenDataToReadArrays() { - if (_voxelsDirty) { + double start = usecTimestampNow(); + if (_voxelsDirty && _voxelsUpdated) { // lock on the buffer write lock so we can't modify the data when the GPU is reading it pthread_mutex_lock(&_bufferWriteLock); int bytesOfVertices = (_voxelsInArrays * VERTEX_POINTS_PER_VOXEL) * sizeof(GLfloat); @@ -154,6 +203,18 @@ void VoxelSystem::copyWrittenDataToReadArrays() { memcpy(_readColorsArray, _writeColorsArray, bytesOfColors ); pthread_mutex_unlock(&_bufferWriteLock); } + double end = usecTimestampNow(); + double elapsedmsec = (end - start)/1000.0; + if (_renderWarningsOn && elapsedmsec > 1) { + if (elapsedmsec > 1000) { + double elapsedsec = (end - start)/1000000.0; + printLog("WARNING! copyWrittenDataToReadArrays() took %lf seconds for %ld voxels %ld updated\n", + elapsedsec, _voxelsInArrays, _voxelsUpdated); + } else { + printLog("WARNING! copyWrittenDataToReadArrays() took %lf milliseconds for %ld voxels %ld updated\n", + elapsedmsec, _voxelsInArrays, _voxelsUpdated); + } + } } int VoxelSystem::newTreeToArrays(VoxelNode* node) { @@ -164,7 +225,7 @@ int VoxelSystem::newTreeToArrays(VoxelNode* node) { float childBoundary = boundaryDistanceForRenderLevel(*node->octalCode + 2); bool inBoundary = (distanceToNode <= boundary); bool inChildBoundary = (distanceToNode <= childBoundary); - bool shouldRender = node->isColored() && ((node->isLeaf() && inChildBoundary) || (inBoundary && !inChildBoundary)); + bool shouldRender = node->isColored() && ((node->isLeaf() && inChildBoundary) || (inBoundary && !inChildBoundary)); node->setShouldRender(shouldRender); // let children figure out their renderness @@ -213,8 +274,8 @@ int VoxelSystem::newTreeToArrays(VoxelNode* node) { _voxelsInArrays++; // our know vertices in the arrays } voxelsUpdated++; - node->clearDirtyBit(); } + node->clearDirtyBit(); // always clear the dirty bit, even if it doesn't need to be rendered return voxelsUpdated; } @@ -225,6 +286,11 @@ VoxelSystem* VoxelSystem::clone() const { void VoxelSystem::init() { + _renderWarningsOn = false; + _callsToTreesToArrays = 0; + _setupNewVoxelsForDrawingLastFinished = 0; + _setupNewVoxelsForDrawingLastElapsed = 0; + // When we change voxels representations in the arrays, we'll update this _voxelsDirty = false; _voxelsInArrays = 0; @@ -296,6 +362,7 @@ void VoxelSystem::init() { } void VoxelSystem::updateVBOs() { + double start = usecTimestampNow(); if (_voxelsDirty) { glBufferIndex segmentStart = 0; glBufferIndex segmentEnd = 0; @@ -327,9 +394,27 @@ void VoxelSystem::updateVBOs() { } _voxelsDirty = false; } + double end = usecTimestampNow(); + double elapsedmsec = (end - start)/1000.0; + if (_renderWarningsOn && elapsedmsec > 1) { + if (elapsedmsec > 1) { + if (elapsedmsec > 1000) { + double elapsedsec = (end - start)/1000000.0; + printLog("WARNING! updateVBOs() took %lf seconds after %d calls to newTreeToArrays()\n", + elapsedsec, _callsToTreesToArrays); + } else { + printLog("WARNING! updateVBOs() took %lf milliseconds after %d calls to newTreeToArrays()\n", + elapsedmsec, _callsToTreesToArrays); + } + } else { + printLog("WARNING! updateVBOs() called after %d calls to newTreeToArrays()\n",_callsToTreesToArrays); + } + } + _callsToTreesToArrays = 0; // clear it } void VoxelSystem::render() { + double start = usecTimestampNow(); glPushMatrix(); updateVBOs(); // tell OpenGL where to find vertex and color information @@ -362,14 +447,31 @@ void VoxelSystem::render() { // scale back down to 1 so heads aren't massive glPopMatrix(); + double end = usecTimestampNow(); + double elapsedmsec = (end - start)/1000.0; + if (_renderWarningsOn && elapsedmsec > 1) { + if (elapsedmsec > 1000) { + double elapsedsec = (end - start)/1000000.0; + printLog("WARNING! render() took %lf seconds\n",elapsedsec); + } else { + printLog("WARNING! render() took %lf milliseconds\n",elapsedmsec); + } + } } int VoxelSystem::_nodeCount = 0; +void VoxelSystem::killLocalVoxels() { + _tree->eraseAllVoxels(); + _voxelsInArrays = 0; // better way to do this?? + //setupNewVoxelsForDrawing(); +} + + bool VoxelSystem::randomColorOperation(VoxelNode* node, void* extraData) { _nodeCount++; if (node->isColored()) { - nodeColor newColor = { randomColorValue(150), randomColorValue(150), randomColorValue(150), 1 }; + nodeColor newColor = { 255, randomColorValue(150), randomColorValue(150), 1 }; node->setColor(newColor); } return true; @@ -385,7 +487,7 @@ void VoxelSystem::randomizeVoxelColors() { bool VoxelSystem::falseColorizeRandomOperation(VoxelNode* node, void* extraData) { _nodeCount++; // always false colorize - node->setFalseColor(randomColorValue(150), randomColorValue(150), randomColorValue(150)); + node->setFalseColor(255, randomColorValue(150), randomColorValue(150)); return true; // keep going! } diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h index e6798f9fd1..d217e8aa55 100644 --- a/interface/src/VoxelSystem.h +++ b/interface/src/VoxelSystem.h @@ -58,7 +58,14 @@ public: void falseColorizeInView(ViewFrustum* viewFrustum); void falseColorizeDistanceFromView(ViewFrustum* viewFrustum); + void killLocalVoxels(); + void setRenderPipelineWarnings(bool on) { _renderWarningsOn = on; }; + bool getRenderPipelineWarnings() const { return _renderWarningsOn; }; + private: + int _callsToTreesToArrays; + + bool _renderWarningsOn; // Operation functions for tree recursion methods static int _nodeCount; static bool randomColorOperation(VoxelNode* node, void* extraData); @@ -83,6 +90,10 @@ private: unsigned long _voxelsUpdated; unsigned long _voxelsInArrays; + + double _setupNewVoxelsForDrawingLastElapsed; + double _setupNewVoxelsForDrawingLastFinished; + GLuint _vboVerticesID; GLuint _vboNormalsID; GLuint _vboColorsID; diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 957b93e453..c602d37823 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -15,7 +15,7 @@ // Welcome Aboard! // // -// Keyboard Commands: +// Keyboard Commands: // // / = toggle stats display // spacebar = reset gyros/head position @@ -130,16 +130,21 @@ glm::vec3 box(WORLD_SIZE,WORLD_SIZE,WORLD_SIZE); VoxelSystem voxels; +bool wantToKillLocalVoxels = false; + + #ifndef _WIN32 Audio audio(&audioScope, &myAvatar); #endif -#define IDLE_SIMULATE_MSECS 8 // How often should call simulate and other stuff +#define IDLE_SIMULATE_MSECS 16 // How often should call simulate and other stuff // in the idle loop? // Where one's own agent begins in the world (needs to become a dynamic thing passed to the program) glm::vec3 start_location(6.1f, 0, 1.4f); +bool renderWarningsOn = false; // Whether to show render pipeline warnings + bool statsOn = false; // Whether to show onscreen text overlay with stats bool starsOn = false; // Whether to display the stars bool paintOn = false; // Whether to paint voxels as you fly around @@ -224,37 +229,30 @@ void displayStats(void) if (::menuOn == 0) { statsVerticalOffset = 8; } - // bitmap chars are about 10 pels high - char legend[] = "/ - toggle this display, Q - exit, H - show head, M - show hand, T - test audio"; - drawtext(10, statsVerticalOffset + 15, 0.10f, 0, 1.0, 0, legend); - char legend2[] = "* - toggle stars, & - toggle paint mode, '-' - send erase all, '%' - send add scene"; - drawtext(10, statsVerticalOffset + 32, 0.10f, 0, 1.0, 0, legend2); - - glm::vec3 avatarPos = myAvatar.getPosition(); - char stats[200]; - sprintf(stats, "FPS = %3.0f Pkts/s = %d Bytes/s = %d Head(x,y,z)= %4.2f, %4.2f, %4.2f ", - FPS, packetsPerSecond, bytesPerSecond, avatarPos.x,avatarPos.y,avatarPos.z); - drawtext(10, statsVerticalOffset + 49, 0.10f, 0, 1.0, 0, stats); + sprintf(stats, "%3.0f FPS, %d Pkts/sec, %3.2f Mbps", + FPS, packetsPerSecond, (float)bytesPerSecond * 8.f / 1000000.f); + drawtext(10, statsVerticalOffset + 15, 0.10f, 0, 1.0, 0, stats); std::stringstream voxelStats; - voxelStats << "Voxels Rendered: " << voxels.getVoxelsRendered() << " Updated: " << voxels.getVoxelsUpdated(); - drawtext(10, statsVerticalOffset + 70, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); + voxelStats.precision(4); + voxelStats << "Voxels Rendered: " << voxels.getVoxelsRendered() / 1000.f << "K Updated: " << voxels.getVoxelsUpdated()/1000.f << "K"; + drawtext(10, statsVerticalOffset + 230, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); voxelStats.str(""); - voxelStats << "Voxels Created: " << voxels.getVoxelsCreated() << " (" << voxels.getVoxelsCreatedPerSecondAverage() - << "/sec) "; + voxelStats << "Voxels Created: " << voxels.getVoxelsCreated() / 1000.f << "K (" << voxels.getVoxelsCreatedPerSecondAverage() / 1000.f + << "Kps) "; drawtext(10, statsVerticalOffset + 250, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); voxelStats.str(""); - voxelStats << "Voxels Colored: " << voxels.getVoxelsColored() << " (" << voxels.getVoxelsColoredPerSecondAverage() - << "/sec) "; + voxelStats << "Voxels Colored: " << voxels.getVoxelsColored() / 1000.f << "K (" << voxels.getVoxelsColoredPerSecondAverage() / 1000.f + << "Kps) "; drawtext(10, statsVerticalOffset + 270, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); voxelStats.str(""); - voxelStats << "Voxels Bytes Read: " << voxels.getVoxelsBytesRead() - << " (" << voxels.getVoxelsBytesReadPerSecondAverage() << " Bps)"; + voxelStats << "Voxel Bits Read: " << voxels.getVoxelsBytesRead() * 8.f / 1000000.f + << "M (" << voxels.getVoxelsBytesReadPerSecondAverage() * 8.f / 1000000.f << " Mbps)"; drawtext(10, statsVerticalOffset + 290,0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); voxelStats.str(""); @@ -262,13 +260,13 @@ void displayStats(void) ? ((float) voxels.getVoxelsBytesRead() / voxels.getVoxelsColored()) : 0; - voxelStats << "Voxels Bytes per Colored: " << voxelsBytesPerColored; + voxelStats << "Voxels Bits per Colored: " << voxelsBytesPerColored * 8; drawtext(10, statsVerticalOffset + 310, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); Agent *avatarMixer = AgentList::getInstance()->soloAgentOfType(AGENT_TYPE_AVATAR_MIXER); char avatarMixerStats[200]; if (avatarMixer) { - sprintf(avatarMixerStats, "Avatar Mixer - %.f kbps, %.f pps", + sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps", roundf(avatarMixer->getAverageKilobitsPerSecond()), roundf(avatarMixer->getAveragePacketsPerSecond())); } else { @@ -701,7 +699,7 @@ void displaySide(Camera& whichCamera) { glPopMatrix(); //draw a grid ground plane.... - drawGroundPlaneGrid( 5.0f, 9 ); + drawGroundPlaneGrid(10.f); // Draw voxels if ( showingVoxels ) @@ -1217,6 +1215,14 @@ int setMenu(int state) { return setValue(state, &::menuOn); } +int setRenderWarnings(int state) { + int value = setValue(state, &::renderWarningsOn); + if (state == MENU_ROW_PICKED) { + ::voxels.setRenderPipelineWarnings(::renderWarningsOn); + } + return value; +} + int setDisplayFrustum(int state) { return setValue(state, &::frustumOn); } @@ -1250,6 +1256,13 @@ int setFrustumRenderMode(int state) { return ::frustumDrawingMode; } +int doKillLocalVoxels(int state) { + if (state == MENU_ROW_PICKED) { + ::wantToKillLocalVoxels = true; + } + return state; +} + int doRandomizeVoxelColors(int state) { if (state == MENU_ROW_PICKED) { ::voxels.randomizeVoxelColors(); @@ -1348,6 +1361,8 @@ void initMenu() { // Debug menuColumnDebug = menu.addColumn("Debug"); + menuColumnDebug->addRow("Show Render Pipeline Warnings", setRenderWarnings); + menuColumnDebug->addRow("Kill Local Voxels", doKillLocalVoxels); menuColumnDebug->addRow("Randomize Voxel TRUE Colors", doRandomizeVoxelColors); menuColumnDebug->addRow("FALSE Color Voxels Randomly", doFalseRandomizeVoxelColors); menuColumnDebug->addRow("FALSE Color Voxels by Distance", doFalseColorizeByDistance); @@ -1593,6 +1608,12 @@ void* networkReceive(void* args) ssize_t bytesReceived; while (!stopNetworkReceiveThread) { + // check to see if the UI thread asked us to kill the voxel tree. since we're the only thread allowed to do that + if (::wantToKillLocalVoxels) { + ::voxels.killLocalVoxels(); + ::wantToKillLocalVoxels = false; + } + if (AgentList::getInstance()->getAgentSocket().receive(&senderAddress, incomingPacket, &bytesReceived)) { packetCount++; bytesCount += bytesReceived; diff --git a/libraries/voxels/src/VoxelConstants.h b/libraries/voxels/src/VoxelConstants.h index 2ba4d05e31..8669ec7130 100644 --- a/libraries/voxels/src/VoxelConstants.h +++ b/libraries/voxels/src/VoxelConstants.h @@ -24,4 +24,5 @@ const int COLOR_VALUES_PER_VOXEL = 3 * VERTICES_PER_VOXEL; typedef unsigned long int glBufferIndex; const glBufferIndex GLBUFFER_INDEX_UNKNOWN = ULONG_MAX; +const double SIXTY_FPS_IN_MILLISECONDS = 1000.0/60; #endif diff --git a/libraries/voxels/src/VoxelNode.cpp b/libraries/voxels/src/VoxelNode.cpp index 6f8f1d71f8..3d0e400782 100644 --- a/libraries/voxels/src/VoxelNode.cpp +++ b/libraries/voxels/src/VoxelNode.cpp @@ -125,7 +125,9 @@ void VoxelNode::setFalseColor(colorPart red, colorPart green, colorPart blue) { _currentColor[1] = green; _currentColor[2] = blue; _currentColor[3] = 1; // XXXBHG - False colors are always considered set - _isDirty = true; + //if (_shouldRender) { + _isDirty = true; + //} } } @@ -136,18 +138,24 @@ void VoxelNode::setFalseColored(bool isFalseColored) { memcpy(&_currentColor,&_trueColor,sizeof(nodeColor)); } _falseColored = isFalseColored; - _isDirty = true; + //if (_shouldRender) { + _isDirty = true; + //} } }; void VoxelNode::setColor(const nodeColor& color) { if (_trueColor[0] != color[0] || _trueColor[1] != color[1] || _trueColor[2] != color[2]) { + //printLog("VoxelNode::setColor() was: (%d,%d,%d) is: (%d,%d,%d)\n", + // _trueColor[0],_trueColor[1],_trueColor[2],color[0],color[1],color[2]); memcpy(&_trueColor,&color,sizeof(nodeColor)); if (!_falseColored) { memcpy(&_currentColor,&color,sizeof(nodeColor)); } - _isDirty = true; + //if (_shouldRender) { + _isDirty = true; + //} } } #endif @@ -234,6 +242,7 @@ bool VoxelNode::isInView(const ViewFrustum& viewFrustum) const { float VoxelNode::distanceToCamera(const ViewFrustum& viewFrustum) const { AABox box; getAABox(box); + box.scale(TREE_SCALE); float distanceToVoxelCenter = sqrtf(powf(viewFrustum.getPosition().x - (box.getCorner().x + box.getSize().x), 2) + powf(viewFrustum.getPosition().y - (box.getCorner().y + box.getSize().y), 2) + powf(viewFrustum.getPosition().z - (box.getCorner().z + box.getSize().z), 2)); diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index 3ff14b5afd..72327c2346 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -35,7 +35,8 @@ VoxelTree::VoxelTree() : voxelsBytesRead(0), voxelsCreatedStats(100), voxelsColoredStats(100), - voxelsBytesReadStats(100) { + voxelsBytesReadStats(100), + _isDirty(true) { rootNode = new VoxelNode(); rootNode->octalCode = new unsigned char[1]; @@ -127,17 +128,29 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, // check the colors mask to see if we have a child to color in if (oneAtBit(*nodeData, i)) { // create the child if it doesn't exist - if (destinationNode->children[i] == NULL) { + if (!destinationNode->children[i]) { destinationNode->addChildAtIndex(i); - this->voxelsCreated++; - this->voxelsCreatedStats.updateAverage(1); + if (destinationNode->isDirty()) { + _isDirty = true; + _nodesChangedFromBitstream++; + } + voxelsCreated++; + voxelsCreatedStats.updateAverage(1); } // pull the color for this child nodeColor newColor; memcpy(newColor, nodeData + bytesRead, 3); newColor[3] = 1; + bool nodeWasDirty = destinationNode->children[i]->isDirty(); destinationNode->children[i]->setColor(newColor); + bool nodeIsDirty = destinationNode->children[i]->isDirty(); + if (nodeIsDirty) { + _isDirty = true; + } + if (!nodeWasDirty && nodeIsDirty) { + _nodesChangedFromBitstream++; + } this->voxelsColored++; this->voxelsColoredStats.updateAverage(1); @@ -145,7 +158,15 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, } } // average node's color based on color of children + bool nodeWasDirty = destinationNode->isDirty(); destinationNode->setColorFromAverageOfChildren(); + bool nodeIsDirty = destinationNode->isDirty(); + if (nodeIsDirty) { + _isDirty = true; + } + if (!nodeWasDirty && nodeIsDirty) { + _nodesChangedFromBitstream++; + } // give this destination node the child mask from the packet unsigned char childMask = *(nodeData + bytesRead); @@ -157,9 +178,17 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, // check the exists mask to see if we have a child to traverse into if (oneAtBit(childMask, childIndex)) { - if (destinationNode->children[childIndex] == NULL) { + if (!destinationNode->children[childIndex]) { // add a child at that index, if it doesn't exist + bool nodeWasDirty = destinationNode->isDirty(); destinationNode->addChildAtIndex(childIndex); + bool nodeIsDirty = destinationNode->isDirty(); + if (nodeIsDirty) { + _isDirty = true; + } + if (!nodeWasDirty && nodeIsDirty) { + _nodesChangedFromBitstream++; + } this->voxelsCreated++; this->voxelsCreatedStats.updateAverage(this->voxelsCreated); } @@ -179,6 +208,8 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, void VoxelTree::readBitstreamToTree(unsigned char * bitstream, int bufferSizeBytes) { int bytesRead = 0; unsigned char* bitstreamAt = bitstream; + + _nodesChangedFromBitstream = 0; // Keep looping through the buffer calling readNodeData() this allows us to pack multiple root-relative Octal codes // into a single network packet. readNodeData() basically goes down a tree from the root, and fills things in from there @@ -193,6 +224,10 @@ void VoxelTree::readBitstreamToTree(unsigned char * bitstream, int bufferSizeByt // Note: we need to create this node relative to root, because we're assuming that the bitstream for the initial // octal code is always relative to root! bitstreamRootNode = createMissingNode(rootNode, (unsigned char*) bitstreamAt); + if (bitstreamRootNode->isDirty()) { + _isDirty = true; + _nodesChangedFromBitstream++; + } } int octalCodeBytes = bytesRequiredForCodeLength(*bitstreamAt); @@ -403,6 +438,12 @@ void VoxelTree::loadVoxelsFile(const char* fileName, bool wantColorRandomizer) { } } +void VoxelTree::createVoxel(float x, float y, float z, float s, unsigned char red, unsigned char green, unsigned char blue) { + unsigned char* voxelData = pointToVoxel(x,y,z,s,red,green,blue); + this->readCodeColorBufferToTree(voxelData); + delete voxelData; +} + void VoxelTree::createSphere(float r,float xc, float yc, float zc, float s, bool solid, bool wantColorRandomizer) { // About the color of the sphere... we're going to make this sphere be a gradient // between two RGB colors. We will do the gradient along the phi spectrum @@ -498,10 +539,10 @@ int VoxelTree::searchForColoredNodesRecursion(int maxSearchLevel, int& currentSe // Keep track of how deep we've searched. currentSearchLevel++; - // If we've reached our max Search Level, then stop searching. - if (currentSearchLevel >= maxSearchLevel) { - return currentSearchLevel; - } + // If we've passed our max Search Level, then stop searching. return last level searched + if (currentSearchLevel > maxSearchLevel) { + return currentSearchLevel-1; + } // If we're at a node that is out of view, then we can return, because no nodes below us will be in view! if (!node->isInView(viewFrustum)) { diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index 7f5a6b866b..2ccb704964 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -46,6 +46,7 @@ public: void reaverageVoxelColors(VoxelNode *startNode); void loadVoxelsFile(const char* fileName, bool wantColorRandomizer); void createSphere(float r,float xc, float yc, float zc, float s, bool solid, bool wantColorRandomizer); + void createVoxel(float x, float y, float z, float s, unsigned char red, unsigned char green, unsigned char blue); void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL); @@ -54,6 +55,10 @@ public: VoxelNodeBag& bag); int searchForColoredNodes(int maxSearchLevel, VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag); + + bool isDirty() const { return _isDirty; }; + void clearDirtyBit() { _isDirty = false; }; + unsigned long int getNodesChangedFromBitstream() const { return _nodesChangedFromBitstream; }; private: int encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel, @@ -68,6 +73,9 @@ private: VoxelNode* nodeForOctalCode(VoxelNode* ancestorNode, unsigned char* needleCode, VoxelNode** parentOfFoundNode); VoxelNode* createMissingNode(VoxelNode* lastParentNode, unsigned char* deepestCodeToCreate); int readNodeData(VoxelNode *destinationNode, unsigned char* nodeData, int bufferSizeBytes); + + bool _isDirty; + unsigned long int _nodesChangedFromBitstream; }; int boundaryDistanceForRenderLevel(unsigned int renderLevel); diff --git a/voxel-server/src/main.cpp b/voxel-server/src/main.cpp index 9ab5675d7e..9f5fc75ccb 100644 --- a/voxel-server/src/main.cpp +++ b/voxel-server/src/main.cpp @@ -38,15 +38,14 @@ const float DEATH_STAR_RADIUS = 4.0; const float MAX_CUBE = 0.05f; const int VOXEL_SEND_INTERVAL_USECS = 100 * 1000; -const int PACKETS_PER_CLIENT_PER_INTERVAL = 20; +int PACKETS_PER_CLIENT_PER_INTERVAL = 20; const int MAX_VOXEL_TREE_DEPTH_LEVELS = 4; VoxelTree randomTree; bool wantColorRandomizer = false; -bool debugViewFrustum = false; -bool viewFrustumCulling = true; // for now +bool debugVoxelSending = false; void addSphere(VoxelTree * tree,bool random, bool wantColorRandomizer) { float r = random ? randFloatInRange(0.05,0.1) : 0.25; @@ -76,43 +75,57 @@ bool countVoxelsOperation(VoxelNode* node, void* extraData) { } void addSphereScene(VoxelTree * tree, bool wantColorRandomizer) { - printf("adding scene of spheres...\n"); - - int sphereBaseSize = 512; - - tree->createSphere(0.25, 0.5, 0.5, 0.5, (1.0 / sphereBaseSize), true, wantColorRandomizer); - printf("one sphere added...\n"); - tree->createSphere(0.030625, 0.5, 0.5, (0.25-0.06125), (1.0 / (sphereBaseSize * 2)), true, true); + printf("adding scene of spheres...\n"); + + int sphereBaseSize = 512; + + tree->createSphere(0.25, 0.5, 0.5, 0.5, (1.0 / sphereBaseSize), true, wantColorRandomizer); + printf("one sphere added...\n"); + tree->createSphere(0.030625, 0.5, 0.5, (0.25-0.06125), (1.0 / (sphereBaseSize * 2)), true, true); - printf("two spheres added...\n"); - tree->createSphere(0.030625, (1.0 - 0.030625), (1.0 - 0.030625), (1.0 - 0.06125), (1.0 / (sphereBaseSize * 2)), true, true); - printf("three spheres added...\n"); - tree->createSphere(0.030625, (1.0 - 0.030625), (1.0 - 0.030625), 0.06125, (1.0 / (sphereBaseSize * 2)), true, true); - printf("four spheres added...\n"); - tree->createSphere(0.030625, (1.0 - 0.030625), 0.06125, (1.0 - 0.06125), (1.0 / (sphereBaseSize * 2)), true, true); - printf("five spheres added...\n"); - tree->createSphere(0.06125, 0.125, 0.125, (1.0 - 0.125), (1.0 / (sphereBaseSize * 2)), true, true); + printf("two spheres added...\n"); + tree->createSphere(0.030625, (0.75 - 0.030625), (0.75 - 0.030625), (0.75 - 0.06125), (1.0 / (sphereBaseSize * 2)), true, true); + printf("three spheres added...\n"); + tree->createSphere(0.030625, (0.75 - 0.030625), (0.75 - 0.030625), 0.06125, (1.0 / (sphereBaseSize * 2)), true, true); + printf("four spheres added...\n"); + tree->createSphere(0.030625, (0.75 - 0.030625), 0.06125, (0.75 - 0.06125), (1.0 / (sphereBaseSize * 2)), true, true); + printf("five spheres added...\n"); + tree->createSphere(0.06125, 0.125, 0.125, (0.75 - 0.125), (1.0 / (sphereBaseSize * 2)), true, true); float radius = 0.0125f; - printf("6 spheres added...\n"); - tree->createSphere(radius, 0.25, radius * 5.0f, 0.25, (1.0 / 4096), true, true); - printf("7 spheres added...\n"); - tree->createSphere(radius, 0.125, radius * 5.0f, 0.25, (1.0 / 4096), true, true); - printf("8 spheres added...\n"); - tree->createSphere(radius, 0.075, radius * 5.0f, 0.25, (1.0 / 4096), true, true); - printf("9 spheres added...\n"); - tree->createSphere(radius, 0.05, radius * 5.0f, 0.25, (1.0 / 4096), true, true); - printf("10 spheres added...\n"); - tree->createSphere(radius, 0.025, radius * 5.0f, 0.25, (1.0 / 4096), true, true); - printf("11 spheres added...\n"); + printf("6 spheres added...\n"); + tree->createSphere(radius, 0.25, radius * 5.0f, 0.25, (1.0 / 4096), true, true); + printf("7 spheres added...\n"); + tree->createSphere(radius, 0.125, radius * 5.0f, 0.25, (1.0 / 4096), true, true); + printf("8 spheres added...\n"); + tree->createSphere(radius, 0.075, radius * 5.0f, 0.25, (1.0 / 4096), true, true); + printf("9 spheres added...\n"); + tree->createSphere(radius, 0.05, radius * 5.0f, 0.25, (1.0 / 4096), true, true); + printf("10 spheres added...\n"); + tree->createSphere(radius, 0.025, radius * 5.0f, 0.25, (1.0 / 4096), true, true); + printf("11 spheres added...\n"); + + float voxelSize = 0.99f/8; + printf("creating corner points...\n"); + tree->createVoxel(0 , 0 , 0 , voxelSize, 255, 255 ,255); + tree->createVoxel(1.0 - voxelSize, 0 , 0 , voxelSize, 255, 0 ,0 ); + tree->createVoxel(0 , 1.0 - voxelSize, 0 , voxelSize, 0 , 255 ,0 ); + tree->createVoxel(0 , 0 , 1.0 - voxelSize, voxelSize, 0 , 0 ,255); + + + tree->createVoxel(1.0 - voxelSize, 0 , 1.0 - voxelSize, voxelSize, 255, 0 ,255); + tree->createVoxel(0 , 1.0 - voxelSize, 1.0 - voxelSize, voxelSize, 0 , 255 ,255); + tree->createVoxel(1.0 - voxelSize, 1.0 - voxelSize, 1.0 - voxelSize, voxelSize, 255, 255 ,255); + tree->createVoxel(1.0 - voxelSize, 1.0 - voxelSize, 0 , voxelSize, 255, 255 ,0 ); + printf("DONE creating corner points...\n"); _nodeCount=0; tree->recurseTreeWithOperation(countVoxelsOperation); printf("Nodes after adding scene %d nodes\n", _nodeCount); - printf("DONE adding scene of spheres...\n"); + printf("DONE adding scene of spheres...\n"); } @@ -167,8 +180,14 @@ void eraseVoxelTreeAndCleanupAgentVisitData() { void voxelDistributor(AgentList* agentList, AgentList::iterator& agent, VoxelAgentData* agentData, ViewFrustum& viewFrustum) { - // If the bag is empty, fill it... - if (agentData->nodeBag.isEmpty()) { + bool searchReset = false; + int searchLoops = 0; + int searchLevelWas = agentData->getMaxSearchLevel(); + double start = usecTimestampNow(); + while (!searchReset && agentData->nodeBag.isEmpty()) { + searchLoops++; + + searchLevelWas = agentData->getMaxSearchLevel(); int maxLevelReached = randomTree.searchForColoredNodes(agentData->getMaxSearchLevel(), randomTree.rootNode, viewFrustum, agentData->nodeBag); agentData->setMaxLevelReached(maxLevelReached); @@ -177,17 +196,38 @@ void voxelDistributor(AgentList* agentList, AgentList::iterator& agent, VoxelAge if (agentData->nodeBag.isEmpty()) { if (agentData->getMaxLevelReached() < agentData->getMaxSearchLevel()) { agentData->resetMaxSearchLevel(); + searchReset = true; } else { agentData->incrementMaxSearchLevel(); } } } + double end = usecTimestampNow(); + double elapsedmsec = (end - start)/1000.0; + if (elapsedmsec > 100) { + if (elapsedmsec > 1000) { + double elapsedsec = (end - start)/1000000.0; + printf("WARNING! searchForColoredNodes() took %lf seconds to identify %d nodes at level %d in %d loops\n", + elapsedsec, agentData->nodeBag.count(), searchLevelWas, searchLoops); + } else { + printf("WARNING! searchForColoredNodes() took %lf milliseconds to identify %d nodes at level %d in %d loops\n", + elapsedmsec, agentData->nodeBag.count(), searchLevelWas, searchLoops); + } + } else if (::debugVoxelSending) { + printf("searchForColoredNodes() took %lf milliseconds to identify %d nodes at level %d in %d loops\n", + elapsedmsec, agentData->nodeBag.count(), searchLevelWas, searchLoops); + } + // If we have something in our nodeBag, then turn them into packets and send them out... if (!agentData->nodeBag.isEmpty()) { static unsigned char tempOutputBuffer[MAX_VOXEL_PACKET_SIZE - 1]; // save on allocs by making this static int bytesWritten = 0; int packetsSentThisInterval = 0; + int truePacketsSent = 0; + int trueBytesSent = 0; + double start = usecTimestampNow(); + while (packetsSentThisInterval < PACKETS_PER_CLIENT_PER_INTERVAL) { if (!agentData->nodeBag.isEmpty()) { VoxelNode* subTree = agentData->nodeBag.extract(); @@ -200,6 +240,8 @@ void voxelDistributor(AgentList* agentList, AgentList::iterator& agent, VoxelAge } else { agentList->getAgentSocket().send(agent->getActiveSocket(), agentData->getPacket(), agentData->getPacketLength()); + trueBytesSent += agentData->getPacketLength(); + truePacketsSent++; packetsSentThisInterval++; agentData->resetVoxelPacket(); agentData->writeToPacket(&tempOutputBuffer[0], bytesWritten); @@ -208,12 +250,30 @@ void voxelDistributor(AgentList* agentList, AgentList::iterator& agent, VoxelAge if (agentData->isPacketWaiting()) { agentList->getAgentSocket().send(agent->getActiveSocket(), agentData->getPacket(), agentData->getPacketLength()); + trueBytesSent += agentData->getPacketLength(); + truePacketsSent++; agentData->resetVoxelPacket(); } packetsSentThisInterval = PACKETS_PER_CLIENT_PER_INTERVAL; // done for now, no nodes left } } + double end = usecTimestampNow(); + double elapsedmsec = (end - start)/1000.0; + if (elapsedmsec > 100) { + if (elapsedmsec > 1000) { + double elapsedsec = (end - start)/1000000.0; + printf("WARNING! packetLoop() took %lf seconds to generate %d bytes in %d packets at level %d, %d nodes still to send\n", + elapsedsec, trueBytesSent, truePacketsSent, searchLevelWas, agentData->nodeBag.count()); + } else { + printf("WARNING! packetLoop() took %lf milliseconds to generate %d bytes in %d packets at level %d, %d nodes still to send\n", + elapsedmsec, trueBytesSent, truePacketsSent, searchLevelWas, agentData->nodeBag.count()); + } + } else if (::debugVoxelSending) { + printf("packetLoop() took %lf milliseconds to generate %d bytes in %d packets at level %d, %d nodes still to send\n", + elapsedmsec, trueBytesSent, truePacketsSent, searchLevelWas, agentData->nodeBag.count()); + } + // if during this last pass, we emptied our bag, then we want to move to the next level. if (agentData->nodeBag.isEmpty()) { if (agentData->getMaxLevelReached() < agentData->getMaxSearchLevel()) { @@ -296,13 +356,9 @@ int main(int argc, const char * argv[]) srand((unsigned)time(0)); - const char* DEBUG_VIEW_FRUSTUM = "--DebugViewFrustum"; - ::debugViewFrustum = cmdOptionExists(argc, argv, DEBUG_VIEW_FRUSTUM); - printf("debugViewFrustum=%s\n", (::debugViewFrustum ? "yes" : "no")); - - const char* NO_VIEW_FRUSTUM_CULLING = "--NoViewFrustumCulling"; - ::viewFrustumCulling = !cmdOptionExists(argc, argv, NO_VIEW_FRUSTUM_CULLING); - printf("viewFrustumCulling=%s\n", (::viewFrustumCulling ? "yes" : "no")); + const char* DEBUG_VOXEL_SENDING = "--debugVoxelSending"; + ::debugVoxelSending = cmdOptionExists(argc, argv, DEBUG_VOXEL_SENDING); + printf("debugVoxelSending=%s\n", (::debugVoxelSending ? "yes" : "no")); const char* WANT_COLOR_RANDOMIZER = "--wantColorRandomizer"; ::wantColorRandomizer = cmdOptionExists(argc, argv, WANT_COLOR_RANDOMIZER); @@ -315,6 +371,17 @@ int main(int argc, const char * argv[]) if (voxelsFilename) { randomTree.loadVoxelsFile(voxelsFilename,wantColorRandomizer); } + + // 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) { + PACKETS_PER_CLIENT_PER_INTERVAL = atoi(packetsPerSecond)/10; + if (PACKETS_PER_CLIENT_PER_INTERVAL < 1) { + PACKETS_PER_CLIENT_PER_INTERVAL = 1; + } + printf("packetsPerSecond=%s PACKETS_PER_CLIENT_PER_INTERVAL=%d\n", packetsPerSecond, PACKETS_PER_CLIENT_PER_INTERVAL); + } const char* ADD_RANDOM_VOXELS = "--AddRandomVoxels"; if (cmdOptionExists(argc, argv, ADD_RANDOM_VOXELS)) { @@ -380,8 +447,6 @@ int main(int argc, const char * argv[]) delete []vertices; randomTree.readCodeColorBufferToTree(pVoxelData); - //printf("readCodeColorBufferToTree() of size=%d atByte=%d receivedBytes=%ld\n", - // voxelDataSize,atByte,receivedBytes); // skip to next pVoxelData+=voxelDataSize; atByte+=voxelDataSize;