Merge branch 'master' of https://github.com/highfidelity/hifi into more_upload_fixes

This commit is contained in:
Atlante45 2014-03-27 10:12:34 -07:00
commit 11f40258b3
37 changed files with 813 additions and 352 deletions

View file

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

View file

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

View file

@ -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<OctreeQueryNode*>(_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()) {

View file

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

View file

@ -6,9 +6,10 @@
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QTimer>
#include <QtCore/QUuid>
#include <QtNetwork/QNetworkAccessManager>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QTimer>
#include <QUuid>
#include <time.h>
#include <HTTPConnection.h>
@ -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 += "<b>Configuration:</b>\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 += "<b>Current Nodes in scene:</b>\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 += "<b>Current Elements in scene:</b>\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 = "<a href='" + detailedStats + "'>"+detailedStats+"</a>";
} 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<OctreeSendThread*, quint64> OctreeServer::_threadsDidProcess;
QMap<OctreeSendThread*, quint64> OctreeServer::_threadsDidPacketDistributor;
QMap<OctreeSendThread*, quint64> OctreeServer::_threadsDidHandlePacketSend;
QMap<OctreeSendThread*, quint64> 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<OctreeSendThread*, quint64>& something, quint64 since) {
if (since == 0) {
return something.size();
}
int count = 0;
QMap<OctreeSendThread*, quint64>::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);
}

View file

@ -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<OctreeSendThread*, quint64> _threadsDidProcess;
static QMap<OctreeSendThread*, quint64> _threadsDidPacketDistributor;
static QMap<OctreeSendThread*, quint64> _threadsDidHandlePacketSend;
static QMap<OctreeSendThread*, quint64> _threadsDidCallWriteDatagram;
};
#endif // __octree_server__OctreeServer__

View file

@ -14,12 +14,12 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="3597"/>
<location filename="src/Application.cpp" line="3608"/>
<source>Open Script</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="3598"/>
<location filename="src/Application.cpp" line="3609"/>
<source>JavaScript Files (*.js)</source>
<translation type="unfinished"></translation>
</message>
@ -113,18 +113,18 @@
<context>
<name>Menu</name>
<message>
<location filename="src/Menu.cpp" line="457"/>
<location filename="src/Menu.cpp" line="459"/>
<source>Open .ini config file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="459"/>
<location filename="src/Menu.cpp" line="471"/>
<location filename="src/Menu.cpp" line="461"/>
<location filename="src/Menu.cpp" line="473"/>
<source>Text files (*.ini)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="469"/>
<location filename="src/Menu.cpp" line="471"/>
<source>Save .ini config file</source>
<translation type="unfinished"></translation>
</message>

View file

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

View file

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

View file

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

View file

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

View file

@ -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<const Shape*>& 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<const Shape*>& 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<Model*>(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;
}

View file

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

View file

@ -9,6 +9,7 @@
#include <NodeList.h>
#include <GeometryUtil.h>
#include <StreamUtils.h>
#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<const Shape*> 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<glm::vec3>& handPositions,
const std::vector<glm::vec3>& 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);
}
}
}

View file

@ -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<HandBall> _leapFingerTipBalls;
std::vector<HandBall> _leapFingerRootBalls;
// private methods
void setLeapHands(const std::vector<glm::vec3>& handPositions,
const std::vector<glm::vec3>& handNormals);
void renderLeapHands(bool isMine);
void renderLeapFingerTrails();
@ -84,3 +82,4 @@ private:
};
#endif

View file

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

View file

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

View file

@ -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<Avatar*>(avatarPointer.data());
if (static_cast<Avatar*>(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 {

View file

@ -63,13 +63,30 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
}
void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& 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;
}
}
}
}
}

View file

@ -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<const Shape*>& shapes) const;
protected:
void applyHandPosition(int jointIndex, const glm::vec3& position);

View file

@ -11,6 +11,7 @@
#include <QThreadPool>
#include <glm/gtx/transform.hpp>
#include <glm/gtx/norm.hpp>
#include <GeometryUtil.h>
@ -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);
}

View file

@ -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<glm::vec3>& vertices, const QVector<glm::vec3>& 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<QVector<QSharedPointer<Texture> > > _dilatedTextures;
QVector<Model*> _attachments;
float _boundingRadius;
static ProgramObject _program;
static ProgramObject _normalMapProgram;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<QPointer<Resource> > ResourceCache::_pendingRequests;
QList<Resource*> 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) {

View file

@ -37,6 +37,10 @@ public:
static void setRequestLimit(int limit) { _requestLimit = limit; }
static int getRequestLimit() { return _requestLimit; }
static const QList<Resource*>& 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>& 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<QPointer<Resource> > _pendingRequests;
static QList<Resource*> _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<Resource>& 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;

View file

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

View file

@ -0,0 +1,75 @@
//
// StreamUtils.cpp
//
// Created by Andrew Meadows on 2014.02.21
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include <glm/gtc/type_ptr.hpp>
#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

View file

@ -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 <iostream>
#include <QByteArray>
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
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__

View file

@ -13,12 +13,17 @@
#include <CollisionInfo.h>
#include <SharedUtil.h>
#include <StreamUtils.h>
#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;

View file

@ -16,11 +16,14 @@
#include <ShapeCollider.h>
#include <SharedUtil.h>
#include <SphereShape.h>
#include <StreamUtils.h>
#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