diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index e0ff29effd..984ce42631 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -76,9 +76,9 @@ void OctreeQueryNode::deleteLater() { } -void OctreeQueryNode::initializeOctreeSendThread(OctreeServer* octreeServer, const QUuid& nodeUUID) { +void OctreeQueryNode::initializeOctreeSendThread(OctreeServer* octreeServer, SharedNodePointer node) { // Create octree sending thread... - _octreeSendThread = new OctreeSendThread(nodeUUID, octreeServer); + _octreeSendThread = new OctreeSendThread(octreeServer, node); _octreeSendThread->initialize(true); } diff --git a/assignment-client/src/octree/OctreeQueryNode.h b/assignment-client/src/octree/OctreeQueryNode.h index b7e68e805d..17d6f6337c 100644 --- a/assignment-client/src/octree/OctreeQueryNode.h +++ b/assignment-client/src/octree/OctreeQueryNode.h @@ -83,7 +83,7 @@ public: OctreeSceneStats stats; - void initializeOctreeSendThread(OctreeServer* octreeServer, const QUuid& nodeUUID); + void initializeOctreeSendThread(OctreeServer* octreeServer, SharedNodePointer node); bool isOctreeSendThreadInitalized() { return _octreeSendThread; } void dumpOutOfView(); diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index a215d9b3c3..382d8aa528 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -19,11 +19,10 @@ quint64 startSceneSleepTime = 0; quint64 endSceneSleepTime = 0; -OctreeSendThread::OctreeSendThread(const QUuid& nodeUUID, OctreeServer* myServer) : - _nodeUUID(nodeUUID), +OctreeSendThread::OctreeSendThread(OctreeServer* myServer, SharedNodePointer node) : _myServer(myServer), + _node(node), _packetData(), - _nodeMissingCount(0), _processLock(), _isShuttingDown(false) { @@ -44,51 +43,43 @@ OctreeSendThread::~OctreeSendThread() { } qDebug() << qPrintable(safeServerName) << "server [" << _myServer << "]: client disconnected " "- ending sending thread [" << this << "]"; - OctreeServer::clientDisconnected(); + _node.clear(); + OctreeServer::clientDisconnected(); } void OctreeSendThread::setIsShuttingDown() { QMutexLocker locker(&_processLock); // this will cause us to wait till the process loop is complete _isShuttingDown = true; + OctreeServer::stopTrackingThread(this); } bool OctreeSendThread::process() { + OctreeServer::didProcess(this); + + float lockWaitElapsedUsec = OctreeServer::SKIP_TIME; + quint64 lockWaitStart = usecTimestampNow(); QMutexLocker locker(&_processLock); + quint64 lockWaitEnd = usecTimestampNow(); + lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart); + OctreeServer::trackProcessWaitTime(lockWaitElapsedUsec); if (_isShuttingDown) { return false; // exit early if we're shutting down } - const int MAX_NODE_MISSING_CHECKS = 10; - if (_nodeMissingCount > MAX_NODE_MISSING_CHECKS) { - qDebug() << "our target node:" << _nodeUUID << "has been missing the last" << _nodeMissingCount - << "times we checked, we are going to stop attempting to send."; - return false; // stop processing and shutdown, our node no longer exists - } - quint64 start = usecTimestampNow(); // don't do any send processing until the initial load of the octree is complete... if (_myServer->isInitialLoadComplete()) { - - // see if we can get access to our node, but don't wait on the lock, if the nodeList is busy - // it might not return a node that is known, but that's ok we can handle that case. - SharedNodePointer node = NodeList::getInstance()->nodeWithUUID(_nodeUUID, false); - - if (node) { - _nodeMissingCount = 0; - OctreeQueryNode* nodeData = NULL; - - nodeData = (OctreeQueryNode*) node->getLinkedData(); + if (!_node.isNull()) { + OctreeQueryNode* nodeData = static_cast(_node->getLinkedData()); // Sometimes the node data has not yet been linked, in which case we can't really do anything if (nodeData && !nodeData->isShuttingDown()) { bool viewFrustumChanged = nodeData->updateCurrentViewFrustum(); - packetDistributor(node, nodeData, viewFrustumChanged); + packetDistributor(_node, nodeData, viewFrustumChanged); } - } else { - _nodeMissingCount++; } } @@ -119,6 +110,8 @@ quint64 OctreeSendThread::_totalPackets = 0; int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) { + + OctreeServer::didHandlePacketSend(this); // if we're shutting down, then exit early if (nodeData->isShuttingDown()) { @@ -175,10 +168,12 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, } // actually send it + OctreeServer::didCallWriteDatagram(this); NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, SharedNodePointer(node)); packetSent = true; } else { // not enough room in the packet, send two packets + OctreeServer::didCallWriteDatagram(this); NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, SharedNodePointer(node)); // since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since @@ -197,6 +192,7 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, truePacketsSent++; packetsSent++; + OctreeServer::didCallWriteDatagram(this); NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), SharedNodePointer(node)); @@ -217,6 +213,7 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, // If there's actually a packet waiting, then send it. if (nodeData->isPacketWaiting() && !nodeData->isShuttingDown()) { // just send the voxel packet + OctreeServer::didCallWriteDatagram(this); NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), SharedNodePointer(node)); packetSent = true; @@ -246,6 +243,7 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, /// Version of voxel distributor that sends the deepest LOD level at once int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQueryNode* nodeData, bool viewFrustumChanged) { + OctreeServer::didPacketDistributor(this); // if shutting down, exit early if (nodeData->isShuttingDown()) { diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index ab88121ee8..4e18ee9b2a 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -21,7 +21,7 @@ class OctreeSendThread : public GenericThread { Q_OBJECT public: - OctreeSendThread(const QUuid& nodeUUID, OctreeServer* myServer); + OctreeSendThread(OctreeServer* myServer, SharedNodePointer node); virtual ~OctreeSendThread(); void setIsShuttingDown(); @@ -38,7 +38,7 @@ protected: virtual bool process(); private: - QUuid _nodeUUID; + SharedNodePointer _node; OctreeServer* _myServer; int handlePacketSend(const SharedNodePointer& node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent); @@ -46,7 +46,6 @@ private: OctreePacketData _packetData; - int _nodeMissingCount; QMutex _processLock; // don't allow us to have our nodeData, or our thread to be deleted while we're processing bool _isShuttingDown; }; diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 6f604c5fd5..fa087cced2 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -6,9 +6,10 @@ // Copyright (c) 2013 HighFidelity, Inc. All rights reserved. // -#include -#include -#include +#include +#include +#include +#include #include #include @@ -59,6 +60,15 @@ int OctreeServer::_noCompress = 0; SimpleMovingAverage OctreeServer::_averagePacketSendingTime(MOVING_AVERAGE_SAMPLE_COUNTS); int OctreeServer::_noSend = 0; +SimpleMovingAverage OctreeServer::_averageProcessWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS); +SimpleMovingAverage OctreeServer::_averageProcessShortWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS); +SimpleMovingAverage OctreeServer::_averageProcessLongWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS); +SimpleMovingAverage OctreeServer::_averageProcessExtraLongWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS); +int OctreeServer::_extraLongProcessWait = 0; +int OctreeServer::_longProcessWait = 0; +int OctreeServer::_shortProcessWait = 0; +int OctreeServer::_noProcessWait = 0; + void OctreeServer::resetSendingStats() { _averageLoopTime.reset(); @@ -95,6 +105,15 @@ void OctreeServer::resetSendingStats() { _averagePacketSendingTime.reset(); _noSend = 0; + + _averageProcessWaitTime.reset(); + _averageProcessShortWaitTime.reset(); + _averageProcessLongWaitTime.reset(); + _averageProcessExtraLongWaitTime.reset(); + _extraLongProcessWait = 0; + _longProcessWait = 0; + _shortProcessWait = 0; + _noProcessWait = 0; } void OctreeServer::trackEncodeTime(float time) { @@ -164,6 +183,24 @@ void OctreeServer::trackPacketSendingTime(float time) { } +void OctreeServer::trackProcessWaitTime(float time) { + const float MAX_SHORT_TIME = 10.0f; + const float MAX_LONG_TIME = 100.0f; + if (time == SKIP_TIME) { + _noProcessWait++; + time = 0.0f; + } else if (time <= MAX_SHORT_TIME) { + _shortProcessWait++; + _averageProcessShortWaitTime.updateAverage(time); + } else if (time <= MAX_LONG_TIME) { + _longProcessWait++; + _averageProcessLongWaitTime.updateAverage(time); + } else { + _extraLongProcessWait++; + _averageProcessExtraLongWaitTime.updateAverage(time); + } + _averageProcessWaitTime.updateAverage(time); +} void OctreeServer::attachQueryNodeToNode(Node* newNode) { if (!newNode->getLinkedData()) { @@ -179,6 +216,7 @@ OctreeServer::OctreeServer(const QByteArray& packet) : _argv(NULL), _parsedArgV(NULL), _httpManager(NULL), + _statusPort(0), _packetsPerClientPerInterval(10), _packetsTotalPerInterval(DEFAULT_PACKETS_PER_INTERVAL), _tree(NULL), @@ -289,41 +327,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url statsString += "\r\n"; - quint64 now = usecTimestampNow(); - const int USECS_PER_MSEC = 1000; - quint64 msecsElapsed = (now - _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)); - - statsString += "Uptime: "; - - if (hours > 0) { - statsString += QString("%1 hour").arg(hours); - if (hours > 1) { - statsString += QString("s"); - } - } - if (minutes > 0) { - if (hours > 0) { - statsString += QString(" "); - } - statsString += QString("%1 minute").arg(minutes); - if (minutes > 1) { - statsString += QString("s"); - } - } - if (seconds > 0) { - if (hours > 0 || minutes > 0) { - statsString += QString(" "); - } - statsString += QString().sprintf("%.3f seconds", seconds); - } + statsString += "Uptime: " + getUptime(); statsString += "\r\n\r\n"; // display voxel file load time @@ -336,33 +340,8 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url statsString += "\r\n"; - quint64 msecsElapsed = 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)); - statsString += QString("%1 File Load Took ").arg(getMyServerName()); - if (hours > 0) { - statsString += QString("%1 hour").arg(hours); - if (hours > 1) { - statsString += QString("s"); - } - } - if (minutes > 0) { - if (hours > 0) { - statsString += QString(" "); - } - statsString += QString("%1 minute").arg(minutes); - if (minutes > 1) { - statsString += QString("s"); - } - } - if (seconds >= 0) { - if (hours > 0 || minutes > 0) { - statsString += QString(" "); - } - statsString += QString().sprintf("%.3f seconds", seconds); - } + statsString += getFileLoadTime(); statsString += "\r\n"; } else { @@ -371,27 +350,32 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url statsString += "\r\n\r\n"; statsString += "Configuration:\r\n"; + statsString += getConfiguration() + "\r\n"; //one to end the config line + statsString += "\r\n"; + + const int COLUMN_WIDTH = 19; + QLocale locale(QLocale::English); + const float AS_PERCENT = 100.0; + + statsString += QString(" Configured Max PPS/Client: %1 pps/client\r\n") + .arg(locale.toString((uint)getPacketsPerClientPerSecond()).rightJustified(COLUMN_WIDTH, ' ')); + statsString += QString(" Configured Max PPS/Server: %1 pps/server\r\n\r\n") + .arg(locale.toString((uint)getPacketsTotalPerSecond()).rightJustified(COLUMN_WIDTH, ' ')); - for (int i = 1; i < _argc; i++) { - statsString += _argv[i]; - } - statsString += "\r\n"; //one to end the config line - statsString += "\r\n\r\n"; // two more for spacing // display scene stats 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; - statsString += "Current Nodes in scene:\r\n"; - statsString += QString(" Total Nodes: %1 nodes\r\n").arg(locale.toString((uint)nodeCount).rightJustified(16, ' ')); - statsString += QString().sprintf(" Internal Nodes: %s nodes (%5.2f%%)\r\n", + statsString += "Current Elements in scene:\r\n"; + statsString += QString(" Total Elements: %1 nodes\r\n") + .arg(locale.toString((uint)nodeCount).rightJustified(16, ' ')); + statsString += QString().sprintf(" Internal Elements: %s nodes (%5.2f%%)\r\n", locale.toString((uint)internalNodeCount).rightJustified(16, ' ').toLocal8Bit().constData(), ((float)internalNodeCount / (float)nodeCount) * AS_PERCENT); - statsString += QString().sprintf(" Leaf Nodes: %s nodes (%5.2f%%)\r\n", + statsString += QString().sprintf(" Leaf Elements: %s nodes (%5.2f%%)\r\n", locale.toString((uint)leafNodeCount).rightJustified(16, ' ').toLocal8Bit().constData(), ((float)leafNodeCount / (float)nodeCount) * AS_PERCENT); statsString += "\r\n"; @@ -408,20 +392,65 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url quint64 totalBytesOfBitMasks = OctreePacketData::getTotalBytesOfBitMasks(); quint64 totalBytesOfColor = OctreePacketData::getTotalBytesOfColor(); - const int COLUMN_WIDTH = 19; - statsString += QString(" Configured Max PPS/Client: %1 pps/client\r\n") - .arg(locale.toString((uint)getPacketsPerClientPerSecond()).rightJustified(COLUMN_WIDTH, ' ')); - statsString += QString(" Configured Max PPS/Server: %1 pps/server\r\n\r\n") - .arg(locale.toString((uint)getPacketsTotalPerSecond()).rightJustified(COLUMN_WIDTH, ' ')); - statsString += QString(" Total Clients Connected: %1 clients\r\n\r\n") + statsString += QString(" Total Clients Connected: %1 clients\r\n") .arg(locale.toString((uint)getCurrentClientCount()).rightJustified(COLUMN_WIDTH, ' ')); + quint64 oneSecondAgo = usecTimestampNow() - USECS_PER_SECOND; + + statsString += QString(" process() last second: %1 clients\r\n") + .arg(locale.toString((uint)howManyThreadsDidProcess(oneSecondAgo)).rightJustified(COLUMN_WIDTH, ' ')); + statsString += QString(" packetDistributor() last second: %1 clients\r\n") + .arg(locale.toString((uint)howManyThreadsDidPacketDistributor(oneSecondAgo)).rightJustified(COLUMN_WIDTH, ' ')); + statsString += QString(" handlePacketSend() last second: %1 clients\r\n") + .arg(locale.toString((uint)howManyThreadsDidHandlePacketSend(oneSecondAgo)).rightJustified(COLUMN_WIDTH, ' ')); + statsString += QString(" writeDatagram() last second: %1 clients\r\n\r\n") + .arg(locale.toString((uint)howManyThreadsDidCallWriteDatagram(oneSecondAgo)).rightJustified(COLUMN_WIDTH, ' ')); + float averageLoopTime = getAverageLoopTime(); - statsString += QString().sprintf(" Average packetLoop() time: %7.2f msecs\r\n", averageLoopTime); + statsString += QString().sprintf(" Average packetLoop() time: %7.2f msecs" + " samples: %12d \r\n", + averageLoopTime, _averageLoopTime.getSampleCount()); float averageInsideTime = getAverageInsideTime(); - statsString += QString().sprintf(" Average 'inside' time: %9.2f usecs\r\n\r\n", averageInsideTime); + statsString += QString().sprintf(" Average 'inside' time: %9.2f usecs" + " samples: %12d \r\n\r\n", + averageInsideTime, _averageInsideTime.getSampleCount()); + + // Process Wait + { + int allWaitTimes = _extraLongProcessWait +_longProcessWait + _shortProcessWait + _noProcessWait; + + float averageProcessWaitTime = getAverageProcessWaitTime(); + statsString += QString().sprintf(" Average process lock wait time:" + " %9.2f usecs samples: %12d \r\n", + averageProcessWaitTime, allWaitTimes); + + float zeroVsTotal = (allWaitTimes > 0) ? ((float)_noProcessWait / (float)allWaitTimes) : 0.0f; + statsString += QString().sprintf(" No Lock Wait:" + " (%6.2f%%) samples: %12d \r\n", + zeroVsTotal * AS_PERCENT, _noProcessWait); + + float shortVsTotal = (allWaitTimes > 0) ? ((float)_shortProcessWait / (float)allWaitTimes) : 0.0f; + statsString += QString().sprintf(" Avg process lock short wait time:" + " %9.2f usecs (%6.2f%%) samples: %12d \r\n", + _averageProcessShortWaitTime.getAverage(), + shortVsTotal * AS_PERCENT, _shortProcessWait); + + float longVsTotal = (allWaitTimes > 0) ? ((float)_longProcessWait / (float)allWaitTimes) : 0.0f; + statsString += QString().sprintf(" Avg process lock long wait time:" + " %9.2f usecs (%6.2f%%) samples: %12d \r\n", + _averageProcessLongWaitTime.getAverage(), + longVsTotal * AS_PERCENT, _longProcessWait); + + float extraLongVsTotal = (allWaitTimes > 0) ? ((float)_extraLongProcessWait / (float)allWaitTimes) : 0.0f; + statsString += QString().sprintf("Avg process lock extralong wait time:" + " %9.2f usecs (%6.2f%%) samples: %12d \r\n\r\n", + _averageProcessExtraLongWaitTime.getAverage(), + extraLongVsTotal * AS_PERCENT, _extraLongProcessWait); + } + + // Tree Wait int allWaitTimes = _extraLongTreeWait +_longTreeWait + _shortTreeWait + _noTreeWait; float averageTreeWaitTime = getAverageTreeWaitTime(); @@ -452,6 +481,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url _averageTreeExtraLongWaitTime.getAverage(), extraLongVsTotal * AS_PERCENT, _extraLongTreeWait); + // encode float averageEncodeTime = getAverageEncodeTime(); statsString += QString().sprintf(" Average encode time: %9.2f usecs\r\n", averageEncodeTime); @@ -482,7 +512,8 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url float averageCompressAndWriteTime = getAverageCompressAndWriteTime(); - statsString += QString().sprintf(" Average compress and write time: %9.2f usecs\r\n", averageCompressAndWriteTime); + statsString += QString().sprintf(" Average compress and write time: %9.2f usecs\r\n", + averageCompressAndWriteTime); int allCompressTimes = _noCompress + _shortCompress + _longCompress + _extraLongCompress; @@ -668,8 +699,8 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url for (int i=0; i <= NUMBER_OF_CHILDREN; i++) { checkSum += OctreeElement::getChildrenCount(i); statsString += QString().sprintf(" Nodes with %d children: %s nodes (%5.2f%%)\r\n", i, - locale.toString((uint)OctreeElement::getChildrenCount(i)).rightJustified(16, ' ').toLocal8Bit().constData(), - ((float)OctreeElement::getChildrenCount(i) / (float)nodeCount) * AS_PERCENT); + locale.toString((uint)OctreeElement::getChildrenCount(i)).rightJustified(16, ' ').toLocal8Bit().constData(), + ((float)OctreeElement::getChildrenCount(i) / (float)nodeCount) * AS_PERCENT); } statsString += " ----------------------\r\n"; statsString += QString(" Total: %1 nodes\r\n") @@ -680,23 +711,23 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url statsString += "OctreeElement Children Encoding Statistics...\r\n"; statsString += QString().sprintf(" Single or No Children: %10.llu nodes (%5.2f%%)\r\n", - OctreeElement::getSingleChildrenCount(), - ((float)OctreeElement::getSingleChildrenCount() / (float)nodeCount) * AS_PERCENT)); + OctreeElement::getSingleChildrenCount(), + ((float)OctreeElement::getSingleChildrenCount() / (float)nodeCount) * AS_PERCENT)); statsString += QString().sprintf(" Two Children as Offset: %10.llu nodes (%5.2f%%)\r\n", - OctreeElement::getTwoChildrenOffsetCount(), - ((float)OctreeElement::getTwoChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT)); + OctreeElement::getTwoChildrenOffsetCount(), + ((float)OctreeElement::getTwoChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT)); statsString += QString().sprintf(" Two Children as External: %10.llu nodes (%5.2f%%)\r\n", - OctreeElement::getTwoChildrenExternalCount(), - ((float)OctreeElement::getTwoChildrenExternalCount() / (float)nodeCount) * AS_PERCENT); + OctreeElement::getTwoChildrenExternalCount(), + ((float)OctreeElement::getTwoChildrenExternalCount() / (float)nodeCount) * AS_PERCENT); statsString += QString().sprintf(" Three Children as Offset: %10.llu nodes (%5.2f%%)\r\n", - OctreeElement::getThreeChildrenOffsetCount(), - ((float)OctreeElement::getThreeChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT); + OctreeElement::getThreeChildrenOffsetCount(), + ((float)OctreeElement::getThreeChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT); statsString += QString().sprintf(" Three Children as External: %10.llu nodes (%5.2f%%)\r\n", - OctreeElement::getThreeChildrenExternalCount(), - ((float)OctreeElement::getThreeChildrenExternalCount() / (float)nodeCount) * AS_PERCENT); + OctreeElement::getThreeChildrenExternalCount(), + ((float)OctreeElement::getThreeChildrenExternalCount() / (float)nodeCount) * AS_PERCENT); statsString += QString().sprintf(" Children as External Array: %10.llu nodes (%5.2f%%)\r\n", - OctreeElement::getExternalChildrenCount(), - ((float)OctreeElement::getExternalChildrenCount() / (float)nodeCount) * AS_PERCENT); + OctreeElement::getExternalChildrenCount(), + ((float)OctreeElement::getExternalChildrenCount() / (float)nodeCount) * AS_PERCENT); checkSum = OctreeElement::getSingleChildrenCount() + OctreeElement::getTwoChildrenOffsetCount() + OctreeElement::getTwoChildrenExternalCount() + @@ -804,7 +835,7 @@ void OctreeServer::readPendingDatagrams() { if (debug) { qDebug() << "calling initializeOctreeSendThread()... node:" << *matchingNode; } - nodeData->initializeOctreeSendThread(this, matchingNode->getUUID()); + nodeData->initializeOctreeSendThread(this, matchingNode); } } } else if (packetType == PacketTypeJurisdictionRequest) { @@ -821,7 +852,9 @@ void OctreeServer::readPendingDatagrams() { void OctreeServer::run() { _safeServerName = getMyServerName(); + // Before we do anything else, create our tree... + OctreeElement::resetPopulationStatistics(); _tree = createTree(); // use common init to setup common timers and logging @@ -839,9 +872,19 @@ void OctreeServer::run() { const char* STATUS_PORT = "--statusPort"; const char* statusPort = getCmdOption(_argc, _argv, STATUS_PORT); if (statusPort) { - int statusPortNumber = atoi(statusPort); - initHTTPManager(statusPortNumber); + _statusPort = atoi(statusPort); + initHTTPManager(_statusPort); } + + const char* STATUS_HOST = "--statusHost"; + const char* statusHost = getCmdOption(_argc, _argv, STATUS_HOST); + if (statusHost) { + qDebug("--statusHost=%s", statusHost); + _statusHost = statusHost; + } else { + _statusHost = QHostAddress(getHostOrderLocalAddress()).toString(); + } + qDebug("statusHost=%s", qPrintable(_statusHost)); const char* JURISDICTION_FILE = "--jurisdictionFile"; @@ -1017,3 +1060,240 @@ void OctreeServer::aboutToFinish() { qDebug() << qPrintable(_safeServerName) << "server ENDING about to finish..."; } +QString OctreeServer::getUptime() { + QString formattedUptime; + quint64 now = usecTimestampNow(); + const int USECS_PER_MSEC = 1000; + quint64 msecsElapsed = (now - _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)); + + if (hours > 0) { + formattedUptime += QString("%1 hour").arg(hours); + if (hours > 1) { + formattedUptime += QString("s"); + } + } + if (minutes > 0) { + if (hours > 0) { + formattedUptime += QString(" "); + } + formattedUptime += QString("%1 minute").arg(minutes); + if (minutes > 1) { + formattedUptime += QString("s"); + } + } + if (seconds > 0) { + if (hours > 0 || minutes > 0) { + formattedUptime += QString(" "); + } + formattedUptime += QString().sprintf("%.3f seconds", seconds); + } + return formattedUptime; +} + +QString OctreeServer::getFileLoadTime() { + QString result; + if (isInitialLoadComplete()) { + + const int USECS_PER_MSEC = 1000; + 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; + + quint64 msecsElapsed = 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)); + + if (hours > 0) { + result += QString("%1 hour").arg(hours); + if (hours > 1) { + result += QString("s"); + } + } + if (minutes > 0) { + if (hours > 0) { + result += QString(" "); + } + result += QString("%1 minute").arg(minutes); + if (minutes > 1) { + result += QString("s"); + } + } + if (seconds >= 0) { + if (hours > 0 || minutes > 0) { + result += QString(" "); + } + result += QString().sprintf("%.3f seconds", seconds); + } + } else { + result = "Not yet loaded..."; + } + return result; +} + +QString OctreeServer::getConfiguration() { + QString result; + for (int i = 1; i < _argc; i++) { + result += _argv[i] + QString(" "); + } + return result; +} + +QString OctreeServer::getStatusLink() { + QString result; + if (_statusPort > 0) { + QString detailedStats= QString("http://") + _statusHost + QString(":%1").arg(_statusPort); + result = ""+detailedStats+""; + } else { + result = "Status port not enabled."; + } + return result; +} + +void OctreeServer::sendStatsPacket() { + + // TODO: we have too many stats to fit in a single MTU... so for now, we break it into multiple JSON objects and + // send them separately. What we really should do is change the NodeList::sendStatsToDomainServer() to handle the + // the following features: + // 1) remember last state sent + // 2) only send new data + // 3) automatically break up into multiple packets + static QJsonObject statsObject1; + + QString baseName = getMyServerName() + QString("Server"); + + statsObject1[baseName + QString(".0.1.configuration")] = getConfiguration(); + + statsObject1[baseName + QString(".0.2.detailed_stats_url")] = getStatusLink(); + + statsObject1[baseName + QString(".0.3.uptime")] = getUptime(); + statsObject1[baseName + QString(".0.4.persistFileLoadTime")] = getFileLoadTime(); + statsObject1[baseName + QString(".0.5.clients")] = getCurrentClientCount(); + + quint64 oneSecondAgo = usecTimestampNow() - USECS_PER_SECOND; + + statsObject1[baseName + QString(".0.6.threads.1.processing")] = (double)howManyThreadsDidProcess(oneSecondAgo); + statsObject1[baseName + QString(".0.6.threads.2.packetDistributor")] = + (double)howManyThreadsDidPacketDistributor(oneSecondAgo); + statsObject1[baseName + QString(".0.6.threads.3.handlePacektSend")] = + (double)howManyThreadsDidHandlePacketSend(oneSecondAgo); + statsObject1[baseName + QString(".0.6.threads.4.writeDatagram")] = + (double)howManyThreadsDidCallWriteDatagram(oneSecondAgo); + + statsObject1[baseName + QString(".1.1.octree.elementCount")] = (double)OctreeElement::getNodeCount(); + statsObject1[baseName + QString(".1.2.octree.internalElementCount")] = (double)OctreeElement::getInternalNodeCount(); + statsObject1[baseName + QString(".1.3.octree.leafElementCount")] = (double)OctreeElement::getLeafNodeCount(); + + ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject1); + + static QJsonObject statsObject2; + + statsObject2[baseName + QString(".2.outbound.data.totalPackets")] = (double)OctreeSendThread::_totalPackets; + statsObject2[baseName + QString(".2.outbound.data.totalBytes")] = (double)OctreeSendThread::_totalBytes; + statsObject2[baseName + QString(".2.outbound.data.totalBytesWasted")] = (double)OctreeSendThread::_totalWastedBytes; + statsObject2[baseName + QString(".2.outbound.data.totalBytesOctalCodes")] = + (double)OctreePacketData::getTotalBytesOfOctalCodes(); + statsObject2[baseName + QString(".2.outbound.data.totalBytesBitMasks")] = + (double)OctreePacketData::getTotalBytesOfBitMasks(); + statsObject2[baseName + QString(".2.outbound.data.totalBytesBitMasks")] = (double)OctreePacketData::getTotalBytesOfColor(); + + statsObject2[baseName + QString(".2.outbound.timing.1.avgLoopTime")] = getAverageLoopTime(); + statsObject2[baseName + QString(".2.outbound.timing.2.avgInsideTime")] = getAverageInsideTime(); + statsObject2[baseName + QString(".2.outbound.timing.3.avgTreeLockTime")] = getAverageTreeWaitTime(); + statsObject2[baseName + QString(".2.outbound.timing.4.avgEncodeTime")] = getAverageEncodeTime(); + statsObject2[baseName + QString(".2.outbound.timing.5.avgCompressAndWriteTime")] = getAverageCompressAndWriteTime(); + statsObject2[baseName + QString(".2.outbound.timing.5.avgSendTime")] = getAveragePacketSendingTime(); + statsObject2[baseName + QString(".2.outbound.timing.5.nodeWaitTime")] = getAverageNodeWaitTime(); + + NodeList::getInstance()->sendStatsToDomainServer(statsObject2); + + static QJsonObject statsObject3; + + statsObject3[baseName + QString(".3.inbound.data.1.totalPackets")] = + (double)_octreeInboundPacketProcessor->getTotalPacketsProcessed(); + statsObject3[baseName + QString(".3.inbound.data.2.totalElements")] = + (double)_octreeInboundPacketProcessor->getTotalElementsProcessed(); + statsObject3[baseName + QString(".3.inbound.timing.1.avgTransitTimePerPacket")] = + (double)_octreeInboundPacketProcessor->getAverageTransitTimePerPacket(); + statsObject3[baseName + QString(".3.inbound.timing.2.avgProcessTimePerPacket")] = + (double)_octreeInboundPacketProcessor->getAverageProcessTimePerPacket(); + statsObject3[baseName + QString(".3.inbound.timing.3.avgLockWaitTimePerPacket")] = + (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerPacket(); + statsObject3[baseName + QString(".3.inbound.timing.4.avgProcessTimePerElement")] = + (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement(); + statsObject3[baseName + QString(".3.inbound.timing.5.avgLockWaitTimePerElement")] = + (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement(); + + NodeList::getInstance()->sendStatsToDomainServer(statsObject3); +} + +QMap OctreeServer::_threadsDidProcess; +QMap OctreeServer::_threadsDidPacketDistributor; +QMap OctreeServer::_threadsDidHandlePacketSend; +QMap OctreeServer::_threadsDidCallWriteDatagram; + + +void OctreeServer::didProcess(OctreeSendThread* thread) { + _threadsDidProcess[thread] = usecTimestampNow(); +} + +void OctreeServer::didPacketDistributor(OctreeSendThread* thread) { + _threadsDidPacketDistributor[thread] = usecTimestampNow(); +} + +void OctreeServer::didHandlePacketSend(OctreeSendThread* thread) { + _threadsDidHandlePacketSend[thread] = usecTimestampNow(); +} + +void OctreeServer::didCallWriteDatagram(OctreeSendThread* thread) { + _threadsDidCallWriteDatagram[thread] = usecTimestampNow(); +} + + +void OctreeServer::stopTrackingThread(OctreeSendThread* thread) { + _threadsDidProcess.remove(thread); + _threadsDidPacketDistributor.remove(thread); + _threadsDidHandlePacketSend.remove(thread); +} + +int howManyThreadsDidSomething(QMap& something, quint64 since) { + if (since == 0) { + return something.size(); + } + int count = 0; + QMap::const_iterator i = something.constBegin(); + while (i != something.constEnd()) { + if (i.value() > since) { + count++; + } + ++i; + } + return count; +} + + +int OctreeServer::howManyThreadsDidProcess(quint64 since) { + return howManyThreadsDidSomething(_threadsDidProcess, since); +} + +int OctreeServer::howManyThreadsDidPacketDistributor(quint64 since) { + return howManyThreadsDidSomething(_threadsDidPacketDistributor, since); +} + +int OctreeServer::howManyThreadsDidHandlePacketSend(quint64 since) { + return howManyThreadsDidSomething(_threadsDidHandlePacketSend, since); +} + +int OctreeServer::howManyThreadsDidCallWriteDatagram(quint64 since) { + return howManyThreadsDidSomething(_threadsDidCallWriteDatagram, since); +} + diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 2664499b6a..3dac42709f 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -97,6 +97,21 @@ public: static void trackPacketSendingTime(float time); static float getAveragePacketSendingTime() { return _averagePacketSendingTime.getAverage(); } + static void trackProcessWaitTime(float time); + static float getAverageProcessWaitTime() { return _averageProcessWaitTime.getAverage(); } + + // these methods allow us to track which threads got to various states + static void didProcess(OctreeSendThread* thread); + static void didPacketDistributor(OctreeSendThread* thread); + static void didHandlePacketSend(OctreeSendThread* thread); + static void didCallWriteDatagram(OctreeSendThread* thread); + static void stopTrackingThread(OctreeSendThread* thread); + + static int howManyThreadsDidProcess(quint64 since = 0); + static int howManyThreadsDidPacketDistributor(quint64 since = 0); + static int howManyThreadsDidHandlePacketSend(quint64 since = 0); + static int howManyThreadsDidCallWriteDatagram(quint64 since = 0); + bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); virtual void aboutToFinish(); @@ -107,17 +122,24 @@ public slots: void readPendingDatagrams(); void nodeAdded(SharedNodePointer node); void nodeKilled(SharedNodePointer node); + void sendStatsPacket(); protected: void parsePayload(); void initHTTPManager(int port); void resetSendingStats(); + QString getUptime(); + QString getFileLoadTime(); + QString getConfiguration(); + QString getStatusLink(); int _argc; const char** _argv; char** _parsedArgV; HTTPManager* _httpManager; + int _statusPort; + QString _statusHost; char _persistFilename[MAX_FILENAME_LENGTH]; int _packetsPerClientPerInterval; @@ -151,6 +173,7 @@ protected: static int _noEncode; static SimpleMovingAverage _averageInsideTime; + static SimpleMovingAverage _averageTreeWaitTime; static SimpleMovingAverage _averageTreeShortWaitTime; static SimpleMovingAverage _averageTreeLongWaitTime; @@ -174,6 +197,20 @@ protected: static SimpleMovingAverage _averagePacketSendingTime; static int _noSend; + static SimpleMovingAverage _averageProcessWaitTime; + static SimpleMovingAverage _averageProcessShortWaitTime; + static SimpleMovingAverage _averageProcessLongWaitTime; + static SimpleMovingAverage _averageProcessExtraLongWaitTime; + static int _extraLongProcessWait; + static int _longProcessWait; + static int _shortProcessWait; + static int _noProcessWait; + + static QMap _threadsDidProcess; + static QMap _threadsDidPacketDistributor; + static QMap _threadsDidHandlePacketSend; + static QMap _threadsDidCallWriteDatagram; + }; #endif // __octree_server__OctreeServer__ diff --git a/interface/interface_en.ts b/interface/interface_en.ts index f5c7f225df..da8827d89d 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -14,12 +14,12 @@ - + Open Script - + JavaScript Files (*.js) @@ -113,18 +113,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5e0d49a94a..8ec2673446 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2676,7 +2676,7 @@ void Application::displayStats() { glm::vec3 avatarPos = _myAvatar->getPosition(); - lines = _statsExpanded ? 4 : 3; + lines = _statsExpanded ? 5 : 3; displayStatsBackground(backgroundColor, horizontalOffset, 0, _glWidget->width() - (mirrorEnabled ? 301 : 411) - horizontalOffset, lines * STATS_PELS_PER_LINE + 10); horizontalOffset += 5; @@ -2713,12 +2713,23 @@ void Application::displayStats() { verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarMixerStats, WHITE_TEXT); + + stringstream downloadStats; + downloadStats << "Downloads: "; + foreach (Resource* resource, ResourceCache::getLoadingRequests()) { + const float MAXIMUM_PERCENTAGE = 100.0f; + downloadStats << roundf(resource->getProgress() * MAXIMUM_PERCENTAGE) << "% "; + } + downloadStats << "(" << ResourceCache::getPendingRequestCount() << " pending)"; + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, downloadStats.str().c_str(), WHITE_TEXT); } verticalOffset = 0; horizontalOffset = _glWidget->width() - (mirrorEnabled ? 300 : 410); - lines = _statsExpanded ? 11 : 3; + lines = _statsExpanded ? 12 : 3; displayStatsBackground(backgroundColor, horizontalOffset, 0, _glWidget->width() - horizontalOffset, lines * STATS_PELS_PER_LINE + 10); horizontalOffset += 5; diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index c2d5f535a7..67f2e2caec 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -143,6 +143,28 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { } } #endif +#ifdef WIN32 + QString deviceName; + if (mode == QAudio::AudioInput) { + WAVEINCAPS wic; + // first use WAVE_MAPPER to get the default devices manufacturer ID + waveInGetDevCaps(WAVE_MAPPER, &wic, sizeof(wic)); + //Use the received manufacturer id to get the device's real name + waveInGetDevCaps(wic.wMid, &wic, sizeof(wic)); + qDebug() << "input device:" << wic.szPname; + deviceName = wic.szPname; + } else { + WAVEOUTCAPS woc; + // first use WAVE_MAPPER to get the default devices manufacturer ID + waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(woc)); + //Use the received manufacturer id to get the device's real name + waveOutGetDevCaps(woc.wMid, &woc, sizeof(woc)); + qDebug() << "output device:" << woc.szPname; + deviceName = woc.szPname; + } + return getNamedAudioDeviceForMode(mode, deviceName); +#endif + // fallback for failed lookup is the default device return (mode == QAudio::AudioInput) ? QAudioDeviceInfo::defaultInputDevice() : QAudioDeviceInfo::defaultOutputDevice(); @@ -500,7 +522,7 @@ void Audio::handleAudioInput() { if (audioMixer && audioMixer->getActiveSocket()) { MyAvatar* interfaceAvatar = Application::getInstance()->getAvatar(); glm::vec3 headPosition = interfaceAvatar->getHead()->getPosition(); - glm::quat headOrientation = interfaceAvatar->getHead()->getOrientation(); + glm::quat headOrientation = interfaceAvatar->getHead()->getTweakedOrientation(); // we need the amount of bytes in the buffer + 1 for type // + 12 for 3 floats for position + float for bearing + 1 attenuation byte @@ -840,7 +862,6 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) // cleanup any previously initialized device if (_audioOutput) { _audioOutput->stop(); - disconnect(_outputDevice, 0, 0, 0); _outputDevice = NULL; delete _audioOutput; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index d9dcb23b77..b3bb358ffa 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -62,7 +62,8 @@ Menu* Menu::getInstance() { const ViewFrustumOffset DEFAULT_FRUSTUM_OFFSET = {-135.0f, 0.0f, 0.0f, 25.0f, 0.0f}; const float DEFAULT_FACESHIFT_EYE_DEFLECTION = 0.25f; const float DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER = 1.0f; -const int FIVE_SECONDS_OF_FRAMES = 5 * 60; +const int ONE_SECOND_OF_FRAMES = 60; +const int FIVE_SECONDS_OF_FRAMES = 5 * ONE_SECOND_OF_FRAMES; Menu::Menu() : _actionHash(), @@ -82,6 +83,7 @@ Menu::Menu() : _lastAdjust(usecTimestampNow()), _lastAvatarDetailDrop(usecTimestampNow()), _fpsAverage(FIVE_SECONDS_OF_FRAMES), + _fastFPSAverage(ONE_SECOND_OF_FRAMES), _loginAction(NULL) { Application *appInstance = Application::getInstance(); @@ -1193,19 +1195,21 @@ void Menu::autoAdjustLOD(float currentFPS) { currentFPS = ASSUMED_FPS; } _fpsAverage.updateAverage(currentFPS); + _fastFPSAverage.updateAverage(currentFPS); quint64 now = usecTimestampNow(); - if (_fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS) { - if (now - _lastAvatarDetailDrop > ADJUST_LOD_DOWN_DELAY) { + const quint64 ADJUST_AVATAR_LOD_DOWN_DELAY = 1000 * 1000; + if (_fastFPSAverage.getAverage() < ADJUST_LOD_DOWN_FPS) { + if (now - _lastAvatarDetailDrop > ADJUST_AVATAR_LOD_DOWN_DELAY) { // attempt to lower the detail in proportion to the fps difference float targetFps = (ADJUST_LOD_DOWN_FPS + ADJUST_LOD_UP_FPS) * 0.5f; - _avatarLODDistanceMultiplier *= (targetFps / _fpsAverage.getAverage()); + _avatarLODDistanceMultiplier *= (targetFps / _fastFPSAverage.getAverage()); _lastAvatarDetailDrop = now; } - } else if (_fpsAverage.getAverage() > ADJUST_LOD_UP_FPS) { + } else if (_fastFPSAverage.getAverage() > ADJUST_LOD_UP_FPS) { // let the detail level creep slowly upwards - const float DISTANCE_DECREASE_RATE = 0.01f; + const float DISTANCE_DECREASE_RATE = 0.02f; const float MINIMUM_DISTANCE_MULTIPLIER = 0.1f; _avatarLODDistanceMultiplier = qMax(MINIMUM_DISTANCE_MULTIPLIER, _avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 25810f0bdd..d337ddc265 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -66,8 +66,6 @@ public: static Menu* getInstance(); ~Menu(); - bool isOptionChecked(const QString& menuOption); - void setIsOptionChecked(const QString& menuOption, bool isChecked); void triggerOption(const QString& menuOption); QAction* getActionForOption(const QString& menuOption); @@ -133,6 +131,8 @@ public slots: void removeSeparator(const QString& menuName, const QString& separatorName); void addMenuItem(const MenuItemProperties& properties); void removeMenuItem(const QString& menuName, const QString& menuitem); + bool isOptionChecked(const QString& menuOption); + void setIsOptionChecked(const QString& menuOption, bool isChecked); private slots: void aboutApp(); @@ -211,6 +211,7 @@ private: quint64 _lastAdjust; quint64 _lastAvatarDetailDrop; SimpleMovingAverage _fpsAverage; + SimpleMovingAverage _fastFPSAverage; QAction* _loginAction; QAction* _chatAction; }; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 7e5a777484..de9b33d9c7 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -499,13 +499,19 @@ bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penet //return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions); } -bool Avatar::findCollisions(const QVector& shapes, CollisionList& collisions) { +void Avatar::updateShapePositions() { _skeletonModel.updateShapePositions(); - bool collided = _skeletonModel.findCollisions(shapes, collisions); - Model& headModel = getHead()->getFaceModel(); headModel.updateShapePositions(); - collided = headModel.findCollisions(shapes, collisions); +} + +bool Avatar::findCollisions(const QVector& shapes, CollisionList& collisions) { + // TODO: Andrew to fix: also collide against _skeleton + //bool collided = _skeletonModel.findCollisions(shapes, collisions); + + Model& headModel = getHead()->getFaceModel(); + //collided = headModel.findCollisions(shapes, collisions) || collided; + bool collided = headModel.findCollisions(shapes, collisions); return collided; } @@ -752,17 +758,30 @@ bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const { return false; } -void Avatar::applyCollision(CollisionInfo& collision) { - if (!collision._data || collision._type != MODEL_COLLISION) { - return; - } - // TODO: make skeleton also respond to collisions - Model* model = static_cast(collision._data); - if (model == &(getHead()->getFaceModel())) { - getHead()->applyCollision(collision); +void Avatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { + // compute lean angles + glm::vec3 leverAxis = contactPoint - getPosition(); + float leverLength = glm::length(leverAxis); + if (leverLength > EPSILON) { + glm::quat bodyRotation = getOrientation(); + glm::vec3 xAxis = bodyRotation * glm::vec3(1.f, 0.f, 0.f); + glm::vec3 zAxis = bodyRotation * glm::vec3(0.f, 0.f, 1.f); + + leverAxis = leverAxis / leverLength; + glm::vec3 effectivePenetration = penetration - glm::dot(penetration, leverAxis) * leverAxis; + // we use the small-angle approximation for sine below to compute the length of + // the opposite side of a narrow right triangle + float sideways = - glm::dot(effectivePenetration, xAxis) / leverLength; + float forward = glm::dot(effectivePenetration, zAxis) / leverLength; + getHead()->addLean(sideways, forward); } } +float Avatar::getBoundingRadius() const { + // TODO: also use head model when computing the avatar's bounding radius + return _skeletonModel.getBoundingRadius(); +} + float Avatar::getPelvisFloatingHeight() const { return -_skeletonModel.getBindExtents().minimum.y; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 25600e0943..685705bfc4 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -145,10 +145,11 @@ public: /// \return true if we expect the avatar would move as a result of the collision bool collisionWouldMoveAvatar(CollisionInfo& collision) const; - /// \param collision a data structure for storing info about collisions against Models - void applyCollision(CollisionInfo& collision); + void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration); - float getBoundingRadius() const { return 0.5f * getSkeletonHeight(); } + /// \return bounding radius of avatar + virtual float getBoundingRadius() const; + void updateShapePositions(); public slots: void updateCollisionFlags(); diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 77586dd7ae..750fae7b2a 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "Application.h" #include "Avatar.h" @@ -109,8 +110,6 @@ void Hand::collideAgainstAvatarOld(Avatar* avatar, bool isMyHand) { for (int j = 0; j < handCollisions.size(); ++j) { CollisionInfo* collision = handCollisions.getCollision(j); if (isMyHand) { - // we resolve the hand from collision when it belongs to MyAvatar AND the other Avatar is - // not expected to respond to the collision (hand hit unmovable part of their Avatar) totalPenetration = addPenetrations(totalPenetration, collision->_penetration); } } @@ -128,55 +127,37 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) { return; } - // 2 = NUM_HANDS - int palmIndices[2]; - getLeftRightPalmIndices(*palmIndices, *(palmIndices + 1)); - const SkeletonModel& skeletonModel = _owningAvatar->getSkeletonModel(); int jointIndices[2]; jointIndices[0] = skeletonModel.getLeftHandJointIndex(); jointIndices[1] = skeletonModel.getRightHandJointIndex(); - palmIndices[1] = -1; // adebug temporarily disable right hand - jointIndices[1] = -1; // adebug temporarily disable right hand - - for (size_t i = 0; i < 1; i++) { - int palmIndex = palmIndices[i]; + for (size_t i = 0; i < 2; i++) { int jointIndex = jointIndices[i]; - if (palmIndex == -1 || jointIndex == -1) { + if (jointIndex < 0) { continue; } - PalmData& palm = _palms[palmIndex]; - if (!palm.isActive()) { - continue; - } - if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) { - playSlaps(palm, avatar); - } handCollisions.clear(); QVector shapes; skeletonModel.getHandShapes(jointIndex, shapes); - bool collided = isMyHand ? avatar->findCollisions(shapes, handCollisions) : avatar->findCollisions(shapes, handCollisions); - if (collided) { - //if (avatar->findCollisions(shapes, handCollisions)) { - glm::vec3 averagePenetration; + + if (avatar->findCollisions(shapes, handCollisions)) { + glm::vec3 totalPenetration(0.f); glm::vec3 averageContactPoint; for (int j = 0; j < handCollisions.size(); ++j) { CollisionInfo* collision = handCollisions.getCollision(j); - averagePenetration += collision->_penetration; + totalPenetration += collision->_penetration; averageContactPoint += collision->_contactPoint; } - averagePenetration /= float(handCollisions.size()); if (isMyHand) { // our hand against other avatar - // for now we resolve it to test shapes/collisions - // TODO: only partially resolve this penetration - palm.addToPosition(-averagePenetration); + // TODO: resolve this penetration when we don't think the other avatar will yield + //palm.addToPenetration(averagePenetration); } else { // someone else's hand against MyAvatar - // TODO: submit collision info to MyAvatar which should lean accordingly averageContactPoint /= float(handCollisions.size()); + avatar->applyCollision(averageContactPoint, totalPenetration); } } } @@ -192,7 +173,7 @@ void Hand::collideAgainstOurself() { float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale(); const Model& skeletonModel = _owningAvatar->getSkeletonModel(); - for (size_t i = 0; i < getNumPalms(); i++) { + for (int i = 0; i < int(getNumPalms()); i++) { PalmData& palm = getPalms()[i]; if (!palm.isActive()) { continue; @@ -210,11 +191,18 @@ void Hand::collideAgainstOurself() { totalPenetration = addPenetrations(totalPenetration, collision->_penetration); } // resolve penetration - palm.addToPosition(-totalPenetration); + palm.addToPenetration(totalPenetration); } } } +void Hand::resolvePenetrations() { + for (size_t i = 0; i < getNumPalms(); ++i) { + PalmData& palm = getPalms()[i]; + palm.resolvePenetrations(); + } +} + void Hand::calculateGeometry() { // generate finger tip balls.... _leapFingerTipBalls.clear(); @@ -385,19 +373,3 @@ void Hand::renderLeapHands(bool isMine) { glPopMatrix(); } - -void Hand::setLeapHands(const std::vector& handPositions, - const std::vector& handNormals) { - for (size_t i = 0; i < getNumPalms(); ++i) { - PalmData& palm = getPalms()[i]; - if (i < handPositions.size()) { - palm.setActive(true); - palm.setRawPosition(handPositions[i]); - palm.setRawNormal(handNormals[i]); - } - else { - palm.setActive(false); - } - } -} - diff --git a/interface/src/avatar/Hand.h b/interface/src/avatar/Hand.h index f6ee5b281f..3be3bc0f77 100755 --- a/interface/src/avatar/Hand.h +++ b/interface/src/avatar/Hand.h @@ -59,6 +59,8 @@ public: void collideAgainstAvatar(Avatar* avatar, bool isMyHand); void collideAgainstOurself(); + void resolvePenetrations(); + private: // disallow copies of the Hand, copy of owning Avatar is disallowed too Hand(const Hand&); @@ -71,10 +73,6 @@ private: std::vector _leapFingerTipBalls; std::vector _leapFingerRootBalls; - // private methods - void setLeapHands(const std::vector& handPositions, - const std::vector& handNormals); - void renderLeapHands(bool isMine); void renderLeapFingerTrails(); @@ -84,3 +82,4 @@ private: }; #endif + diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 4a81df8b74..fc18eed885 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -55,9 +55,6 @@ void Head::reset() { _faceModel.reset(); } - - - void Head::simulate(float deltaTime, bool isMine, bool billboard) { // Update audio trailing average for rendering facial animations @@ -212,34 +209,6 @@ float Head::getTweakedRoll() const { return glm::clamp(_roll + _rollTweak, MIN_HEAD_ROLL, MAX_HEAD_ROLL); } -void Head::applyCollision(CollisionInfo& collision) { - // HACK: the collision proxies for the FaceModel are bad. As a temporary workaround - // we collide against a hard coded collision proxy. - // TODO: get a better collision proxy here. - const float HEAD_RADIUS = 0.15f; - const glm::vec3 HEAD_CENTER = _position; - - // collide the contactPoint against the collision proxy to obtain a new penetration - // NOTE: that penetration is in opposite direction (points the way out for the point, not the sphere) - glm::vec3 penetration; - if (findPointSpherePenetration(collision._contactPoint, HEAD_CENTER, HEAD_RADIUS, penetration)) { - // compute lean angles - Avatar* owningAvatar = static_cast(_owningAvatar); - glm::quat bodyRotation = owningAvatar->getOrientation(); - glm::vec3 neckPosition; - if (owningAvatar->getSkeletonModel().getNeckPosition(neckPosition)) { - glm::vec3 xAxis = bodyRotation * glm::vec3(1.f, 0.f, 0.f); - glm::vec3 zAxis = bodyRotation * glm::vec3(0.f, 0.f, 1.f); - float neckLength = glm::length(_position - neckPosition); - if (neckLength > 0.f) { - float forward = glm::dot(collision._penetration, zAxis) / neckLength; - float sideways = - glm::dot(collision._penetration, xAxis) / neckLength; - addLean(sideways, forward); - } - } - } -} - void Head::renderLookatVectors(glm::vec3 leftEyePosition, glm::vec3 rightEyePosition, glm::vec3 lookatPosition) { Application::getInstance()->getGlowEffect()->begin(); diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 60730c8724..733323abc5 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -82,8 +82,6 @@ public: virtual float getTweakedPitch() const; virtual float getTweakedYaw() const; virtual float getTweakedRoll() const; - - void applyCollision(CollisionInfo& collisionInfo); private: // disallow copies of the Head, copy of owning Avatar is disallowed too diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 19d15fb803..618cda1199 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -177,26 +177,6 @@ void MyAvatar::simulate(float deltaTime) { _velocity += _scale * _gravity * (GRAVITY_EARTH * deltaTime); } - if (_collisionFlags != 0) { - Camera* myCamera = Application::getInstance()->getCamera(); - - float radius = getSkeletonHeight() * COLLISION_RADIUS_SCALE; - if (myCamera->getMode() == CAMERA_MODE_FIRST_PERSON && !OculusManager::isConnected()) { - radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cosf(0.5f * RADIANS_PER_DEGREE * myCamera->getFieldOfView())); - radius *= COLLISION_RADIUS_SCALAR; - } - - if (_collisionFlags & COLLISION_GROUP_ENVIRONMENT) { - updateCollisionWithEnvironment(deltaTime, radius); - } - if (_collisionFlags & COLLISION_GROUP_VOXELS) { - updateCollisionWithVoxels(deltaTime, radius); - } - if (_collisionFlags & COLLISION_GROUP_AVATARS) { - updateCollisionWithAvatars(deltaTime); - } - } - // add thrust to velocity _velocity += _thrust * deltaTime; @@ -320,7 +300,28 @@ void MyAvatar::simulate(float deltaTime) { // Zero thrust out now that we've added it to velocity in this frame _thrust = glm::vec3(0, 0, 0); - + + // now that we're done stepping the avatar forward in time, compute new collisions + if (_collisionFlags != 0) { + Camera* myCamera = Application::getInstance()->getCamera(); + + float radius = getSkeletonHeight() * COLLISION_RADIUS_SCALE; + if (myCamera->getMode() == CAMERA_MODE_FIRST_PERSON && !OculusManager::isConnected()) { + radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.f)); + radius *= COLLISION_RADIUS_SCALAR; + } + + if (_collisionFlags & COLLISION_GROUP_ENVIRONMENT) { + updateCollisionWithEnvironment(deltaTime, radius); + } + if (_collisionFlags & COLLISION_GROUP_VOXELS) { + updateCollisionWithVoxels(deltaTime, radius); + } + if (_collisionFlags & COLLISION_GROUP_AVATARS) { + updateCollisionWithAvatars(deltaTime); + } + } + // consider updating our billboard maybeUpdateBillboard(); } @@ -361,7 +362,7 @@ void MyAvatar::updateFromGyros(float deltaTime) { } } else { // restore rotation, lean to neutral positions - const float RESTORE_PERIOD = 1.f; // seconds + const float RESTORE_PERIOD = 0.25f; // seconds float restorePercentage = glm::clamp(deltaTime/RESTORE_PERIOD, 0.f, 1.f); head->setPitchTweak(glm::mix(head->getPitchTweak(), 0.0f, restorePercentage)); head->setYawTweak(glm::mix(head->getYawTweak(), 0.0f, restorePercentage)); @@ -881,35 +882,31 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { // no need to compute a bunch of stuff if we have one or fewer avatars return; } + updateShapePositions(); float myBoundingRadius = getBoundingRadius(); + /* TODO: Andrew to fix Avatar-Avatar body collisions // HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis // TODO: make the collision work without assuming avatar orientation + Extents myStaticExtents = _skeletonModel.getStaticExtents(); + glm::vec3 staticScale = myStaticExtents.maximum - myStaticExtents.minimum; + float myCapsuleRadius = 0.25f * (staticScale.x + staticScale.z); + float myCapsuleHeight = staticScale.y; + */ - // TODO: these local variables are not used in the live code, only in the - // commented-outTODO code below. - //Extents myStaticExtents = _skeletonModel.getStaticExtents(); - //glm::vec3 staticScale = myStaticExtents.maximum - myStaticExtents.minimum; - //float myCapsuleRadius = 0.25f * (staticScale.x + staticScale.z); - //float myCapsuleHeight = staticScale.y; - - CollisionInfo collisionInfo; foreach (const AvatarSharedPointer& avatarPointer, avatars) { Avatar* avatar = static_cast(avatarPointer.data()); if (static_cast(this) == avatar) { // don't collide with ourselves continue; } + avatar->updateShapePositions(); float distance = glm::length(_position - avatar->getPosition()); if (_distanceToNearestAvatar > distance) { _distanceToNearestAvatar = distance; } float theirBoundingRadius = avatar->getBoundingRadius(); if (distance < myBoundingRadius + theirBoundingRadius) { - _skeletonModel.updateShapePositions(); - Model& headModel = getHead()->getFaceModel(); - headModel.updateShapePositions(); - /* TODO: Andrew to fix Avatar-Avatar body collisions Extents theirStaticExtents = _skeletonModel.getStaticExtents(); glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum; @@ -925,12 +922,16 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { */ // collide our hands against them - getHand()->collideAgainstAvatar(avatar, true); + // TODO: make this work when we can figure out when the other avatar won't yeild + // (for example, we're colling against their chest or leg) + //getHand()->collideAgainstAvatar(avatar, true); // collide their hands against us avatar->getHand()->collideAgainstAvatar(this, false); } } + // TODO: uncomment this when we handle collisions that won't affect other avatar + //getHand()->resolvePenetrations(); } class SortedAvatar { diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 9e4740df15..b4746a39d2 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -63,13 +63,30 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { } void SkeletonModel::getHandShapes(int jointIndex, QVector& shapes) const { - if (jointIndex == -1) { + if (jointIndex < 0 || jointIndex >= int(_shapes.size())) { return; } if (jointIndex == getLeftHandJointIndex() || jointIndex == getRightHandJointIndex()) { - // TODO: also add fingers and other hand-parts - shapes.push_back(_shapes[jointIndex]); + // get all shapes that have this hand as an ancestor in the skeleton heirarchy + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + for (int i = 0; i < _jointStates.size(); i++) { + const FBXJoint& joint = geometry.joints[i]; + if (i == jointIndex) { + // this shape is the hand + shapes.push_back(_shapes[i]); + } else { + int parentIndex = joint.parentIndex; + while (parentIndex != -1) { + if (parentIndex == jointIndex) { + // this shape is a child of the hand + shapes.push_back(_shapes[i]); + break; + } + parentIndex = geometry.joints[parentIndex].parentIndex; + } + } + } } } diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 514b5daf1c..213a53d9ed 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -27,7 +27,7 @@ public: /// \param jointIndex index of hand joint /// \param shapes[out] list in which is stored pointers to hand shapes void getHandShapes(int jointIndex, QVector& shapes) const; - + protected: void applyHandPosition(int jointIndex, const glm::vec3& position); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 690b19ee5e..df5560a4b1 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -11,6 +11,7 @@ #include #include +#include #include @@ -32,7 +33,8 @@ Model::Model(QObject* parent) : _scale(1.0f, 1.0f, 1.0f), _shapesAreDirty(true), _lodDistance(0.0f), - _pupilDilation(0.0f) { + _pupilDilation(0.0f), + _boundingRadius(0.f) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); } @@ -164,6 +166,7 @@ void Model::createCollisionShapes() { void Model::updateShapePositions() { if (_shapesAreDirty && _shapes.size() == _jointStates.size()) { + _boundingRadius = 0.f; float uniformScale = extractUniformScale(_scale); const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointStates.size(); i++) { @@ -173,7 +176,12 @@ void Model::updateShapePositions() { glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation; _shapes[i]->setPosition(worldPosition); _shapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); + float distance2 = glm::distance2(worldPosition, _translation); + if (distance2 > _boundingRadius) { + _boundingRadius = distance2; + } } + _boundingRadius = sqrtf(_boundingRadius); _shapesAreDirty = false; } } @@ -321,18 +329,10 @@ bool Model::getRightHandRotation(glm::quat& rotation) const { return getJointRotation(getRightHandJointIndex(), rotation); } -bool Model::setLeftHandPosition(const glm::vec3& position) { - return setJointPosition(getLeftHandJointIndex(), position); -} - bool Model::restoreLeftHandPosition(float percent) { return restoreJointPosition(getLeftHandJointIndex(), percent); } -bool Model::setLeftHandRotation(const glm::quat& rotation) { - return setJointRotation(getLeftHandJointIndex(), rotation); -} - bool Model::getLeftShoulderPosition(glm::vec3& position) const { return getJointPosition(getLastFreeJointIndex(getLeftHandJointIndex()), position); } @@ -341,18 +341,10 @@ float Model::getLeftArmLength() const { return getLimbLength(getLeftHandJointIndex()); } -bool Model::setRightHandPosition(const glm::vec3& position) { - return setJointPosition(getRightHandJointIndex(), position); -} - bool Model::restoreRightHandPosition(float percent) { return restoreJointPosition(getRightHandJointIndex(), percent); } -bool Model::setRightHandRotation(const glm::quat& rotation) { - return setJointRotation(getRightHandJointIndex(), rotation); -} - bool Model::getRightShoulderPosition(glm::vec3& position) const { return getJointPosition(getLastFreeJointIndex(getRightHandJointIndex()), position); } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index b4f71f14d3..3fdf1cd291 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -135,19 +135,11 @@ public: /// \return true whether or not the rotation was found bool getRightHandRotation(glm::quat& rotation) const; - /// Sets the position of the left hand using inverse kinematics. - /// \return whether or not the left hand joint was found - bool setLeftHandPosition(const glm::vec3& position); - /// Restores some percentage of the default position of the left hand. /// \param percent the percentage of the default position to restore /// \return whether or not the left hand joint was found bool restoreLeftHandPosition(float percent = 1.0f); - /// Sets the rotation of the left hand. - /// \return whether or not the left hand joint was found - bool setLeftHandRotation(const glm::quat& rotation); - /// Gets the position of the left shoulder. /// \return whether or not the left shoulder joint was found bool getLeftShoulderPosition(glm::vec3& position) const; @@ -155,19 +147,11 @@ public: /// Returns the extended length from the left hand to its last free ancestor. float getLeftArmLength() const; - /// Sets the position of the right hand using inverse kinematics. - /// \return whether or not the right hand joint was found - bool setRightHandPosition(const glm::vec3& position); - /// Restores some percentage of the default position of the right hand. /// \param percent the percentage of the default position to restore /// \return whether or not the right hand joint was found bool restoreRightHandPosition(float percent = 1.0f); - /// Sets the rotation of the right hand. - /// \return whether or not the right hand joint was found - bool setRightHandRotation(const glm::quat& rotation); - /// Gets the position of the right shoulder. /// \return whether or not the right shoulder joint was found bool getRightShoulderPosition(glm::vec3& position) const; @@ -195,6 +179,8 @@ public: /// Use the collision to affect the model void applyCollision(CollisionInfo& collision); + float getBoundingRadius() const { return _boundingRadius; } + /// Sets blended vertices computed in a separate thread. void setBlendedVertices(const QVector& vertices, const QVector& normals); @@ -254,7 +240,7 @@ protected: /// Computes and returns the extended length of the limb terminating at the specified joint and starting at the joint's /// first free ancestor. float getLimbLength(int jointIndex) const; - + void applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain = true); private: @@ -280,6 +266,8 @@ private: QVector > > _dilatedTextures; QVector _attachments; + + float _boundingRadius; static ProgramObject _program; static ProgramObject _normalMapProgram; diff --git a/interface/src/scripting/MenuScriptingInterface.cpp b/interface/src/scripting/MenuScriptingInterface.cpp index ccb3fe5eb9..4f9003b288 100644 --- a/interface/src/scripting/MenuScriptingInterface.cpp +++ b/interface/src/scripting/MenuScriptingInterface.cpp @@ -65,9 +65,15 @@ void MenuScriptingInterface::removeMenuItem(const QString& menu, const QString& }; bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) { - return Menu::getInstance()->isOptionChecked(menuOption); + bool result; + QMetaObject::invokeMethod(Menu::getInstance(), "isOptionChecked", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, menuOption)); + return result; } void MenuScriptingInterface::setIsOptionChecked(const QString& menuOption, bool isChecked) { - return Menu::getInstance()->setIsOptionChecked(menuOption, isChecked); + QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked", Qt::BlockingQueuedConnection, + Q_ARG(const QString&, menuOption), + Q_ARG(bool, isChecked)); } diff --git a/libraries/avatars/src/HandData.cpp b/libraries/avatars/src/HandData.cpp index 78ebf4b921..201d17d3f5 100644 --- a/libraries/avatars/src/HandData.cpp +++ b/libraries/avatars/src/HandData.cpp @@ -61,11 +61,12 @@ void HandData::getLeftRightPalmIndices(int& leftPalmIndex, int& rightPalmIndex) } PalmData::PalmData(HandData* owningHandData) : -_rawRotation(0, 0, 0, 1), -_rawPosition(0, 0, 0), -_rawNormal(0, 1, 0), -_rawVelocity(0, 0, 0), -_rotationalVelocity(0, 0, 0), +_rawRotation(0.f, 0.f, 0.f, 1.f), +_rawPosition(0.f), +_rawNormal(0.f, 1.f, 0.f), +_rawVelocity(0.f), +_rotationalVelocity(0.f), +_totalPenetration(0.f), _controllerButtons(0), _isActive(false), _leapID(LEAPID_INVALID), diff --git a/libraries/avatars/src/HandData.h b/libraries/avatars/src/HandData.h index fffc596247..9ebdc8dcf6 100755 --- a/libraries/avatars/src/HandData.h +++ b/libraries/avatars/src/HandData.h @@ -154,6 +154,9 @@ public: void setRawVelocity(const glm::vec3& velocity) { _rawVelocity = velocity; } const glm::vec3& getRawVelocity() const { return _rawVelocity; } void addToPosition(const glm::vec3& delta); + + void addToPenetration(const glm::vec3& penetration) { _totalPenetration += penetration; } + void resolvePenetrations() { addToPosition(-_totalPenetration); _totalPenetration = glm::vec3(0.f); } void setTipPosition(const glm::vec3& position) { _tipPosition = position; } const glm::vec3 getTipPosition() const { return _owningHandData->leapPositionToWorldPosition(_tipPosition); } @@ -203,6 +206,7 @@ private: glm::vec3 _tipPosition; glm::vec3 _tipVelocity; + glm::vec3 _totalPenetration; // accumulator for per-frame penetrations unsigned int _controllerButtons; unsigned int _lastControllerButtons; float _trigger; diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index cdc4f419c0..7697338a28 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -29,6 +29,11 @@ quint64 OctreeElement::_externalChildrenMemoryUsage = 0; quint64 OctreeElement::_voxelNodeCount = 0; quint64 OctreeElement::_voxelNodeLeafCount = 0; +void OctreeElement::resetPopulationStatistics() { + _voxelNodeCount = 0; + _voxelNodeLeafCount = 0; +} + OctreeElement::OctreeElement() { // Note: you must call init() from your subclass, otherwise the OctreeElement will not be properly // initialized. You will see DEADBEEF in your memory debugger if you have not properly called init() diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index ca2fa0b540..a37866bdbe 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -152,6 +152,7 @@ public: static void addUpdateHook(OctreeElementUpdateHook* hook); static void removeUpdateHook(OctreeElementUpdateHook* hook); + static void resetPopulationStatistics(); static unsigned long getNodeCount() { return _voxelNodeCount; } static unsigned long getInternalNodeCount() { return _voxelNodeCount - _voxelNodeLeafCount; } static unsigned long getLeafNodeCount() { return _voxelNodeLeafCount; } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c0834aad9d..1f1eab6baf 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -463,7 +463,6 @@ void ScriptEngine::timerFired() { if (!callingTimer->isActive()) { // this timer is done, we can kill it - qDebug() << "Deleting a single shot timer"; delete callingTimer; } } diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 6eb6eb77d8..dc5a419295 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -357,18 +357,9 @@ int NodeList::findNodeAndUpdateWithDataFromPacket(const QByteArray& packet) { return 0; } -SharedNodePointer NodeList::nodeWithUUID(const QUuid& nodeUUID, bool blockingLock) { - SharedNodePointer node; - // if caller wants us to block and guarantee the correct answer, then honor that request - if (blockingLock) { - // this will block till we can get access - QMutexLocker locker(&_nodeHashMutex); - node = _nodeHash.value(nodeUUID); - } else if (_nodeHashMutex.tryLock()) { // some callers are willing to get wrong answers but not block - node = _nodeHash.value(nodeUUID); - _nodeHashMutex.unlock(); - } - return node; +SharedNodePointer NodeList::nodeWithUUID(const QUuid& nodeUUID) { + QMutexLocker locker(&_nodeHashMutex); + return _nodeHash.value(nodeUUID); } SharedNodePointer NodeList::sendingNodeForPacket(const QByteArray& packet) { diff --git a/libraries/shared/src/NodeList.h b/libraries/shared/src/NodeList.h index 0a59537a46..f10e01f3f4 100644 --- a/libraries/shared/src/NodeList.h +++ b/libraries/shared/src/NodeList.h @@ -103,8 +103,7 @@ public: QByteArray constructPingReplyPacket(const QByteArray& pingPacket); void pingPublicAndLocalSocketsForInactiveNode(const SharedNodePointer& node); - /// passing false for blockingLock, will tryLock, and may return NULL when a node with the UUID actually does exist - SharedNodePointer nodeWithUUID(const QUuid& nodeUUID, bool blockingLock = true); + SharedNodePointer nodeWithUUID(const QUuid& nodeUUID); SharedNodePointer sendingNodeForPacket(const QByteArray& packet); SharedNodePointer addOrUpdateNode(const QUuid& uuid, char nodeType, diff --git a/libraries/shared/src/ResourceCache.cpp b/libraries/shared/src/ResourceCache.cpp index 6cca856b78..c7858e6e20 100644 --- a/libraries/shared/src/ResourceCache.cpp +++ b/libraries/shared/src/ResourceCache.cpp @@ -63,10 +63,12 @@ void ResourceCache::attemptRequest(Resource* resource) { return; } _requestLimit--; + _loadingRequests.append(resource); resource->makeRequest(); } -void ResourceCache::requestCompleted() { +void ResourceCache::requestCompleted(Resource* resource) { + _loadingRequests.removeOne(resource); _requestLimit++; // look for the highest priority pending request @@ -96,6 +98,7 @@ const int DEFAULT_REQUEST_LIMIT = 10; int ResourceCache::_requestLimit = DEFAULT_REQUEST_LIMIT; QList > ResourceCache::_pendingRequests; +QList ResourceCache::_loadingRequests; Resource::Resource(const QUrl& url, bool delayLoad) : _url(url), @@ -121,7 +124,7 @@ Resource::Resource(const QUrl& url, bool delayLoad) : Resource::~Resource() { if (_reply) { - ResourceCache::requestCompleted(); + ResourceCache::requestCompleted(this); delete _reply; } } @@ -215,7 +218,7 @@ void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { _replyTimer->disconnect(this); _replyTimer->deleteLater(); _replyTimer = NULL; - ResourceCache::requestCompleted(); + ResourceCache::requestCompleted(this); downloadFinished(reply); } @@ -250,7 +253,7 @@ void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) _replyTimer->disconnect(this); _replyTimer->deleteLater(); _replyTimer = NULL; - ResourceCache::requestCompleted(); + ResourceCache::requestCompleted(this); // retry for certain types of failures switch (error) { diff --git a/libraries/shared/src/ResourceCache.h b/libraries/shared/src/ResourceCache.h index a544f43731..2fcda0bb98 100644 --- a/libraries/shared/src/ResourceCache.h +++ b/libraries/shared/src/ResourceCache.h @@ -37,6 +37,10 @@ public: static void setRequestLimit(int limit) { _requestLimit = limit; } static int getRequestLimit() { return _requestLimit; } + static const QList& getLoadingRequests() { return _loadingRequests; } + + static int getPendingRequestCount() { return _pendingRequests.size(); } + ResourceCache(QObject* parent = NULL); virtual ~ResourceCache(); @@ -58,7 +62,7 @@ protected: void addUnusedResource(const QSharedPointer& resource); static void attemptRequest(Resource* resource); - static void requestCompleted(); + static void requestCompleted(Resource* resource); private: @@ -70,6 +74,7 @@ private: static QNetworkAccessManager* _networkAccessManager; static int _requestLimit; static QList > _pendingRequests; + static QList _loadingRequests; }; /// Base class for resources. @@ -102,6 +107,15 @@ public: /// Checks whether the resource has loaded. bool isLoaded() const { return _loaded; } + /// For loading resources, returns the number of bytes received. + qint64 getBytesReceived() const { return _bytesReceived; } + + /// For loading resources, returns the number of total bytes (or zero if unknown). + qint64 getBytesTotal() const { return _bytesTotal; } + + /// For loading resources, returns the load progress. + float getProgress() const { return (_bytesTotal == 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; } + void setSelf(const QWeakPointer& self) { _self = self; } void setCache(ResourceCache* cache) { _cache = cache; } @@ -152,6 +166,7 @@ private: int _lruKey; QNetworkReply* _reply; QTimer* _replyTimer; + int _index; qint64 _bytesReceived; qint64 _bytesTotal; int _attempts; diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp index e83e6842a9..70f9b9c4bf 100644 --- a/libraries/shared/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -86,7 +86,8 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col if (absAxialDistance < totalRadius + capsuleB->getHalfHeight()) { glm::vec3 radialAxis = BA + axialDistance * capsuleAxis; // points from A to axis of B float radialDistance2 = glm::length2(radialAxis); - if (radialDistance2 > totalRadius * totalRadius) { + float totalRadius2 = totalRadius * totalRadius; + if (radialDistance2 > totalRadius2) { // sphere is too far from capsule axis return false; } @@ -95,6 +96,9 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col float sign = (axialDistance > 0.f) ? 1.f : -1.f; radialAxis = BA + (sign * capsuleB->getHalfHeight()) * capsuleAxis; radialDistance2 = glm::length2(radialAxis); + if (radialDistance2 > totalRadius2) { + return false; + } } if (radialDistance2 > EPSILON * EPSILON) { CollisionInfo* collision = collisions.getNewCollision(); @@ -147,7 +151,8 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col if (absAxialDistance < totalRadius + capsuleA->getHalfHeight()) { glm::vec3 radialAxis = AB + axialDistance * capsuleAxis; // from sphereB to axis of capsuleA float radialDistance2 = glm::length2(radialAxis); - if (radialDistance2 > totalRadius * totalRadius) { + float totalRadius2 = totalRadius * totalRadius; + if (radialDistance2 > totalRadius2) { // sphere is too far from capsule axis return false; } @@ -162,6 +167,9 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col closestApproach = capsuleA->getPosition() + (sign * capsuleA->getHalfHeight()) * capsuleAxis; radialAxis = closestApproach - sphereB->getPosition(); radialDistance2 = glm::length2(radialAxis); + if (radialDistance2 > totalRadius2) { + return false; + } } if (radialDistance2 > EPSILON * EPSILON) { CollisionInfo* collision = collisions.getNewCollision(); diff --git a/libraries/shared/src/StreamUtils.cpp b/libraries/shared/src/StreamUtils.cpp new file mode 100644 index 0000000000..44c81f9464 --- /dev/null +++ b/libraries/shared/src/StreamUtils.cpp @@ -0,0 +1,75 @@ +// +// StreamUtils.cpp +// +// Created by Andrew Meadows on 2014.02.21 +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#include + +#include "StreamUtils.h" + +const char* hex_digits = "0123456789abcdef"; + +void StreamUtil::dump(std::ostream& s, const QByteArray& buffer) { + int row_size = 32; + int i = 0; + while (i < buffer.size()) { + for(int j = 0; i < buffer.size() && j < row_size; ++j) { + char byte = buffer[i]; + s << hex_digits[(byte >> 4) & 0x0f] << hex_digits[byte & 0x0f] << " "; + ++i; + } + s << "\n"; + } +} + +std::ostream& operator<<(std::ostream& s, const glm::vec3& v) { + s << "<" << v.x << " " << v.y << " " << v.z << ">"; + return s; +} + +std::ostream& operator<<(std::ostream& s, const glm::quat& q) { + s << "<" << q.x << " " << q.y << " " << q.z << " " << q.w << ">"; + return s; +} + +std::ostream& operator<<(std::ostream& s, const glm::mat4& m) { + s << "["; + for (int j = 0; j < 4; ++j) { + s << " " << m[0][j] << " " << m[1][j] << " " << m[2][j] << " " << m[3][j] << ";"; + } + s << " ]"; + return s; +} + +// less common utils can be enabled with DEBUG +#ifdef DEBUG + +std::ostream& operator<<(std::ostream& s, const CollisionInfo& c) { + s << "{penetration=" << c._penetration + << ", contactPoint=" << c._contactPoint + << ", addedVelocity=" << c._addedVelocity + << "}"; + return s; +} + +std::ostream& operator<<(std::ostream& s, const SphereShape& sphere) { + s << "{type='sphere', center=" << sphere.getPosition() + << ", radius=" << sphere.getRadius() + << "}"; + return s; +} + +std::ostream& operator<<(std::ostream& s, const CapsuleShape& capsule) { + s << "{type='capsule', center=" << capsule.getPosition() + << ", radius=" << capsule.getRadius() + << ", length=" << (2.f * capsule.getHalfHeight()) + << ", begin=" << capsule.getStartPoint() + << ", end=" << capsule.getEndPoint() + << "}"; + return s; +} + +#endif // DEBUG + diff --git a/libraries/shared/src/StreamUtils.h b/libraries/shared/src/StreamUtils.h new file mode 100644 index 0000000000..1a17a94658 --- /dev/null +++ b/libraries/shared/src/StreamUtils.h @@ -0,0 +1,39 @@ +// +// StreamUtils.h +// +// Created by Andrew Meadows on 2014.02.21 +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#ifndef __tests__StreamUtils__ +#define __tests__StreamUtils__ + +#include + +#include + +#include +#include + + +namespace StreamUtil { + // dump the buffer, 32 bytes per row, each byte in hex, separated by whitespace + void dump(std::ostream& s, const QByteArray& buffer); +} + +std::ostream& operator<<(std::ostream& s, const glm::vec3& v); +std::ostream& operator<<(std::ostream& s, const glm::quat& q); +std::ostream& operator<<(std::ostream& s, const glm::mat4& m); + +// less common utils can be enabled with DEBUG +#ifdef DEBUG +#include "CollisionInfo.h" +#include "SphereShape.h" +#include "CapsuleShape.h" +std::ostream& operator<<(std::ostream& s, const CollisionInfo& c); +std::ostream& operator<<(std::ostream& s, const SphereShape& shape); +std::ostream& operator<<(std::ostream& s, const CapsuleShape& capsule); +#endif // DEBUG + + +#endif // __tests__StreamUtils__ diff --git a/tests/physics/src/CollisionInfoTests.cpp b/tests/physics/src/CollisionInfoTests.cpp index 43601978ae..241204d036 100644 --- a/tests/physics/src/CollisionInfoTests.cpp +++ b/tests/physics/src/CollisionInfoTests.cpp @@ -13,12 +13,17 @@ #include #include +#include #include "CollisionInfoTests.h" -#include "PhysicsTestUtil.h" /* + +static glm::vec3 xAxis(1.f, 0.f, 0.f); +static glm::vec3 xZxis(0.f, 1.f, 0.f); +static glm::vec3 xYxis(0.f, 0.f, 1.f); + void CollisionInfoTests::rotateThenTranslate() { CollisionInfo collision; collision._penetration = xAxis; diff --git a/tests/physics/src/ShapeColliderTests.cpp b/tests/physics/src/ShapeColliderTests.cpp index 6d7e9a6db1..4a2e648323 100644 --- a/tests/physics/src/ShapeColliderTests.cpp +++ b/tests/physics/src/ShapeColliderTests.cpp @@ -16,11 +16,14 @@ #include #include #include +#include -#include "PhysicsTestUtil.h" #include "ShapeColliderTests.h" const glm::vec3 origin(0.f); +static const glm::vec3 xAxis(1.f, 0.f, 0.f); +static const glm::vec3 yAxis(0.f, 1.f, 0.f); +static const glm::vec3 zAxis(0.f, 0.f, 1.f); void ShapeColliderTests::sphereMissesSphere() { // non-overlapping spheres of unequal size