diff --git a/BUILD.md b/BUILD.md index e033916e08..8f871c3cea 100644 --- a/BUILD.md +++ b/BUILD.md @@ -38,7 +38,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation: - cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.2.0/clang_64/lib/cmake + cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.2.0/lib/cmake UNIX diff --git a/CMakeLists.txt b/CMakeLists.txt index 4674df40de..cb1e4224cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,11 @@ elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") endif(WIN32) -set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} $ENV{QT_CMAKE_PREFIX_PATH}) +if (NOT QT_CMAKE_PREFIX_PATH) + set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) +endif () + +set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT_CMAKE_PREFIX_PATH}) # set our Base SDK to 10.8 set(CMAKE_OSX_SYSROOT /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk) diff --git a/README.md b/README.md index 1c374901d2..f8a6725ed8 100644 --- a/README.md +++ b/README.md @@ -63,4 +63,5 @@ To test things out you'll want to run the Interface client. To access your local domain in Interface, open your Preferences -- on OS X this is available in the Interface menu, on Linux you'll find it in the File menu. Enter "localhost" in the "Domain server" field. -If everything worked you should see that you are connected to at least one server. Nice work! +If everything worked you should see that you are connected to at least one server. +Nice work! diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 4eb6b17260..b23f9d210a 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -120,10 +120,12 @@ void Agent::readPendingDatagrams() { } } +const QString AGENT_LOGGING_NAME = "agent"; + void Agent::run() { - NodeList* nodeList = NodeList::getInstance(); - nodeList->setOwnerType(NodeType::Agent); + ThreadedAssignment::commonInit(AGENT_LOGGING_NAME, NodeType::Agent); + NodeList* nodeList = NodeList::getInstance(); nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer); // figure out the URL for the script for this agent assignment @@ -148,17 +150,6 @@ void Agent::run() { qDebug() << "Downloaded script:" << scriptContents; - timeval startTime; - gettimeofday(&startTime, NULL); - - QTimer* domainServerTimer = new QTimer(this); - connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); - domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000); - - QTimer* silentNodeTimer = new QTimer(this); - connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); - // setup an Avatar for the script to use AvatarData scriptedAvatar; @@ -189,4 +180,9 @@ void Agent::run() { _scriptEngine.setScriptContents(scriptContents); _scriptEngine.run(); + setFinished(true); +} + +void Agent::aboutToFinish() { + _scriptEngine.stop(); } diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index b638c39356..0a61bd73f7 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -41,6 +41,8 @@ public: bool isListeningToAudioStream() const { return _scriptEngine.isListeningToAudioStream(); } void setIsListeningToAudioStream(bool isListeningToAudioStream) { _scriptEngine.setIsListeningToAudioStream(isListeningToAudioStream); } + + virtual void aboutToFinish(); public slots: void run(); diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index f5bed48a86..450b6e0ad9 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -53,6 +54,8 @@ const short JITTER_BUFFER_MSECS = 12; const short JITTER_BUFFER_SAMPLES = JITTER_BUFFER_MSECS * (SAMPLE_RATE / 1000.0); +const float LOUDNESS_TO_DISTANCE_RATIO = 0.00305f; + const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer"; void attachNewBufferToNode(Node *newNode) { @@ -64,10 +67,11 @@ void attachNewBufferToNode(Node *newNode) { AudioMixer::AudioMixer(const QByteArray& packet) : ThreadedAssignment(packet), _trailingSleepRatio(1.0f), - _minSourceLoudnessInFrame(1.0f), - _maxSourceLoudnessInFrame(0.0f), - _loudnessCutoffRatio(0.0f), - _minRequiredLoudness(0.0f) + _minAudibilityThreshold(LOUDNESS_TO_DISTANCE_RATIO / 2.0f), + _performanceThrottlingRatio(0.0f), + _numStatFrames(0), + _sumListeners(0), + _sumMixes(0) { } @@ -81,10 +85,24 @@ void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuf if (bufferToAdd != listeningNodeBuffer) { // if the two buffer pointers do not match then these are different buffers - glm::vec3 relativePosition = bufferToAdd->getPosition() - listeningNodeBuffer->getPosition(); + + float distanceBetween = glm::length(relativePosition); + + if (distanceBetween < EPSILON) { + distanceBetween = EPSILON; + } + + if (bufferToAdd->getAverageLoudness() / distanceBetween <= _minAudibilityThreshold) { + // according to mixer performance we have decided this does not get to be mixed in + // bail out + return; + } + + ++_sumMixes; + glm::quat inverseOrientation = glm::inverse(listeningNodeBuffer->getOrientation()); - + float distanceSquareToSource = glm::dot(relativePosition, relativePosition); float radius = 0.0f; @@ -306,7 +324,7 @@ void AudioMixer::prepareMixForListeningNode(Node* node) { if ((*otherNode != *node || otherNodeBuffer->shouldLoopbackForNode()) && otherNodeBuffer->willBeAddedToMix() - && otherNodeBuffer->getAverageLoudness() > _minRequiredLoudness) { + && otherNodeBuffer->getAverageLoudness() > 0) { addBufferToMixForListeningNodeWithBuffer(otherNodeBuffer, nodeRingBuffer); } } @@ -338,9 +356,29 @@ void AudioMixer::readPendingDatagrams() { } } +void AudioMixer::sendStatsPacket() { + static QJsonObject statsObject; + statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100.0f; + statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio; + + statsObject["average_listeners_per_frame"] = (float) _sumListeners / (float) _numStatFrames; + + if (_sumListeners > 0) { + statsObject["average_mixes_per_listener"] = (float) _sumMixes / (float) _sumListeners; + } else { + statsObject["average_mixes_per_listener"] = 0.0; + } + + ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject); + + _sumListeners = 0; + _sumMixes = 0; + _numStatFrames = 0; +} + void AudioMixer::run() { - commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer); + ThreadedAssignment::commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer); NodeList* nodeList = NodeList::getInstance(); @@ -357,20 +395,66 @@ void AudioMixer::run() { + numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)]; int usecToSleep = BUFFER_SEND_INTERVAL_USECS; + + const int TRAILING_AVERAGE_FRAMES = 100; + int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES; while (!_isFinished) { - - _minSourceLoudnessInFrame = 1.0f; - _maxSourceLoudnessInFrame = 0.0f; foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getLinkedData()) { - ((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(JITTER_BUFFER_SAMPLES, - _minSourceLoudnessInFrame, - _maxSourceLoudnessInFrame); + ((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(JITTER_BUFFER_SAMPLES); } } - + + const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f; + const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f; + + const float RATIO_BACK_OFF = 0.02f; + + const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; + const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; + + if (usecToSleep < 0) { + usecToSleep = 0; + } + + _trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) + + (usecToSleep * CURRENT_FRAME_RATIO / (float) BUFFER_SEND_INTERVAL_USECS); + + float lastCutoffRatio = _performanceThrottlingRatio; + bool hasRatioChanged = false; + + if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) { + if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) { + // we're struggling - change our min required loudness to reduce some load + _performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio)); + + qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" + << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; + hasRatioChanged = true; + } else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) { + // we've recovered and can back off the required loudness + _performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF; + + if (_performanceThrottlingRatio < 0) { + _performanceThrottlingRatio = 0; + } + + qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" + << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; + hasRatioChanged = true; + } + + if (hasRatioChanged) { + // set out min audability threshold from the new ratio + _minAudibilityThreshold = LOUDNESS_TO_DISTANCE_RATIO / (2.0f * (1.0f - _performanceThrottlingRatio)); + qDebug() << "Minimum audability required to be mixed is now" << _minAudibilityThreshold; + + framesSinceCutoffEvent = 0; + } + } + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() && ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) { @@ -380,6 +464,8 @@ void AudioMixer::run() { memcpy(clientMixBuffer + numBytesPacketHeader, _clientSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO); nodeList->writeDatagram(clientMixBuffer, NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesPacketHeader, node); + + ++_sumListeners; } } @@ -390,6 +476,8 @@ void AudioMixer::run() { } } + ++_numStatFrames; + QCoreApplication::processEvents(); if (_isFinished) { @@ -400,10 +488,7 @@ void AudioMixer::run() { if (usecToSleep > 0) { usleep(usecToSleep); - } else { - qDebug() << "AudioMixer loop took" << -usecToSleep << "of extra time. Not sleeping."; } - } delete[] clientMixBuffer; diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 4ba8cdebd3..0ca241c5ed 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -28,6 +28,8 @@ public slots: void run(); void readPendingDatagrams(); + + void sendStatsPacket(); private: /// adds one buffer to the mix for a listening node void addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuffer* bufferToAdd, @@ -41,10 +43,11 @@ private: int16_t _clientSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)]; float _trailingSleepRatio; - float _minSourceLoudnessInFrame; - float _maxSourceLoudnessInFrame; - float _loudnessCutoffRatio; - float _minRequiredLoudness; + float _minAudibilityThreshold; + float _performanceThrottlingRatio; + int _numStatFrames; + int _sumListeners; + int _sumMixes; }; #endif /* defined(__hifi__AudioMixer__) */ diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index f370a1509f..381c80cb09 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -83,20 +83,16 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { return 0; } -void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples, - float& currentMinLoudness, - float& currentMaxLoudness) { +void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples) { for (unsigned int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->shouldBeAddedToMix(jitterBufferLengthSamples)) { // this is a ring buffer that is ready to go // set its flag so we know to push its buffer when all is said and done _ringBuffers[i]->setWillBeAddedToMix(true); - // calculate the average loudness for the next NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL // that would be mixed in _ringBuffers[i]->updateAverageLoudnessForBoundarySamples(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); - } } } diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index d41563bbca..7f44390ec5 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -25,7 +25,7 @@ public: AvatarAudioRingBuffer* getAvatarAudioRingBuffer() const; int parseData(const QByteArray& packet); - void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples, float& currentMinLoudness, float& currentMaxLoudness); + void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples); void pushBuffersAfterFrameSend(); private: std::vector _ringBuffers; diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 47f7084f64..0ec7c3e15e 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -29,7 +30,11 @@ const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; const unsigned int AVATAR_DATA_SEND_INTERVAL_USECS = (1 / 60.0) * 1000 * 1000; AvatarMixer::AvatarMixer(const QByteArray& packet) : - ThreadedAssignment(packet) + ThreadedAssignment(packet), + _trailingSleepRatio(1.0f), + _performanceThrottlingRatio(0.0f), + _sumListeners(0), + _numStatFrames(0) { // make sure we hear about node kills so we can tell the other nodes connect(NodeList::getInstance(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled); @@ -48,7 +53,7 @@ void attachAvatarDataToNode(Node* newNode) { // 3) if we need to rate limit the amount of data we send, we can use a distance weighted "semi-random" function to // determine which avatars are included in the packet stream // 4) we should optimize the avatar data format to be more compact (100 bytes is pretty wasteful). -void broadcastAvatarData() { +void AvatarMixer::broadcastAvatarData() { static QByteArray mixedAvatarByteArray; int numPacketHeaderBytes = populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData); @@ -57,6 +62,7 @@ void broadcastAvatarData() { foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getLinkedData() && node->getType() == NodeType::Agent && node->getActiveSocket()) { + ++_sumListeners; // reset packet pointers for this node mixedAvatarByteArray.resize(numPacketHeaderBytes); @@ -78,7 +84,8 @@ void broadcastAvatarData() { // at a distance of twice the full rate distance, there will be a 50% chance of sending this avatar's update const float FULL_RATE_DISTANCE = 2.f; // Decide whether to send this avatar's data based on it's distance from us - if ((distanceToAvatar == 0.f) || (randFloat() < FULL_RATE_DISTANCE / distanceToAvatar)) { + if ((distanceToAvatar == 0.f) || (randFloat() < FULL_RATE_DISTANCE / distanceToAvatar) + * (1 - _performanceThrottlingRatio)) { QByteArray avatarByteArray; avatarByteArray.append(otherNode->getUUID().toRfc4122()); avatarByteArray.append(otherAvatar.toByteArray()); @@ -241,11 +248,24 @@ void AvatarMixer::readPendingDatagrams() { } } +void AvatarMixer::sendStatsPacket() { + QJsonObject statsObject; + statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; + + statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100; + statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio; + + ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject); + + _sumListeners = 0; + _numStatFrames = 0; +} + const qint64 AVATAR_IDENTITY_KEYFRAME_MSECS = 5000; const qint64 AVATAR_BILLBOARD_KEYFRAME_MSECS = 5000; void AvatarMixer::run() { - commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer); + ThreadedAssignment::commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer); NodeList* nodeList = NodeList::getInstance(); nodeList->addNodeTypeToInterestSet(NodeType::Agent); @@ -263,12 +283,57 @@ void AvatarMixer::run() { QElapsedTimer billboardTimer; billboardTimer.start(); + int usecToSleep = AVATAR_DATA_SEND_INTERVAL_USECS; + + const int TRAILING_AVERAGE_FRAMES = 100; + int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES; + while (!_isFinished) { - QCoreApplication::processEvents(); + ++_numStatFrames; - if (_isFinished) { - break; + const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f; + const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f; + + const float RATIO_BACK_OFF = 0.02f; + + const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; + const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; + + if (usecToSleep < 0) { + usecToSleep = 0; + } + + _trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) + + (usecToSleep * CURRENT_FRAME_RATIO / (float) AVATAR_DATA_SEND_INTERVAL_USECS); + + float lastCutoffRatio = _performanceThrottlingRatio; + bool hasRatioChanged = false; + + if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) { + if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) { + // we're struggling - change our min required loudness to reduce some load + _performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio)); + + qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" + << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; + hasRatioChanged = true; + } else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) { + // we've recovered and can back off the required loudness + _performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF; + + if (_performanceThrottlingRatio < 0) { + _performanceThrottlingRatio = 0; + } + + qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" + << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; + hasRatioChanged = true; + } + + if (hasRatioChanged) { + framesSinceCutoffEvent = 0; + } } broadcastAvatarData(); @@ -286,7 +351,13 @@ void AvatarMixer::run() { billboardTimer.restart(); } - int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * AVATAR_DATA_SEND_INTERVAL_USECS) - usecTimestampNow(); + QCoreApplication::processEvents(); + + if (_isFinished) { + break; + } + + usecToSleep = usecTimestamp(&startTime) + (++nextFrame * AVATAR_DATA_SEND_INTERVAL_USECS) - usecTimestampNow(); if (usecToSleep > 0) { usleep(usecToSleep); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index acc5a178aa..4d54b715f8 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -24,6 +24,17 @@ public slots: void nodeKilled(SharedNodePointer killedNode); void readPendingDatagrams(); + + void sendStatsPacket(); + +private: + void broadcastAvatarData(); + + float _trailingSleepRatio; + float _performanceThrottlingRatio; + + int _sumListeners; + int _numStatFrames; }; #endif /* defined(__hifi__AvatarMixer__) */ diff --git a/assignment-client/src/main.cpp b/assignment-client/src/main.cpp index 7ee0ea9e14..0151f93319 100644 --- a/assignment-client/src/main.cpp +++ b/assignment-client/src/main.cpp @@ -15,8 +15,10 @@ int main(int argc, char* argv[]) { +#ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); - +#endif + // use the verbose message handler in Logging qInstallMessageHandler(Logging::verboseMessageHandler); diff --git a/assignment-client/src/metavoxels/MetavoxelServer.h b/assignment-client/src/metavoxels/MetavoxelServer.h index bd7a280c43..d178127ac7 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.h +++ b/assignment-client/src/metavoxels/MetavoxelServer.h @@ -35,7 +35,7 @@ public: virtual void run(); virtual void readPendingDatagrams(); - + private slots: void maybeAttachSession(const SharedNodePointer& node); diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index 2ceb9e1040..e0ff29effd 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -67,6 +67,14 @@ OctreeQueryNode::~OctreeQueryNode() { } +void OctreeQueryNode::deleteLater() { + _isShuttingDown = true; + if (_octreeSendThread) { + _octreeSendThread->setIsShuttingDown(); + } + OctreeQuery::deleteLater(); +} + void OctreeQueryNode::initializeOctreeSendThread(OctreeServer* octreeServer, const QUuid& nodeUUID) { // Create octree sending thread... diff --git a/assignment-client/src/octree/OctreeQueryNode.h b/assignment-client/src/octree/OctreeQueryNode.h index eab8cb5d0a..b7e68e805d 100644 --- a/assignment-client/src/octree/OctreeQueryNode.h +++ b/assignment-client/src/octree/OctreeQueryNode.h @@ -27,6 +27,7 @@ class OctreeQueryNode : public OctreeQuery { public: OctreeQueryNode(); virtual ~OctreeQueryNode(); + virtual void deleteLater(); void init(); // called after creation to set up some virtual items virtual PacketType getMyPacketType() const = 0; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 9c04c4a1ad..a215d9b3c3 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -5,6 +5,8 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // +#include + #include #include #include @@ -21,7 +23,9 @@ OctreeSendThread::OctreeSendThread(const QUuid& nodeUUID, OctreeServer* myServer _nodeUUID(nodeUUID), _myServer(myServer), _packetData(), - _nodeMissingCount(0) + _nodeMissingCount(0), + _processLock(), + _isShuttingDown(false) { QString safeServerName("Octree"); if (_myServer) { @@ -43,8 +47,19 @@ OctreeSendThread::~OctreeSendThread() { OctreeServer::clientDisconnected(); } +void OctreeSendThread::setIsShuttingDown() { + QMutexLocker locker(&_processLock); // this will cause us to wait till the process loop is complete + _isShuttingDown = true; +} + bool OctreeSendThread::process() { + QMutexLocker locker(&_processLock); + + 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 @@ -56,7 +71,10 @@ bool OctreeSendThread::process() { // don't do any send processing until the initial load of the octree is complete... if (_myServer->isInitialLoadComplete()) { - SharedNodePointer node = NodeList::getInstance()->nodeWithUUID(_nodeUUID); + + // 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; @@ -113,19 +131,6 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, bool packetSent = false; // did we send a packet? int packetsSent = 0; - // double check that the node has an active socket, otherwise, don't send... - - quint64 lockWaitStart = usecTimestampNow(); - QMutexLocker locker(&node->getMutex()); - quint64 lockWaitEnd = usecTimestampNow(); - float lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart); - OctreeServer::trackNodeWaitTime(lockWaitElapsedUsec); - - const HifiSockAddr* nodeAddress = node->getActiveSocket(); - if (!nodeAddress) { - return packetsSent; // without sending... - } - // Here's where we check to see if this packet is a duplicate of the last packet. If it is, we will silently // obscure the packet and not send it. This allows the callers and upper level logic to not need to know about // this rate control savings. diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index 39c27911b0..ab88121ee8 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -23,6 +23,8 @@ class OctreeSendThread : public GenericThread { public: OctreeSendThread(const QUuid& nodeUUID, OctreeServer* myServer); virtual ~OctreeSendThread(); + + void setIsShuttingDown(); static quint64 _totalBytes; static quint64 _totalWastedBytes; @@ -45,6 +47,8 @@ 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; }; #endif // __octree_server__OctreeSendThread__ diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 496f9af1a0..6f604c5fd5 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -236,7 +236,7 @@ void OctreeServer::initHTTPManager(int port) { _httpManager = new HTTPManager(port, documentRoot, this, this); } -bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString& path) { +bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url) { #ifdef FORCE_CRASH if (connection->requestOperation() == QNetworkAccessManager::GetOperation @@ -259,9 +259,9 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString& bool showStats = false; if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { - if (path == "/") { + if (url.path() == "/") { showStats = true; - } else if (path == "/resetStats") { + } else if (url.path() == "/resetStats") { _octreeInboundPacketProcessor->resetStats(); resetSendingStats(); showStats = true; @@ -823,9 +823,9 @@ void OctreeServer::run() { _safeServerName = getMyServerName(); // Before we do anything else, create our tree... _tree = createTree(); - - // change the logging target name while this is running - Logging::setTargetName(getMyLoggingServerTargetName()); + + // use common init to setup common timers and logging + commonInit(getMyLoggingServerTargetName(), getMyNodeType()); // Now would be a good time to parse our arguments, if we got them as assignment if (getPayload().size() > 0) { @@ -880,7 +880,9 @@ void OctreeServer::run() { // we need to ask the DS about agents so we can ping/reply with them nodeList->addNodeTypeToInterestSet(NodeType::Agent); +#ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); +#endif nodeList->linkedDataCreateCallback = &OctreeServer::attachQueryNodeToNode; @@ -986,14 +988,6 @@ void OctreeServer::run() { strftime(utcBuffer, MAX_TIME_LENGTH, " [%m/%d/%Y %X UTC]", gmtm); } qDebug() << "Now running... started at: " << localBuffer << utcBuffer; - - QTimer* domainServerTimer = new QTimer(this); - connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); - domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000); - - QTimer* silentNodeTimer = new QTimer(this); - connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); } void OctreeServer::nodeAdded(SharedNodePointer node) { diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 12091170d9..2664499b6a 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -97,7 +97,7 @@ public: static void trackPacketSendingTime(float time); static float getAveragePacketSendingTime() { return _averagePacketSendingTime.getAverage(); } - bool handleHTTPRequest(HTTPConnection* connection, const QString& path); + bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); virtual void aboutToFinish(); diff --git a/assignment-client/src/voxels/VoxelServer.h b/assignment-client/src/voxels/VoxelServer.h index 509d838fff..2e97736963 100644 --- a/assignment-client/src/voxels/VoxelServer.h +++ b/assignment-client/src/voxels/VoxelServer.h @@ -46,7 +46,6 @@ public: virtual bool hasSpecialPacketToSend(const SharedNodePointer& node); virtual int sendSpecialPacket(const SharedNodePointer& node); - private: bool _sendEnvironments; bool _sendMinimalEnvironment; diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index d641aa5f71..fb295cffc3 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -38,4 +38,8 @@ span.port { color: #666666; +} + +.stale { + color: red; } \ No newline at end of file diff --git a/domain-server/resources/web/footer.html b/domain-server/resources/web/footer.html index 08ea9fba66..d1a3fc29e8 100644 --- a/domain-server/resources/web/footer.html +++ b/domain-server/resources/web/footer.html @@ -1,3 +1,3 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/domain-server/resources/web/header.html b/domain-server/resources/web/header.html index 83d7ae5c23..2be603b00e 100644 --- a/domain-server/resources/web/header.html +++ b/domain-server/resources/web/header.html @@ -4,8 +4,8 @@ domain-server - - + +
\ No newline at end of file diff --git a/domain-server/resources/web/js/tables.js b/domain-server/resources/web/js/tables.js index d0855d7967..ae5095592b 100644 --- a/domain-server/resources/web/js/tables.js +++ b/domain-server/resources/web/js/tables.js @@ -7,7 +7,7 @@ $(document).ready(function(){ $.each(json.nodes, function (uuid, data) { nodesTableBody += ""; nodesTableBody += "" + data.type + ""; - nodesTableBody += "" + uuid + ""; + nodesTableBody += "" + uuid + ""; nodesTableBody += "" + (data.pool ? data.pool : "") + ""; nodesTableBody += "" + data.public.ip + ":" + data.public.port + ""; nodesTableBody += "" + data.local.ip + ":" + data.local.port + ""; @@ -42,7 +42,7 @@ $(document).ready(function(){ $(document.body).on('click', '.glyphicon-remove', function(){ // fire off a delete for this node $.ajax({ - url: "/node/" + $(this).data('uuid'), + url: "/nodes/" + $(this).data('uuid'), type: 'DELETE', success: function(result) { console.log("Succesful request to delete node."); diff --git a/domain-server/resources/web/stats/index.shtml b/domain-server/resources/web/stats/index.shtml new file mode 100644 index 0000000000..62115d18fe --- /dev/null +++ b/domain-server/resources/web/stats/index.shtml @@ -0,0 +1,6 @@ + +

Stats

+
+ + + \ No newline at end of file diff --git a/domain-server/resources/web/stats/js/stats.js b/domain-server/resources/web/stats/js/stats.js new file mode 100644 index 0000000000..a7b0aecfcf --- /dev/null +++ b/domain-server/resources/web/stats/js/stats.js @@ -0,0 +1,41 @@ +function qs(key) { + key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars + var match = location.search.match(new RegExp("[?&]"+key+"=([^&]+)(&|$)")); + return match && decodeURIComponent(match[1].replace(/\+/g, " ")); +} + +$(document).ready(function(){ + // setup a function to grab the nodeStats + function getNodeStats() { + + var uuid = qs("uuid"); + + var statsTableBody = ""; + + $.getJSON("/nodes/" + uuid + ".json", function(json){ + + // update the table header with the right node type + $('#stats-lead h3').html(json.node_type + " stats (" + uuid + ")"); + + delete json.node_type; + + $.each(json, function(key, value) { + statsTableBody += ""; + statsTableBody += "" + key + ""; + statsTableBody += "" + value + ""; + statsTableBody += ""; + }); + + $('#stats-table tbody').html(statsTableBody); + }).fail(function(data) { + $('#stats-table td').each(function(){ + $(this).addClass('stale'); + }); + }); + } + + // do the first GET on page load + getNodeStats(); + // grab the new assignments JSON every second + var getNodeStatsInterval = setInterval(getNodeStats, 1000); +}); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 216d249858..913ca44e12 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -603,6 +603,11 @@ void DomainServer::readAvailableDatagrams() { if (noisyMessage) { lastNoisyMessage = timeNow; } + } else if (requestType == PacketTypeNodeJsonStats) { + SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); + if (matchingNode) { + reinterpret_cast(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket); + } } } } @@ -646,14 +651,14 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { return nodeJson; } -bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString& path) { +bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url) { const QString JSON_MIME_TYPE = "application/json"; const QString URI_ASSIGNMENT = "/assignment"; - const QString URI_NODE = "/node"; + const QString URI_NODES = "/nodes"; if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { - if (path == "/assignments.json") { + if (url.path() == "/assignments.json") { // user is asking for json list of assignments // setup the JSON @@ -697,7 +702,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString& // we've processed this request return true; - } else if (path == "/nodes.json") { + } else if (url.path() == QString("%1.json").arg(URI_NODES)) { // setup the JSON QJsonObject rootJSON; QJsonObject nodesJSON; @@ -718,14 +723,41 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString& // send the response connection->respond(HTTPConnection::StatusCode200, nodesDocument.toJson(), qPrintable(JSON_MIME_TYPE)); + + return true; + } else { + const QString NODE_REGEX_STRING = + QString("\\%1\\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}).json\\/?$").arg(URI_NODES); + QRegExp nodeShowRegex(NODE_REGEX_STRING); + + if (nodeShowRegex.indexIn(url.path()) != -1) { + QUuid matchingUUID = QUuid(nodeShowRegex.cap(1)); + + // see if we have a node that matches this ID + SharedNodePointer matchingNode = NodeList::getInstance()->nodeWithUUID(matchingUUID); + if (matchingNode) { + // create a QJsonDocument with the stats QJsonObject + QJsonObject statsObject = + reinterpret_cast(matchingNode->getLinkedData())->getStatsJSONObject(); + + // add the node type to the JSON data for output purposes + statsObject["node_type"] = NodeType::getNodeTypeName(matchingNode->getType()).toLower().replace(' ', '-'); + + QJsonDocument statsDocument(statsObject); + + // send the response + connection->respond(HTTPConnection::StatusCode200, statsDocument.toJson(), qPrintable(JSON_MIME_TYPE)); + + // tell the caller we processed the request + return true; + } + } } } else if (connection->requestOperation() == QNetworkAccessManager::PostOperation) { - if (path == URI_ASSIGNMENT) { + if (url.path() == URI_ASSIGNMENT) { // this is a script upload - ask the HTTPConnection to parse the form data QList formData = connection->parseFormData(); - - // check how many instances of this assignment the user wants by checking the ASSIGNMENT-INSTANCES header const QString ASSIGNMENT_INSTANCES_HEADER = "ASSIGNMENT-INSTANCES"; @@ -765,13 +797,15 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString& // respond with a 200 code for successful upload connection->respond(HTTPConnection::StatusCode200); + + return true; } } else if (connection->requestOperation() == QNetworkAccessManager::DeleteOperation) { - if (path.startsWith(URI_NODE)) { + if (url.path().startsWith(URI_NODES)) { // this is a request to DELETE a node by UUID // pull the UUID from the url - QUuid deleteUUID = QUuid(path.mid(URI_NODE.size() + sizeof('/'))); + QUuid deleteUUID = QUuid(url.path().mid(URI_NODES.size() + sizeof('/'))); if (!deleteUUID.isNull()) { SharedNodePointer nodeToKill = NodeList::getInstance()->nodeWithUUID(deleteUUID); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 2d253cc41c..597be7f50d 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -30,7 +30,7 @@ public: bool requiresAuthentication() const { return !_nodeAuthenticationURL.isEmpty(); } - bool handleHTTPRequest(HTTPConnection* connection, const QString& path); + bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); void exit(int retCode = 0); diff --git a/domain-server/src/DomainServerNodeData.cpp b/domain-server/src/DomainServerNodeData.cpp index 2e32903712..f1e08e3bc4 100644 --- a/domain-server/src/DomainServerNodeData.cpp +++ b/domain-server/src/DomainServerNodeData.cpp @@ -6,11 +6,43 @@ // Copyright (c) 2014 HighFidelity, Inc. All rights reserved. // +#include +#include +#include + +#include + #include "DomainServerNodeData.h" DomainServerNodeData::DomainServerNodeData() : _sessionSecretHash(), - _staticAssignmentUUID() + _staticAssignmentUUID(), + _statsJSONObject() { +} + +void DomainServerNodeData::parseJSONStatsPacket(const QByteArray& statsPacket) { + // push past the packet header + QDataStream packetStream(statsPacket); + packetStream.skipRawData(numBytesForPacketHeader(statsPacket)); + + QVariantMap unpackedVariantMap; + + packetStream >> unpackedVariantMap; + + QJsonObject unpackedStatsJSON = QJsonObject::fromVariantMap(unpackedVariantMap); + _statsJSONObject = mergeJSONStatsFromNewObject(unpackedStatsJSON, _statsJSONObject); +} + +QJsonObject DomainServerNodeData::mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject) { + foreach(const QString& key, newObject.keys()) { + if (newObject[key].isObject() && destinationObject.contains(key)) { + destinationObject[key] = mergeJSONStatsFromNewObject(newObject[key].toObject(), destinationObject[key].toObject()); + } else { + destinationObject[key] = newObject[key]; + } + } + + return destinationObject; } \ No newline at end of file diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index 6686b9120f..20531839f4 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -19,13 +19,20 @@ public: DomainServerNodeData(); int parseData(const QByteArray& packet) { return 0; } + const QJsonObject& getStatsJSONObject() const { return _statsJSONObject; } + + void parseJSONStatsPacket(const QByteArray& statsPacket); + void setStaticAssignmentUUID(const QUuid& staticAssignmentUUID) { _staticAssignmentUUID = staticAssignmentUUID; } const QUuid& getStaticAssignmentUUID() const { return _staticAssignmentUUID; } QHash& getSessionSecretHash() { return _sessionSecretHash; } private: + QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject); + QHash _sessionSecretHash; QUuid _staticAssignmentUUID; + QJsonObject _statsJSONObject; }; #endif /* defined(__hifi__DomainServerNodeData__) */ diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index 1d9f554237..970d1dad70 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -20,10 +20,11 @@ int main(int argc, char* argv[]) { +#ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); - +#endif + qInstallMessageHandler(Logging::verboseMessageHandler); - DomainServer domainServer(argc, argv); return domainServer.exec(); diff --git a/examples/audioDeviceExample.js b/examples/audioDeviceExample.js new file mode 100644 index 0000000000..1ee00a1582 --- /dev/null +++ b/examples/audioDeviceExample.js @@ -0,0 +1,52 @@ +// +// audioDeviceExample.js +// hifi +// +// Created by Brad Hefta-Gaub on 3/22/14 +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// +// This is an example script that demonstrates use of the Menu object +// + + +var outputDevices = AudioDevice.getOutputDevices(); +var defaultOutputDevice = AudioDevice.getDefaultOutputDevice(); +var selectOutputDevice = outputDevices[0]; +print("Output Devices:"); +for(var i = 0; i < outputDevices.length; i++) { + if (outputDevices[i] == defaultOutputDevice) { + print(" " + outputDevices[i] + " << default"); + } else { + print(" " + outputDevices[i]); + } +} + +print("Default Output Device:" + defaultOutputDevice); +print("Selected Output Device:" + selectOutputDevice); +print("Current Audio Output Device: " + AudioDevice.getOutputDevice()); +AudioDevice.setOutputDevice(selectOutputDevice); +print("Audio Output Device: " + AudioDevice.getOutputDevice()); + +var inputDevices = AudioDevice.getInputDevices(); +var selectInputDevice = inputDevices[0]; +var defaultInputDevice = AudioDevice.getDefaultInputDevice(); +print("Input Devices:"); +for(var i = 0; i < inputDevices.length; i++) { + if (inputDevices[i] == defaultInputDevice) { + print(" " + inputDevices[i] + " << default"); + } else { + print(" " + inputDevices[i]); + } +} + +print("Default Input Device:" + defaultInputDevice); +print("Selected Input Device:" + selectInputDevice); +print("Current Audio Input Device: " + AudioDevice.getInputDevice()); +AudioDevice.setInputDevice(selectInputDevice); +print("Audio Input Device: " + AudioDevice.getInputDevice()); + +print("Audio Input Device Level: " + AudioDevice.getInputVolume()); +AudioDevice.setInputVolume(AudioDevice.getInputVolume() * 2); // twice as loud! +print("Audio Input Device Level: " + AudioDevice.getInputVolume()); + +Script.stop(); \ No newline at end of file diff --git a/examples/bot.js b/examples/bot.js index ebc8c4d5aa..ea78f40de9 100644 --- a/examples/bot.js +++ b/examples/bot.js @@ -26,15 +26,25 @@ var CHANCE_OF_MOVING = 0.005; var CHANCE_OF_SOUND = 0.005; var CHANCE_OF_HEAD_TURNING = 0.05; var CHANCE_OF_BIG_MOVE = 0.1; +var CHANCE_OF_WAVING = 0.005; // Currently this isn't working + +var shouldReceiveVoxels = true; +var VOXEL_FPS = 60.0; +var lastVoxelQueryTime = 0.0; var isMoving = false; var isTurningHead = false; +var isPlayingAudio = false; +var isWaving = false; +var waveFrequency = 0.0; +var waveAmplitude = 0.0; var X_MIN = 0.0; var X_MAX = 5.0; var Z_MIN = 0.0; var Z_MAX = 5.0; var Y_PELVIS = 2.5; +var SHOULDER_JOINT_NUMBER = 15; var MOVE_RANGE_SMALL = 0.5; var MOVE_RANGE_BIG = Math.max(X_MAX - X_MIN, Z_MAX - Z_MIN) / 2.0; @@ -51,6 +61,8 @@ var targetDirection = { x: 0, y: 0, z: 0, w: 0 }; var currentDirection = { x: 0, y: 0, z: 0, w: 0 }; var targetHeadPitch = 0.0; +var cumulativeTime = 0.0; + var sounds = []; loadSounds(); @@ -100,13 +112,37 @@ Agent.isListeningToAudioStream = true; Avatar.position = firstPosition; printVector("New bot, position = ", Avatar.position); +function stopWaving() { + isWaving = false; + Avatar.clearJointData(SHOULDER_JOINT_NUMBER); +} + function updateBehavior(deltaTime) { - if (Math.random() < CHANCE_OF_SOUND) { - playRandomSound(); + + cumulativeTime += deltaTime; + + if (shouldReceiveVoxels && ((cumulativeTime - lastVoxelQueryTime) > (1.0 / VOXEL_FPS))) { + VoxelViewer.setPosition(Avatar.position); + VoxelViewer.setOrientation(Avatar.orientation); + VoxelViewer.queryOctree(); + lastVoxelQueryTime = cumulativeTime; + /* + if (Math.random() < (1.0 / VOXEL_FPS)) { + print("Voxels in view = " + VoxelViewer.getOctreeElementsCount()); + }*/ } - if (Agent.isPlayingAvatarSound) { - Avatar.handPosition = Vec3.sum(Avatar.position, Quat.getFront(Avatar.orientation)); + if (!isWaving && (Math.random() < CHANCE_OF_WAVING)) { + isWaving = true; + waveFrequency = 1.0 + Math.random() * 5.0; + waveAmplitude = 5.0 + Math.random() * 60.0; + Script.setTimeout(stopWaving, 1000 + Math.random() * 2000); + } else if (isWaving) { + Avatar.setJointData(SHOULDER_JOINT_NUMBER, Quat.fromPitchYawRollDegrees(0.0, 0.0, waveAmplitude * Math.sin(cumulativeTime * waveFrequency))); + } + + if (Math.random() < CHANCE_OF_SOUND) { + playRandomSound(); } if (!isTurningHead && (Math.random() < CHANCE_OF_HEAD_TURNING)) { diff --git a/examples/crazylegs.js b/examples/crazylegs.js index c098758a38..099387e000 100644 --- a/examples/crazylegs.js +++ b/examples/crazylegs.js @@ -12,6 +12,10 @@ var AMPLITUDE = 45.0; var cumulativeTime = 0.0; +print("Joint List:"); +var jointList = MyAvatar.getJointNames(); +print(jointList); + Script.update.connect(function(deltaTime) { cumulativeTime += deltaTime; MyAvatar.setJointData("joint_R_hip", Quat.fromPitchYawRollDegrees(0.0, 0.0, AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY))); diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js new file mode 100644 index 0000000000..4b228492c7 --- /dev/null +++ b/examples/defaultScripts.js @@ -0,0 +1,5 @@ +// defaultScripts.js +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +Script.include("lookWithTouch.js"); +Script.include("editVoxels.js"); +Script.include("selectAudioDevice.js"); diff --git a/examples/editVoxels.js b/examples/editVoxels.js index ac0b67407b..cb2553a8e3 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -32,8 +32,6 @@ var MIN_PASTE_VOXEL_SCALE = .256; var zFightingSizeAdjust = 0.002; // used to adjust preview voxels to prevent z fighting var previewLineWidth = 1.5; -var oldMode = Camera.getMode(); -var trackAsOrbitOrPan = false; var isAdding = false; var isExtruding = false; var extrudeDirection = { x: 0, y: 0, z: 0 }; @@ -614,8 +612,6 @@ function showPreviewVoxel() { var guidePosition; if (trackAsRecolor || recolorToolSelected || trackAsEyedropper || eyedropperToolSelected) { Overlays.editOverlay(voxelPreview, { visible: true }); - } else if (trackAsOrbitOrPan) { - Overlays.editOverlay(voxelPreview, { visible: false }); } else if (voxelToolSelected && !isExtruding) { Overlays.editOverlay(voxelPreview, { visible: true }); } else if (isExtruding) { @@ -706,15 +702,12 @@ function showPreviewGuides() { } function trackMouseEvent(event) { - if (!trackAsOrbitOrPan) { - trackLastMouseX = event.x; - trackLastMouseY = event.y; - trackAsDelete = event.isControl; - trackAsRecolor = event.isShifted; - trackAsEyedropper = event.isMeta; - trackAsOrbitOrPan = event.isAlt; // TODO: double check this...?? - showPreviewGuides(); - } + trackLastMouseX = event.x; + trackLastMouseY = event.y; + trackAsDelete = event.isControl; + trackAsRecolor = event.isShifted; + trackAsEyedropper = event.isMeta; + showPreviewGuides(); } function trackKeyPressEvent(event) { @@ -742,6 +735,7 @@ function trackKeyReleaseEvent(event) { if (event.text == "TAB") { editToolsOn = !editToolsOn; moveTools(); + showPreviewGuides(); Audio.playSound(clickSound, audioOptions); } @@ -788,67 +782,64 @@ function mousePressEvent(event) { return; } - // no clicking on overlays while in panning mode - if (!trackAsOrbitOrPan) { - var clickedOnSomething = false; - var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); - - // If the user clicked on the thumb, handle the slider logic - if (clickedOverlay == thumb) { - isMovingSlider = true; - thumbClickOffsetX = event.x - (sliderX + thumbX); // this should be the position of the mouse relative to the thumb - clickedOnSomething = true; - - Overlays.editOverlay(thumb, { imageURL: toolIconUrl + "voxel-size-slider-handle.svg", }); - - } else if (clickedOverlay == voxelTool) { - voxelToolSelected = true; - recolorToolSelected = false; - eyedropperToolSelected = false; - moveTools(); - clickedOnSomething = true; - } else if (clickedOverlay == recolorTool) { - voxelToolSelected = false; - recolorToolSelected = true; - eyedropperToolSelected = false; - moveTools(); - clickedOnSomething = true; - } else if (clickedOverlay == eyedropperTool) { - voxelToolSelected = false; - recolorToolSelected = false; - eyedropperToolSelected = true; - moveTools(); - clickedOnSomething = true; - } else if (clickedOverlay == slider) { - - if (event.x < sliderX + minThumbX) { - thumbX -= thumbDeltaPerStep; - calcScaleFromThumb(thumbX); - } - - if (event.x > sliderX + maxThumbX) { - thumbX += thumbDeltaPerStep; - calcScaleFromThumb(thumbX); - } - - moveTools(); - clickedOnSomething = true; - } else { - // if the user clicked on one of the color swatches, update the selectedSwatch - for (s = 0; s < numColors; s++) { - if (clickedOverlay == swatches[s]) { - whichColor = s; - moveTools(); - clickedOnSomething = true; - break; - } - } + var clickedOnSomething = false; + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + + // If the user clicked on the thumb, handle the slider logic + if (clickedOverlay == thumb) { + isMovingSlider = true; + thumbClickOffsetX = event.x - (sliderX + thumbX); // this should be the position of the mouse relative to the thumb + clickedOnSomething = true; + + Overlays.editOverlay(thumb, { imageURL: toolIconUrl + "voxel-size-slider-handle.svg", }); + + } else if (clickedOverlay == voxelTool) { + voxelToolSelected = true; + recolorToolSelected = false; + eyedropperToolSelected = false; + moveTools(); + clickedOnSomething = true; + } else if (clickedOverlay == recolorTool) { + voxelToolSelected = false; + recolorToolSelected = true; + eyedropperToolSelected = false; + moveTools(); + clickedOnSomething = true; + } else if (clickedOverlay == eyedropperTool) { + voxelToolSelected = false; + recolorToolSelected = false; + eyedropperToolSelected = true; + moveTools(); + clickedOnSomething = true; + } else if (clickedOverlay == slider) { + + if (event.x < sliderX + minThumbX) { + thumbX -= thumbDeltaPerStep; + calcScaleFromThumb(thumbX); } - if (clickedOnSomething) { - return; // no further processing + + if (event.x > sliderX + maxThumbX) { + thumbX += thumbDeltaPerStep; + calcScaleFromThumb(thumbX); + } + + moveTools(); + clickedOnSomething = true; + } else { + // if the user clicked on one of the color swatches, update the selectedSwatch + for (s = 0; s < numColors; s++) { + if (clickedOverlay == swatches[s]) { + whichColor = s; + moveTools(); + clickedOnSomething = true; + break; + } } } - + if (clickedOnSomething) { + return; // no further processing + } + // TODO: does any of this stuff need to execute if we're panning or orbiting? trackMouseEvent(event); // used by preview support mouseX = event.x; @@ -1071,7 +1062,7 @@ function mouseMoveEvent(event) { } - if (!trackAsOrbitOrPan && isMovingSlider) { + if (isMovingSlider) { thumbX = (event.x - thumbClickOffsetX) - sliderX; if (thumbX < minThumbX) { thumbX = minThumbX; @@ -1081,7 +1072,7 @@ function mouseMoveEvent(event) { } calcScaleFromThumb(thumbX); - } else if (!trackAsOrbitOrPan && isAdding) { + } else if (isAdding) { // Watch the drag direction to tell which way to 'extrude' this voxel if (!isExtruding) { var pickRay = Camera.computePickRay(event.x, event.y); @@ -1112,7 +1103,6 @@ function mouseMoveEvent(event) { var dy = event.y - mouseY; if (Math.sqrt(dx*dx + dy*dy) > PIXELS_PER_EXTRUDE_VOXEL) { lastVoxelPosition = Vec3.sum(lastVoxelPosition, extrudeDirection); - Voxels.eraseVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s); Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z, extrudeScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue); mouseX = event.x; diff --git a/examples/gun.js b/examples/gun.js index 29b60a94ad..94f3fd4ee3 100644 --- a/examples/gun.js +++ b/examples/gun.js @@ -12,6 +12,11 @@ // // + +function getRandomFloat(min, max) { + return Math.random() * (max - min) + min; +} + var lastX = 0; var lastY = 0; var yawFromMouse = 0; @@ -19,17 +24,22 @@ var pitchFromMouse = 0; var isMouseDown = false; var BULLET_VELOCITY = 5.0; +var MIN_THROWER_DELAY = 1000; +var MAX_THROWER_DELAY = 1000; var LEFT_BUTTON_3 = 3; // Load some sound to use for loading and firing var fireSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guns/GUN-SHOT2.raw"); var loadSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guns/Gun_Reload_Weapon22.raw"); var impactSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guns/BulletImpact2.raw"); -var targetLaunchSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guns/GUN-SHOT2.raw"); +var targetHitSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Space%20Invaders/hit.raw"); +var targetLaunchSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Space%20Invaders/shoot.raw"); var audioOptions = new AudioInjectionOptions(); audioOptions.volume = 0.9; +var shotTime = new Date(); + // initialize our triggers var triggerPulled = new Array(); var numberOfTriggers = Controller.getNumberOfTriggers(); @@ -94,7 +104,9 @@ function shootTarget() { var DISTANCE_TO_LAUNCH_FROM = 3.0; var camera = Camera.getPosition(); //printVector("camera", camera); - var forwardVector = Quat.getFront(Camera.getOrientation()); + var targetDirection = Quat.angleAxis(getRandomFloat(-20.0, 20.0), { x:0, y:1, z:0 }); + targetDirection = Quat.multiply(Camera.getOrientation(), targetDirection); + var forwardVector = Quat.getFront(targetDirection); //printVector("forwardVector", forwardVector); var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_TO_LAUNCH_FROM)); //printVector("newPosition", newPosition); @@ -111,6 +123,9 @@ function shootTarget() { lifetime: 1000.0, damping: 0.99 }); + // Record start time + shotTime = new Date(); + // Play target shoot sound audioOptions.position = newPosition; Audio.playSound(targetLaunchSound, audioOptions); @@ -119,31 +134,43 @@ function shootTarget() { function particleCollisionWithVoxel(particle, voxel, penetration) { - Vec3.print('particleCollisionWithVoxel() ... penetration=', penetration); - var HOLE_SIZE = 0.125; var particleProperties = Particles.getParticleProperties(particle); var position = particleProperties.position; Particles.deleteParticle(particle); // Make a hole in this voxel + Vec3.print("penetration", penetration); + Vec3.print("position", position); + var pointOfEntry = Vec3.subtract(position, penetration); + Vec3.print("pointOfEntry", pointOfEntry); + Voxels.eraseVoxel(pointOfEntry.x, pointOfEntry.y, pointOfEntry.z, HOLE_SIZE); Voxels.eraseVoxel(position.x, position.y, position.z, HOLE_SIZE); //audioOptions.position = position; audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); - Audio.playSound(impactSound, audioOptions); + Audio.playSound(targetHitSound, audioOptions); } function particleCollisionWithParticle(particle1, particle2) { - print("Particle/Particle!"); score++; Overlays.editOverlay(text, { text: "Score: " + score } ); + // Sort out which particle is which + + // Record shot time + var endTime = new Date(); + var msecs = endTime.valueOf() - shotTime.valueOf(); + print("hit, msecs = " + msecs); Particles.deleteParticle(particle1); Particles.deleteParticle(particle2); + audioOptions.position = newPosition; + audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); + Audio.playSound(targetHitSound, audioOptions); } function keyPressEvent(event) { // if our tools are off, then don't do anything if (event.text == "t") { - shootTarget(); + var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY; + Script.setTimeout(shootTarget, time); } } @@ -164,7 +191,8 @@ function update(deltaTime) { // Check hydra controller for launch button press if (!isLaunchButtonPressed && Controller.isButtonPressed(LEFT_BUTTON_3)) { isLaunchButtonPressed = true; - shootTarget(); + var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY; + Script.setTimeout(shootTarget, time); } else if (isLaunchButtonPressed && !Controller.isButtonPressed(LEFT_BUTTON_3)) { isLaunchButtonPressed = false; diff --git a/examples/inspect.js b/examples/inspect.js index 9292450784..2443eadf9b 100644 --- a/examples/inspect.js +++ b/examples/inspect.js @@ -14,9 +14,11 @@ // Dragging the mouse will move your camera according to the mode you are in. // +var PI = 3.14 // No need for something more precise + var AZIMUTH_RATE = 90.0; var ALTITUDE_RATE = 200.0; -var RADIUS_RATE = 20.0; +var RADIUS_RATE = 1.0 / 100.0; var PAN_RATE = 50.0; var alt = false; @@ -46,7 +48,7 @@ var altitude = 0.0; function handleRadialMode(dx, dy) { azimuth += dx / AZIMUTH_RATE; - radius += radius * dy / RADIUS_RATE; + radius += radius * dy * RADIUS_RATE; if (radius < 1) { radius = 1; } @@ -61,6 +63,12 @@ function handleRadialMode(dx, dy) { function handleOrbitMode(dx, dy) { azimuth += dx / AZIMUTH_RATE; altitude += dy / ALTITUDE_RATE; + if (altitude > PI / 2.0) { + altitude = PI / 2.0; + } + if (altitude < -PI / 2.0) { + altitude = -PI / 2.0; + } vector = { x:(Math.cos(altitude) * Math.cos(azimuth)) * radius, y:Math.sin(altitude) * radius, @@ -165,7 +173,7 @@ function keyReleaseEvent(event) { } function mousePressEvent(event) { - if (alt) { + if (alt && !isActive) { isActive = true; mouseLastX = event.x; mouseLastY = event.y; diff --git a/examples/menuExample.js b/examples/menuExample.js index 3b18021302..874d95ec31 100644 --- a/examples/menuExample.js +++ b/examples/menuExample.js @@ -32,6 +32,7 @@ function setupMenus() { Menu.addSeparator("Foo","Removable Tools"); Menu.addMenuItem("Foo","Remove Foo item 4"); Menu.addMenuItem("Foo","Remove Foo"); + Menu.addMenuItem("Foo","Remove Bar-Spam"); Menu.addMenu("Bar"); Menu.addMenuItem("Bar","Bar item 1", "b"); @@ -91,6 +92,10 @@ function menuItemEvent(menuItem) { if (menuItem == "Remove Foo") { Menu.removeMenu("Foo"); } + if (menuItem == "Remove Bar-Spam") { + Menu.removeMenu("Bar > Spam"); + } + if (menuItem == "Remove Spam item 2") { Menu.removeMenuItem("Bar > Spam", "Spam item 2"); } diff --git a/examples/selectAudioDevice.js b/examples/selectAudioDevice.js new file mode 100644 index 0000000000..958ca7babf --- /dev/null +++ b/examples/selectAudioDevice.js @@ -0,0 +1,114 @@ +// +// audioDeviceExample.js +// hifi +// +// Created by Brad Hefta-Gaub on 3/22/14 +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// +// This is an example script that demonstrates use of the Menu object +// + +if (typeof String.prototype.startsWith != 'function') { + String.prototype.startsWith = function (str){ + return this.slice(0, str.length) == str; + }; +} + +if (typeof String.prototype.endsWith != 'function') { + String.prototype.endsWith = function (str){ + return this.slice(-str.length) == str; + }; +} + +if (typeof String.prototype.trimStartsWith != 'function') { + String.prototype.trimStartsWith = function (str){ + if (this.startsWith(str)) { + return this.substr(str.length); + } + return this; + }; +} + +if (typeof String.prototype.trimEndsWith != 'function') { + String.prototype.trimEndsWith = function (str){ + if (this.endsWith(str)) { + return this.substr(0,this.length - str.length); + } + return this; + }; +} + +var selectedInputMenu = ""; +var selectedOutputMenu = ""; + +function setupAudioMenus() { + Menu.addMenu("Tools > Audio"); + Menu.addSeparator("Tools > Audio","Output Audio Device"); + + var outputDevices = AudioDevice.getOutputDevices(); + var selectedOutputDevice = AudioDevice.getOutputDevice(); + + for(var i = 0; i < outputDevices.length; i++) { + var thisDeviceSelected = (outputDevices[i] == selectedOutputDevice); + var menuItem = "Use " + outputDevices[i] + " for Output"; + Menu.addMenuItem({ + menuName: "Tools > Audio", + menuItemName: menuItem, + isCheckable: true, + isChecked: thisDeviceSelected + }); + if (thisDeviceSelected) { + selectedOutputMenu = menuItem; + } + } + + Menu.addSeparator("Tools > Audio","Input Audio Device"); + + var inputDevices = AudioDevice.getInputDevices(); + var selectedInputDevice = AudioDevice.getInputDevice(); + + for(var i = 0; i < inputDevices.length; i++) { + var thisDeviceSelected = (inputDevices[i] == selectedInputDevice); + var menuItem = "Use " + inputDevices[i] + " for Input"; + Menu.addMenuItem({ + menuName: "Tools > Audio", + menuItemName: menuItem, + isCheckable: true, + isChecked: thisDeviceSelected + }); + if (thisDeviceSelected) { + selectedInputMenu = menuItem; + } + } +} + +setupAudioMenus(); + +function scriptEnding() { + Menu.removeMenu("Tools > Audio"); +} +Script.scriptEnding.connect(scriptEnding); + + +function menuItemEvent(menuItem) { + if (menuItem.startsWith("Use ")) { + if (menuItem.endsWith(" for Output")) { + var selectedDevice = menuItem.trimStartsWith("Use ").trimEndsWith(" for Output"); + print("output audio selection..." + selectedDevice); + Menu.setIsOptionChecked(selectedOutputMenu, false); + selectedOutputMenu = menuItem; + Menu.setIsOptionChecked(selectedOutputMenu, true); + AudioDevice.setOutputDevice(selectedDevice); + + } else if (menuItem.endsWith(" for Input")) { + var selectedDevice = menuItem.trimStartsWith("Use ").trimEndsWith(" for Input"); + print("input audio selection..." + selectedDevice); + Menu.setIsOptionChecked(selectedInputMenu, false); + selectedInputMenu = menuItem; + Menu.setIsOptionChecked(selectedInputMenu, true); + AudioDevice.setInputDevice(selectedDevice); + } + } +} + +Menu.menuItemEvent.connect(menuItemEvent); diff --git a/examples/settingsExample.js b/examples/settingsExample.js new file mode 100644 index 0000000000..0dcc5482b6 --- /dev/null +++ b/examples/settingsExample.js @@ -0,0 +1,18 @@ +// +// settingsExample.js +// hifi +// +// Created by Brad Hefta-Gaub on 3/22/14 +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// +// This is an example script that demonstrates use of the Menu object +// + + + + +print("mySetting: " + Settings.getValue("mySetting")); +Settings.setValue("mySetting", "spam"); +print("mySetting: " + Settings.getValue("mySetting")); + +Script.stop(); \ No newline at end of file diff --git a/examples/voxelBird.js b/examples/voxelBird.js deleted file mode 100644 index 1e33851ff6..0000000000 --- a/examples/voxelBird.js +++ /dev/null @@ -1,133 +0,0 @@ -// -// This sample script moves a voxel around like a bird and sometimes makes tweeting noises -// - -function vLength(v) { - return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); -} - -function printVector(v) { - print(v.x + ", " + v.y + ", " + v.z + "\n"); -} - -// Create a random vector with individual lengths between a,b -function randVector(a, b) { - var rval = { x: a + Math.random() * (b - a), y: a + Math.random() * (b - a), z: a + Math.random() * (b - a) }; - return rval; -} - -function vMinus(a, b) { - var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; - return rval; -} - -function vPlus(a, b) { - var rval = { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }; - return rval; -} - -function vCopy(a, b) { - a.x = b.x; - a.y = b.y; - a.z = b.z; - return; -} - -// Returns a vector which is fraction of the way between a and b -function vInterpolate(a, b, fraction) { - var rval = { x: a.x + (b.x - a.x) * fraction, y: a.y + (b.y - a.y) * fraction, z: a.z + (b.z - a.z) * fraction }; - return rval; -} - -// Decide what kind of bird we are -var tweet; - -var which = Math.random(); -if (which < 0.2) { - tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/bushtit_1.raw"); -} else if (which < 0.4) { - tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/rosyfacedlovebird.raw"); -} else if (which < 0.6) { - tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/saysphoebe.raw"); -} else if (which < 0.8) { - tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/mexicanWhipoorwill.raw"); -} else { - tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/westernscreechowl.raw"); -} - -var position = { x: 0, y: 0, z: 0 }; -var lastPosition = { x: 0, y: 0, z: 0 }; -var oldPosition = { x: 0, y: 0, z:0 }; -var targetPosition = { x: 0, y: 0, z: 0 }; - -var size = 0.125; -var range = 50.0; // Over what distance in meters do you want your bird to fly around -var color = { r: 100, g: 50, b: 150 }; -var colorEdge = { r:255, g:250, b:175 }; -var frame = 0; -var thisColor = color; -var moving = false; -var tweeting = 0; -var moved = true; - -var CHANCE_OF_MOVING = 0.05; -var CHANCE_OF_TWEETING = 0.05; - -function moveBird(deltaTime) { - frame++; - if (frame % 3 == 0) { - // Tweeting behavior - if (tweeting == 0) { - if (Math.random() < CHANCE_OF_TWEETING) { - //print("tweet!" + "\n"); - var options = new AudioInjectionOptions();
 - options.position = position; - options.volume = 0.75; - Audio.playSound(tweet, options); - tweeting = 10; - } - } else { - tweeting -= 1; - } - // Moving behavior - if (moving == false) { - if (Math.random() < CHANCE_OF_MOVING) { - targetPosition = randVector(0, range); - //printVector(position); - moving = true; - } - } - if (moving) { - position = vInterpolate(position, targetPosition, 0.5); - if (vLength(vMinus(position, targetPosition)) < (size / 2.0)) { - moved = false; - moving = false; - } else { - moved = true; - } - } - - if (tweeting > 0) { - // Change color of voxel to blinky red a bit while playing the sound - var blinkColor = { r: Math.random() * 255, g: 0, b: 0 }; - Voxels.setVoxel(position.x, - position.y, - position.z, - size, - blinkColor.r, blinkColor.g, blinkColor.b); - } - if (moved) { - Voxels.setVoxel(position.x, position.y, position.z, size, thisColor.r, thisColor.g, thisColor.b); - // delete old voxel - - Voxels.eraseVoxel(oldPosition.x, oldPosition.y, oldPosition.z, size); - // Copy old location to new - vCopy(oldPosition, position); - moved = false; - } - } -} - -Voxels.setPacketsPerSecond(10000); -// Connect a call back that happens every frame -Script.update.connect(moveBird); \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 7126e8929f..f991212a6e 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -48,7 +48,7 @@ configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVe # grab the implementation and header files from src dirs file(GLOB INTERFACE_SRCS src/*.cpp src/*.h) -foreach(SUBDIR avatar devices renderer ui starfield location) +foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels) file(GLOB_RECURSE SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h) set(INTERFACE_SRCS ${INTERFACE_SRCS} "${SUBDIR_SRCS}") endforeach(SUBDIR) diff --git a/interface/interface_en.ts b/interface/interface_en.ts index 334711ea16..689b45afcf 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -4,22 +4,22 @@ Application - + Export Voxels - + Sparse Voxel Octree Files (*.svo) - + Open Script - + JavaScript Files (*.js) @@ -113,18 +113,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file @@ -132,28 +132,28 @@ QObject - - + + Import Voxels - + Loading ... - + Place voxels - + <b>Import</b> %1 as voxels - + Cancel diff --git a/interface/resources/shaders/model_shadow.frag b/interface/resources/shaders/model_shadow.frag new file mode 100644 index 0000000000..bcb597b13c --- /dev/null +++ b/interface/resources/shaders/model_shadow.frag @@ -0,0 +1,14 @@ +#version 120 + +// +// model_shadow.frag +// fragment shader +// +// Created by Andrzej Kapolka on 3/24/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +void main(void) { + // fixed color for now (we may eventually want to use texture alpha) + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); +} diff --git a/interface/resources/shaders/model_shadow.vert b/interface/resources/shaders/model_shadow.vert new file mode 100644 index 0000000000..ae7e871887 --- /dev/null +++ b/interface/resources/shaders/model_shadow.vert @@ -0,0 +1,14 @@ +#version 120 + +// +// model_shadow.vert +// vertex shader +// +// Created by Andrzej Kapolka on 3/24/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +void main(void) { + // just use standard pipeline transform + gl_Position = ftransform(); +} diff --git a/interface/resources/shaders/skin_model_shadow.vert b/interface/resources/shaders/skin_model_shadow.vert new file mode 100644 index 0000000000..b9ef05ad8a --- /dev/null +++ b/interface/resources/shaders/skin_model_shadow.vert @@ -0,0 +1,27 @@ +#version 120 + +// +// skin_model_shadow.vert +// vertex shader +// +// Created by Andrzej Kapolka on 3/24/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +const int MAX_CLUSTERS = 128; +const int INDICES_PER_VERTEX = 4; + +uniform mat4 clusterMatrices[MAX_CLUSTERS]; + +attribute vec4 clusterIndices; +attribute vec4 clusterWeights; + +void main(void) { + vec4 position = vec4(0.0, 0.0, 0.0, 0.0); + for (int i = 0; i < INDICES_PER_VERTEX; i++) { + mat4 clusterMatrix = clusterMatrices[int(clusterIndices[i])]; + float clusterWeight = clusterWeights[i]; + position += clusterMatrix * gl_Vertex * clusterWeight; + } + gl_Position = gl_ModelViewProjectionMatrix * position; +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 030e3bc6fd..2ca4ef74cd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -65,17 +65,21 @@ #include #include "Application.h" -#include "ClipboardScriptingInterface.h" #include "InterfaceVersion.h" #include "Menu.h" -#include "MenuScriptingInterface.h" #include "Util.h" #include "devices/OculusManager.h" #include "devices/TV3DManager.h" #include "renderer/ProgramObject.h" -#include "ui/TextRenderer.h" -#include "InfoView.h" + +#include "scripting/AudioDeviceScriptingInterface.h" +#include "scripting/ClipboardScriptingInterface.h" +#include "scripting/MenuScriptingInterface.h" +#include "scripting/SettingsScriptingInterface.h" + +#include "ui/InfoView.h" #include "ui/Snapshot.h" +#include "ui/TextRenderer.h" using namespace std; @@ -2163,21 +2167,22 @@ void Application::updateShadowMap() { glViewport(0, 0, fbo->width(), fbo->height()); glm::vec3 lightDirection = -getSunDirection(); - glm::quat rotation = glm::inverse(rotationBetween(IDENTITY_FRONT, lightDirection)); - glm::vec3 translation = glm::vec3(); + glm::quat rotation = rotationBetween(IDENTITY_FRONT, lightDirection); + glm::quat inverseRotation = glm::inverse(rotation); float nearScale = 0.0f; const float MAX_SHADOW_DISTANCE = 2.0f; - float farScale = (MAX_SHADOW_DISTANCE - _viewFrustum.getNearClip()) / (_viewFrustum.getFarClip() - _viewFrustum.getNearClip()); + float farScale = (MAX_SHADOW_DISTANCE - _viewFrustum.getNearClip()) / + (_viewFrustum.getFarClip() - _viewFrustum.getNearClip()); loadViewFrustum(_myCamera, _viewFrustum); glm::vec3 points[] = { - rotation * (glm::mix(_viewFrustum.getNearTopLeft(), _viewFrustum.getFarTopLeft(), nearScale) + translation), - rotation * (glm::mix(_viewFrustum.getNearTopRight(), _viewFrustum.getFarTopRight(), nearScale) + translation), - rotation * (glm::mix(_viewFrustum.getNearBottomLeft(), _viewFrustum.getFarBottomLeft(), nearScale) + translation), - rotation * (glm::mix(_viewFrustum.getNearBottomRight(), _viewFrustum.getFarBottomRight(), nearScale) + translation), - rotation * (glm::mix(_viewFrustum.getNearTopLeft(), _viewFrustum.getFarTopLeft(), farScale) + translation), - rotation * (glm::mix(_viewFrustum.getNearTopRight(), _viewFrustum.getFarTopRight(), farScale) + translation), - rotation * (glm::mix(_viewFrustum.getNearBottomLeft(), _viewFrustum.getFarBottomLeft(), farScale) + translation), - rotation * (glm::mix(_viewFrustum.getNearBottomRight(), _viewFrustum.getFarBottomRight(), farScale) + translation) }; + inverseRotation * (glm::mix(_viewFrustum.getNearTopLeft(), _viewFrustum.getFarTopLeft(), nearScale)), + inverseRotation * (glm::mix(_viewFrustum.getNearTopRight(), _viewFrustum.getFarTopRight(), nearScale)), + inverseRotation * (glm::mix(_viewFrustum.getNearBottomLeft(), _viewFrustum.getFarBottomLeft(), nearScale)), + inverseRotation * (glm::mix(_viewFrustum.getNearBottomRight(), _viewFrustum.getFarBottomRight(), nearScale)), + inverseRotation * (glm::mix(_viewFrustum.getNearTopLeft(), _viewFrustum.getFarTopLeft(), farScale)), + inverseRotation * (glm::mix(_viewFrustum.getNearTopRight(), _viewFrustum.getFarTopRight(), farScale)), + inverseRotation * (glm::mix(_viewFrustum.getNearBottomLeft(), _viewFrustum.getFarBottomLeft(), farScale)), + inverseRotation * (glm::mix(_viewFrustum.getNearBottomRight(), _viewFrustum.getFarBottomRight(), farScale)) }; glm::vec3 minima(FLT_MAX, FLT_MAX, FLT_MAX), maxima(-FLT_MAX, -FLT_MAX, -FLT_MAX); for (size_t i = 0; i < sizeof(points) / sizeof(points[0]); i++) { minima = glm::min(minima, points[i]); @@ -2190,9 +2195,20 @@ void Application::updateShadowMap() { // save the combined matrix for rendering _shadowMatrix = glm::transpose(glm::translate(glm::vec3(0.5f, 0.5f, 0.5f)) * glm::scale(glm::vec3(0.5f, 0.5f, 0.5f)) * - glm::ortho(minima.x, maxima.x, minima.y, maxima.y, -maxima.z, -minima.z) * - glm::mat4_cast(rotation) * glm::translate(translation)); + glm::ortho(minima.x, maxima.x, minima.y, maxima.y, -maxima.z, -minima.z) * glm::mat4_cast(inverseRotation)); + // update the shadow view frustum + _shadowViewFrustum.setPosition(rotation * ((minima + maxima) * 0.5f)); + _shadowViewFrustum.setOrientation(rotation); + _shadowViewFrustum.setOrthographic(true); + _shadowViewFrustum.setWidth(maxima.x - minima.x); + _shadowViewFrustum.setHeight(maxima.y - minima.y); + _shadowViewFrustum.setNearClip(minima.z); + _shadowViewFrustum.setFarClip(maxima.z); + _shadowViewFrustum.setEyeOffsetPosition(glm::vec3()); + _shadowViewFrustum.setEyeOffsetOrientation(glm::quat()); + _shadowViewFrustum.calculate(); + glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); @@ -2201,16 +2217,14 @@ void Application::updateShadowMap() { glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); - glm::vec3 axis = glm::axis(rotation); - glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glm::vec3 axis = glm::axis(inverseRotation); + glRotatef(glm::degrees(glm::angle(inverseRotation)), axis.x, axis.y, axis.z); // store view matrix without translation, which we'll use for precision-sensitive objects glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&_untranslatedViewMatrix); - _viewMatrixTranslation = translation; + _viewMatrixTranslation = glm::vec3(); - glTranslatef(translation.x, translation.y, translation.z); - - _avatarManager.renderAvatars(true); + _avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE); _particles.render(); glPopMatrix(); @@ -2388,7 +2402,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { } bool mirrorMode = (whichCamera.getInterpolatedMode() == CAMERA_MODE_MIRROR); - _avatarManager.renderAvatars(mirrorMode, selfAvatarOnly); + _avatarManager.renderAvatars(mirrorMode ? Avatar::MIRROR_RENDER_MODE : Avatar::NORMAL_RENDER_MODE, selfAvatarOnly); if (!selfAvatarOnly) { // Render the world box @@ -3252,6 +3266,9 @@ void Application::domainChanged(const QString& domainHostname) { // reset the particle renderer _particles.clear(); + + // reset the voxels renderer + _voxels.killLocalVoxels(); } void Application::connectedToDomain(const QString& hostname) { @@ -3545,6 +3562,8 @@ void Application::loadScript(const QString& fileNameString) { scriptEngine->registerGlobalObject("Overlays", &_overlays); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); QThread* workerThread = new QThread(this); diff --git a/interface/src/Application.h b/interface/src/Application.h index 28060113a9..caeea529af 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -29,12 +29,12 @@ #include #include #include +#include +#include #include "Audio.h" -#include "BandwidthMeter.h" #include "BuckyBalls.h" #include "Camera.h" -#include "ControllerScriptingInterface.h" #include "DatagramProcessor.h" #include "Environment.h" #include "FileLogger.h" @@ -44,13 +44,6 @@ #include "PacketHeaders.h" #include "ParticleTreeRenderer.h" #include "Stars.h" -#include "ViewFrustum.h" -#include "VoxelFade.h" -#include "VoxelEditPacketSender.h" -#include "VoxelHideShowThread.h" -#include "VoxelPacketProcessor.h" -#include "VoxelSystem.h" -#include "VoxelImporter.h" #include "avatar/Avatar.h" #include "avatar/AvatarManager.h" #include "avatar/MyAvatar.h" @@ -63,13 +56,20 @@ #include "renderer/PointShader.h" #include "renderer/TextureCache.h" #include "renderer/VoxelShader.h" +#include "scripting/ControllerScriptingInterface.h" #include "ui/BandwidthDialog.h" +#include "ui/BandwidthMeter.h" #include "ui/OctreeStatsDialog.h" #include "ui/RearMirrorTools.h" #include "ui/LodToolsDialog.h" #include "ui/LogDialog.h" #include "ui/UpdateDialog.h" -#include "ui/Overlays.h" +#include "ui/overlays/Overlays.h" +#include "voxels/VoxelFade.h" +#include "voxels/VoxelHideShowThread.h" +#include "voxels/VoxelImporter.h" +#include "voxels/VoxelPacketProcessor.h" +#include "voxels/VoxelSystem.h" class QAction; @@ -155,6 +155,7 @@ public: Audio* getAudio() { return &_audio; } Camera* getCamera() { return &_myCamera; } ViewFrustum* getViewFrustum() { return &_viewFrustum; } + ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; } VoxelSystem* getVoxels() { return &_voxels; } VoxelTree* getVoxelTree() { return _voxels.getTree(); } ParticleTreeRenderer* getParticles() { return &_particles; } @@ -171,7 +172,11 @@ public: Visage* getVisage() { return &_visage; } SixenseManager* getSixenseManager() { return &_sixenseManager; } BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; } - QSettings* getSettings() { return _settings; } + + /// if you need to access the application settings, use lockSettings()/unlockSettings() + QSettings* lockSettings() { _settingsMutex.lock(); return _settings; } + void unlockSettings() { _settingsMutex.unlock(); } + QMainWindow* getWindow() { return _window; } NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; } void lockOctreeSceneStats() { _octreeSceneStatsLock.lockForRead(); } @@ -352,6 +357,7 @@ private: DatagramProcessor _datagramProcessor; QNetworkAccessManager* _networkAccessManager; + QMutex _settingsMutex; QSettings* _settings; glm::vec3 _gravity; @@ -385,6 +391,7 @@ private: ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. ViewFrustum _lastQueriedViewFrustum; /// last view frustum used to query octree servers (voxels, particles) + ViewFrustum _shadowViewFrustum; quint64 _lastQueriedTime; Oscilloscope _audioScope; diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index b684cec46e..734b5345fb 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -92,6 +92,16 @@ void Audio::reset() { _ringBuffer.reset(); } +QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) { + QAudioDeviceInfo result; + foreach(QAudioDeviceInfo audioDevice, QAudioDeviceInfo::availableDevices(mode)) { + if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) { + result = audioDevice; + } + } + return result; +} + QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #ifdef __APPLE__ if (QAudioDeviceInfo::availableDevices(mode).size() > 1) { @@ -249,27 +259,105 @@ void Audio::start() { _desiredOutputFormat.setChannelCount(2); QAudioDeviceInfo inputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioInput); - qDebug() << "The audio input device is" << inputDeviceInfo.deviceName(); + qDebug() << "The default audio input device is" << inputDeviceInfo.deviceName(); + bool inputFormatSupported = switchInputToAudioDevice(inputDeviceInfo.deviceName()); + + QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput); + qDebug() << "The default audio output device is" << outputDeviceInfo.deviceName(); + bool outputFormatSupported = switchOutputToAudioDevice(outputDeviceInfo.deviceName()); - if (adjustedFormatForAudioDevice(inputDeviceInfo, _desiredInputFormat, _inputFormat)) { - qDebug() << "The format to be used for audio input is" << _inputFormat; + if (!inputFormatSupported || !outputFormatSupported) { + qDebug() << "Unable to set up audio I/O because of a problem with input or output formats."; + } +} + +QString Audio::getDefaultDeviceName(QAudio::Mode mode) { + QAudioDeviceInfo deviceInfo = defaultAudioDeviceForMode(mode); + return deviceInfo.deviceName(); +} + +QVector Audio::getDeviceNames(QAudio::Mode mode) { + QVector deviceNames; + foreach(QAudioDeviceInfo audioDevice, QAudioDeviceInfo::availableDevices(mode)) { + deviceNames << audioDevice.deviceName().trimmed(); + } + return deviceNames; +} + +bool Audio::switchInputToAudioDevice(const QString& inputDeviceName) { + bool supportedFormat = false; + + // cleanup any previously initialized device + if (_audioInput) { + _audioInput->stop(); + disconnect(_inputDevice, 0, 0, 0); + _inputDevice = NULL; + + delete _audioInput; + _audioInput = NULL; + _numInputCallbackBytes = 0; + + _inputAudioDeviceName = ""; + } + + QAudioDeviceInfo inputDeviceInfo = getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName); + + if (!inputDeviceInfo.isNull()) { + qDebug() << "The audio input device " << inputDeviceInfo.deviceName() << "is available."; + _inputAudioDeviceName = inputDeviceInfo.deviceName().trimmed(); + + if (adjustedFormatForAudioDevice(inputDeviceInfo, _desiredInputFormat, _inputFormat)) { + qDebug() << "The format to be used for audio input is" << _inputFormat; - _audioInput = new QAudioInput(inputDeviceInfo, _inputFormat, this); - _numInputCallbackBytes = NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL * _inputFormat.channelCount() - * (_inputFormat.sampleRate() / SAMPLE_RATE) - / CALLBACK_ACCELERATOR_RATIO; - _audioInput->setBufferSize(_numInputCallbackBytes); + _audioInput = new QAudioInput(inputDeviceInfo, _inputFormat, this); + _numInputCallbackBytes = NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL * _inputFormat.channelCount() + * (_inputFormat.sampleRate() / SAMPLE_RATE) + / CALLBACK_ACCELERATOR_RATIO; + _audioInput->setBufferSize(_numInputCallbackBytes); - QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput); - qDebug() << "The audio output device is" << outputDeviceInfo.deviceName(); - - if (adjustedFormatForAudioDevice(outputDeviceInfo, _desiredOutputFormat, _outputFormat)) { - qDebug() << "The format to be used for audio output is" << _outputFormat; - + // how do we want to handle input working, but output not working? _inputRingBuffer.resizeForFrameSize(_numInputCallbackBytes * CALLBACK_ACCELERATOR_RATIO / sizeof(int16_t)); _inputDevice = _audioInput->start(); connect(_inputDevice, SIGNAL(readyRead()), this, SLOT(handleAudioInput())); + supportedFormat = true; + } + } + return supportedFormat; +} + +bool Audio::switchOutputToAudioDevice(const QString& outputDeviceName) { + bool supportedFormat = false; + + // cleanup any previously initialized device + if (_audioOutput) { + _audioOutput->stop(); + disconnect(_outputDevice, 0, 0, 0); + _outputDevice = NULL; + + delete _audioOutput; + _audioOutput = NULL; + _numInputCallbackBytes = 0; + + _loopbackOutputDevice = NULL; + delete _loopbackAudioOutput; + _loopbackAudioOutput = NULL; + + _proceduralOutputDevice = NULL; + delete _proceduralAudioOutput; + _proceduralAudioOutput = NULL; + _outputAudioDeviceName = ""; + } + + QAudioDeviceInfo outputDeviceInfo = getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName); + + if (!outputDeviceInfo.isNull()) { + qDebug() << "The audio output device " << outputDeviceInfo.deviceName() << "is available."; + _outputAudioDeviceName = outputDeviceInfo.deviceName().trimmed(); + + if (adjustedFormatForAudioDevice(outputDeviceInfo, _desiredOutputFormat, _outputFormat)) { + qDebug() << "The format to be used for audio output is" << _outputFormat; + // setup our general output device for audio-mixer audio _audioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); _audioOutput->setBufferSize(_ringBuffer.getSampleCapacity() * sizeof(int16_t)); @@ -278,17 +366,15 @@ void Audio::start() { // setup a loopback audio output device _loopbackAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); - + // setup a procedural audio output device _proceduralAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); gettimeofday(&_lastReceiveTime, NULL); + supportedFormat = true; } - - return; } - - qDebug() << "Unable to set up audio I/O because of a problem with input or output formats."; + return supportedFormat; } void Audio::handleAudioInput() { @@ -309,13 +395,15 @@ void Audio::handleAudioInput() { if (Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio) && !_muted) { // if this person wants local loopback add that to the locally injected audio - if (!_loopbackOutputDevice) { + if (!_loopbackOutputDevice && _loopbackAudioOutput) { // we didn't have the loopback output device going so set that up now _loopbackOutputDevice = _loopbackAudioOutput->start(); } if (_inputFormat == _outputFormat) { - _loopbackOutputDevice->write(inputByteArray); + if (_loopbackOutputDevice) { + _loopbackOutputDevice->write(inputByteArray); + } } else { static float loopbackOutputToInputRatio = (_outputFormat.sampleRate() / (float) _inputFormat.sampleRate()) * (_outputFormat.channelCount() / _inputFormat.channelCount()); @@ -326,7 +414,9 @@ void Audio::handleAudioInput() { inputByteArray.size() / sizeof(int16_t), loopBackByteArray.size() / sizeof(int16_t), _inputFormat, _outputFormat); - _loopbackOutputDevice->write(loopBackByteArray); + if (_loopbackOutputDevice) { + _loopbackOutputDevice->write(loopBackByteArray); + } } } @@ -455,7 +545,7 @@ void Audio::handleAudioInput() { addProceduralSounds(monoAudioSamples, NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); - if (!_proceduralOutputDevice) { + if (!_proceduralOutputDevice && _proceduralAudioOutput) { _proceduralOutputDevice = _proceduralAudioOutput->start(); } @@ -469,7 +559,9 @@ void Audio::handleAudioInput() { NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * 4, _desiredInputFormat, _outputFormat); - _proceduralOutputDevice->write(proceduralOutput); + if (_proceduralOutputDevice) { + _proceduralOutputDevice->write(proceduralOutput); + } NodeList* nodeList = NodeList::getInstance(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); @@ -553,7 +645,7 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { static float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate()) * (_desiredOutputFormat.channelCount() / (float) _outputFormat.channelCount()); - if (!_ringBuffer.isStarved() && _audioOutput->bytesFree() == _audioOutput->bufferSize()) { + if (!_ringBuffer.isStarved() && _audioOutput && _audioOutput->bytesFree() == _audioOutput->bufferSize()) { // we don't have any audio data left in the output buffer // we just starved //qDebug() << "Audio output just starved."; diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 880391d7f3..7aa1ef5afe 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -19,17 +19,20 @@ #include "InterfaceConfig.h" +#include +#include +#include #include #include #include +#include #include #include #include -#include "Oscilloscope.h" +#include "ui/Oscilloscope.h" -#include static const int NUM_AUDIO_CHANNELS = 2; @@ -72,7 +75,7 @@ public: int getNetworkSampleRate() { return SAMPLE_RATE; } int getNetworkBufferLengthSamplesPerChannel() { return NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; } - + public slots: void start(); void addReceivedAudioToBuffer(const QByteArray& audioByteArray); @@ -83,10 +86,21 @@ public slots: virtual void handleAudioByteArray(const QByteArray& audioByteArray); + bool switchInputToAudioDevice(const QString& inputDeviceName); + bool switchOutputToAudioDevice(const QString& outputDeviceName); + QString getDeviceName(QAudio::Mode mode) const { return (mode == QAudio::AudioInput) ? + _inputAudioDeviceName : _outputAudioDeviceName; } + QString getDefaultDeviceName(QAudio::Mode mode); + QVector getDeviceNames(QAudio::Mode mode); + + float getInputVolume() const { return (_audioInput) ? _audioInput->volume() : 0.0f; } + void setInputVolume(float volume) { if (_audioInput) _audioInput->setVolume(volume); } + signals: bool muteToggled(); private: + QByteArray firstInputFrame; QAudioInput* _audioInput; QAudioFormat _desiredInputFormat; @@ -105,6 +119,9 @@ private: QIODevice* _proceduralOutputDevice; AudioRingBuffer _inputRingBuffer; AudioRingBuffer _ringBuffer; + + QString _inputAudioDeviceName; + QString _outputAudioDeviceName; Oscilloscope* _scope; StDev _stdev; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index e62c7e1102..71500c12d0 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -33,11 +33,11 @@ #include "Application.h" #include "Menu.h" -#include "MenuScriptingInterface.h" +#include "scripting/MenuScriptingInterface.h" #include "Util.h" -#include "InfoView.h" +#include "ui/InfoView.h" #include "ui/MetavoxelEditor.h" -#include "ModelBrowser.h" +#include "ui/ModelBrowser.h" Menu* Menu::_instance = NULL; @@ -61,6 +61,7 @@ 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; Menu::Menu() : @@ -75,9 +76,11 @@ Menu::Menu() : _lodToolsDialog(NULL), _maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM), _voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE), + _avatarLODDistanceMultiplier(DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER), _boundaryLevelAdjust(0), _maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS), _lastAdjust(usecTimestampNow()), + _lastAvatarDetailDrop(usecTimestampNow()), _fpsAverage(FIVE_SECONDS_OF_FRAMES), _loginAction(NULL) { @@ -374,8 +377,10 @@ Menu::~Menu() { } void Menu::loadSettings(QSettings* settings) { + bool lockedSettings = false; if (!settings) { - settings = Application::getInstance()->getSettings(); + settings = Application::getInstance()->lockSettings(); + lockedSettings = true; } _audioJitterBufferSamples = loadSetting(settings, "audioJitterBufferSamples", 0); @@ -384,6 +389,8 @@ void Menu::loadSettings(QSettings* settings) { _maxVoxels = loadSetting(settings, "maxVoxels", DEFAULT_MAX_VOXELS_PER_SYSTEM); _maxVoxelPacketsPerSecond = loadSetting(settings, "maxVoxelsPPS", DEFAULT_MAX_VOXEL_PPS); _voxelSizeScale = loadSetting(settings, "voxelSizeScale", DEFAULT_OCTREE_SIZE_SCALE); + _avatarLODDistanceMultiplier = loadSetting(settings, "avatarLODDistanceMultiplier", + DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER); _boundaryLevelAdjust = loadSetting(settings, "boundaryLevelAdjust", 0); settings->beginGroup("View Frustum Offset Camera"); @@ -404,11 +411,17 @@ void Menu::loadSettings(QSettings* settings) { // TODO: cache more settings in MyAvatar that are checked with very high frequency. MyAvatar* myAvatar = Application::getInstance()->getAvatar(); myAvatar->updateCollisionFlags(); + + if (lockedSettings) { + Application::getInstance()->unlockSettings(); + } } void Menu::saveSettings(QSettings* settings) { + bool lockedSettings = false; if (!settings) { - settings = Application::getInstance()->getSettings(); + settings = Application::getInstance()->lockSettings(); + lockedSettings = true; } settings->setValue("audioJitterBufferSamples", _audioJitterBufferSamples); @@ -417,6 +430,7 @@ void Menu::saveSettings(QSettings* settings) { settings->setValue("maxVoxels", _maxVoxels); settings->setValue("maxVoxelsPPS", _maxVoxelPacketsPerSecond); settings->setValue("voxelSizeScale", _voxelSizeScale); + settings->setValue("avatarLODDistanceMultiplier", _avatarLODDistanceMultiplier); settings->setValue("boundaryLevelAdjust", _boundaryLevelAdjust); settings->beginGroup("View Frustum Offset Camera"); settings->setValue("viewFrustumOffsetYaw", _viewFrustumOffset.yaw); @@ -430,6 +444,9 @@ void Menu::saveSettings(QSettings* settings) { Application::getInstance()->getAvatar()->saveData(settings); NodeList::getInstance()->saveData(settings); + if (lockedSettings) { + Application::getInstance()->unlockSettings(); + } } void Menu::importSettings() { @@ -1174,8 +1191,24 @@ void Menu::autoAdjustLOD(float currentFPS) { } _fpsAverage.updateAverage(currentFPS); - bool changed = false; quint64 now = usecTimestampNow(); + + if (_fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS) { + if (now - _lastAvatarDetailDrop > ADJUST_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()); + _lastAvatarDetailDrop = now; + } + } else if (_fpsAverage.getAverage() > ADJUST_LOD_UP_FPS) { + // let the detail level creep slowly upwards + const float DISTANCE_DECREASE_RATE = 0.01f; + const float MINIMUM_DISTANCE_MULTIPLIER = 0.1f; + _avatarLODDistanceMultiplier = qMax(MINIMUM_DISTANCE_MULTIPLIER, + _avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE); + } + + bool changed = false; quint64 elapsed = now - _lastAdjust; if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS @@ -1403,9 +1436,8 @@ void Menu::removeMenu(const QString& menuName) { if (action) { QString finalMenuPart; QMenu* parent = getMenuParent(menuName, finalMenuPart); - if (parent) { - removeAction(parent, finalMenuPart); + parent->removeAction(action); } else { QMenuBar::removeAction(action); } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index cb0ca4c5c4..9cd00db8e9 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -89,7 +89,7 @@ public: void autoAdjustLOD(float currentFPS); void setVoxelSizeScale(float sizeScale); float getVoxelSizeScale() const { return _voxelSizeScale; } - float getAvatarLODDistanceMultiplier() const { return DEFAULT_OCTREE_SIZE_SCALE / _voxelSizeScale; } + float getAvatarLODDistanceMultiplier() const { return _avatarLODDistanceMultiplier; } void setBoundaryLevelAdjust(int boundaryLevelAdjust); int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } @@ -202,12 +202,14 @@ private: LodToolsDialog* _lodToolsDialog; int _maxVoxels; float _voxelSizeScale; + float _avatarLODDistanceMultiplier; int _boundaryLevelAdjust; QAction* _useVoxelShader; int _maxVoxelPacketsPerSecond; QMenu* _activeScriptsMenu; QString replaceLastOccurrence(QChar search, QChar replace, QString string); quint64 _lastAdjust; + quint64 _lastAvatarDetailDrop; SimpleMovingAverage _fpsAverage; QAction* _loginAction; QAction* _chatAction; diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index f54bfb9d00..1921fe924b 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include @@ -24,12 +25,6 @@ #include "Util.h" -#ifdef _WIN32 -int isnan(double value) { return _isnan(value); } -#else -int isnan(double value) { return std::isnan(value); } -#endif - using namespace std; // no clue which versions are affected... @@ -88,7 +83,7 @@ float angleBetween(const glm::vec3& v1, const glm::vec3& v2) { // Helper function return the rotation from the first vector onto the second glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) { float angle = angleBetween(v1, v2); - if (isnan(angle) || angle < EPSILON) { + if (glm::isnan(angle) || angle < EPSILON) { return glm::quat(); } glm::vec3 axis; @@ -586,7 +581,7 @@ void runTimingTests() { float loadSetting(QSettings* settings, const char* name, float defaultValue) { float value = settings->value(name, defaultValue).toFloat(); - if (isnan(value)) { + if (glm::isnan(value)) { value = defaultValue; } return value; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 75a8386ea9..7e5a777484 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -189,10 +189,12 @@ static TextRenderer* textRenderer(TextRendererType type) { return displayNameRenderer; } -void Avatar::render(const glm::vec3& cameraPosition, bool forShadowMap) { +void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { // simple frustum check float boundingRadius = getBillboardSize(); - if (Application::getInstance()->getViewFrustum()->sphereInFrustum(cameraPosition, boundingRadius) == ViewFrustum::OUTSIDE) { + ViewFrustum* frustum = (renderMode == Avatar::SHADOW_RENDER_MODE) ? + Application::getInstance()->getShadowViewFrustum() : Application::getInstance()->getViewFrustum(); + if (frustum->sphereInFrustum(_position, boundingRadius) == ViewFrustum::OUTSIDE) { return; } @@ -202,11 +204,11 @@ void Avatar::render(const glm::vec3& cameraPosition, bool forShadowMap) { { // glow when moving far away const float GLOW_DISTANCE = 20.0f; - Glower glower(_moving && distanceToTarget > GLOW_DISTANCE && !forShadowMap ? 1.0f : 0.0f); + Glower glower(_moving && distanceToTarget > GLOW_DISTANCE && renderMode == NORMAL_RENDER_MODE ? 1.0f : 0.0f); // render body if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { - renderBody(forShadowMap); + renderBody(renderMode); } if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) { _skeletonModel.renderCollisionProxies(0.7f); @@ -230,7 +232,8 @@ void Avatar::render(const glm::vec3& cameraPosition, bool forShadowMap) { float angle = abs(angleBetween(toTarget + delta, toTarget - delta)); float sphereRadius = getHead()->getAverageLoudness() * SPHERE_LOUDNESS_SCALING; - if (!forShadowMap && (sphereRadius > MIN_SPHERE_SIZE) && (angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) { + if (renderMode == NORMAL_RENDER_MODE && (sphereRadius > MIN_SPHERE_SIZE) && + (angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) { glColor4f(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.f - angle / MAX_SPHERE_ANGLE); glPushMatrix(); glTranslatef(_position.x, _position.y, _position.z); @@ -242,8 +245,8 @@ void Avatar::render(const glm::vec3& cameraPosition, bool forShadowMap) { } const float DISPLAYNAME_DISTANCE = 10.0f; - setShowDisplayName(!forShadowMap && distanceToTarget < DISPLAYNAME_DISTANCE); - if (forShadowMap) { + setShowDisplayName(renderMode == NORMAL_RENDER_MODE && distanceToTarget < DISPLAYNAME_DISTANCE); + if (renderMode != NORMAL_RENDER_MODE) { return; } renderDisplayName(); @@ -306,17 +309,16 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { return glm::angleAxis(angle * proportion, axis); } -void Avatar::renderBody(bool forShadowMap) { +void Avatar::renderBody(RenderMode renderMode) { if (_shouldRenderBillboard || !(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { // render the billboard until both models are loaded - if (forShadowMap) { - return; + if (renderMode != SHADOW_RENDER_MODE) { + renderBillboard(); } - renderBillboard(); return; } - _skeletonModel.render(1.0f); - getHead()->render(1.0f); + _skeletonModel.render(1.0f, renderMode == SHADOW_RENDER_MODE); + getHead()->render(1.0f, renderMode == SHADOW_RENDER_MODE); getHand()->render(false); } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 30073c54d4..25600e0943 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -74,7 +74,10 @@ public: void init(); void simulate(float deltaTime); - virtual void render(const glm::vec3& cameraPosition, bool forShadowMap); + + enum RenderMode { NORMAL_RENDER_MODE, SHADOW_RENDER_MODE, MIRROR_RENDER_MODE }; + + virtual void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE); //setters void setDisplayingLookatVectors(bool displayingLookatVectors) { getHead()->setRenderLookatVectors(displayingLookatVectors); } @@ -133,7 +136,7 @@ public: void setShowDisplayName(bool showDisplayName); - int parseDataAtOffset(const QByteArray& packet, int offset); + virtual int parseDataAtOffset(const QByteArray& packet, int offset); static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2); @@ -181,7 +184,7 @@ protected: float getPelvisToHeadLength() const; void renderDisplayName(); - virtual void renderBody(bool forShadowMap); + virtual void renderBody(RenderMode renderMode); private: diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 29b23e1f5b..9147a08dbd 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -72,7 +72,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { simulateAvatarFades(deltaTime); } -void AvatarManager::renderAvatars(bool forShadowMapOrMirror, bool selfAvatarOnly) { +void AvatarManager::renderAvatars(Avatar::RenderMode renderMode, bool selfAvatarOnly) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::renderAvatars()"); bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors); @@ -85,13 +85,13 @@ void AvatarManager::renderAvatars(bool forShadowMapOrMirror, bool selfAvatarOnly if (!avatar->isInitialized()) { continue; } - avatar->render(cameraPosition, forShadowMapOrMirror); + avatar->render(cameraPosition, renderMode); avatar->setDisplayingLookatVectors(renderLookAtVectors); } - renderAvatarFades(cameraPosition, forShadowMapOrMirror); + renderAvatarFades(cameraPosition, renderMode); } else { // just render myAvatar - _myAvatar->render(cameraPosition, forShadowMapOrMirror); + _myAvatar->render(cameraPosition, renderMode); _myAvatar->setDisplayingLookatVectors(renderLookAtVectors); } } @@ -114,14 +114,14 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { } } -void AvatarManager::renderAvatarFades(const glm::vec3& cameraPosition, bool forShadowMap) { +void AvatarManager::renderAvatarFades(const glm::vec3& cameraPosition, Avatar::RenderMode renderMode) { // render avatar fades - Glower glower(forShadowMap ? 0.0f : 1.0f); + Glower glower(renderMode == Avatar::NORMAL_RENDER_MODE ? 1.0f : 0.0f); foreach(const AvatarSharedPointer& fadingAvatar, _avatarFades) { Avatar* avatar = static_cast(fadingAvatar.data()); if (avatar != static_cast(_myAvatar.data())) { - avatar->render(cameraPosition, forShadowMap); + avatar->render(cameraPosition, renderMode); } } } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 455153b92a..06494f309c 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -29,7 +29,7 @@ public: MyAvatar* getMyAvatar() { return _myAvatar.data(); } void updateOtherAvatars(float deltaTime); - void renderAvatars(bool forShadowMapOrMirror = false, bool selfAvatarOnly = false); + void renderAvatars(Avatar::RenderMode renderMode, bool selfAvatarOnly = false); void clearOtherAvatars(); @@ -45,7 +45,7 @@ private: void processKillAvatar(const QByteArray& datagram); void simulateAvatarFades(float deltaTime); - void renderAvatarFades(const glm::vec3& cameraPosition, bool forShadowMap); + void renderAvatarFades(const glm::vec3& cameraPosition, Avatar::RenderMode renderMode); // virtual override AvatarHash::iterator erase(const AvatarHash::iterator& iterator); diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index db6c3fe98d..19faa0da42 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -45,13 +45,6 @@ void FaceModel::simulate(float deltaTime) { Model::simulate(deltaTime, true, newJointStates); } -bool FaceModel::render(float alpha) { - if (!Model::render(alpha)) { - return false; - } - return true; -} - void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { // get the rotation axes in joint space and use them to adjust the rotation glm::mat3 axes = glm::mat3_cast(_rotation); diff --git a/interface/src/avatar/FaceModel.h b/interface/src/avatar/FaceModel.h index d0f0f6baef..acf2d2baf4 100644 --- a/interface/src/avatar/FaceModel.h +++ b/interface/src/avatar/FaceModel.h @@ -22,7 +22,6 @@ public: FaceModel(Head* owningHead); void simulate(float deltaTime); - bool render(float alpha); protected: diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 43a1787d13..77586dd7ae 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -27,10 +27,7 @@ Hand::Hand(Avatar* owningAvatar) : HandData((AvatarData*)owningAvatar), _owningAvatar(owningAvatar), - _renderAlpha(1.0), - _collisionCenter(0,0,0), - _collisionAge(0), - _collisionDuration(0) + _renderAlpha(1.0) { } @@ -42,10 +39,6 @@ void Hand::reset() { void Hand::simulate(float deltaTime, bool isMine) { - if (_collisionAge > 0.f) { - _collisionAge += deltaTime; - } - calculateGeometry(); if (isMine) { @@ -222,26 +215,6 @@ void Hand::collideAgainstOurself() { } } -void Hand::handleVoxelCollision(PalmData* palm, const glm::vec3& fingerTipPosition, VoxelTreeElement* voxel, float deltaTime) { - // Collision between finger and a voxel plays sound - const float LOWEST_FREQUENCY = 100.f; - const float HERTZ_PER_RGB = 3.f; - const float DECAY_PER_SAMPLE = 0.0005f; - const float DURATION_MAX = 2.0f; - const float MIN_VOLUME = 0.1f; - float volume = MIN_VOLUME + glm::clamp(glm::length(palm->getRawVelocity()), 0.f, (1.f - MIN_VOLUME)); - float duration = volume; - _collisionCenter = fingerTipPosition; - _collisionAge = deltaTime; - _collisionDuration = duration; - int voxelBrightness = voxel->getColor()[0] + voxel->getColor()[1] + voxel->getColor()[2]; - float frequency = LOWEST_FREQUENCY + (voxelBrightness * HERTZ_PER_RGB); - Application::getInstance()->getAudio()->startDrumSound(volume, - frequency, - DURATION_MAX, - DECAY_PER_SAMPLE); -} - void Hand::calculateGeometry() { // generate finger tip balls.... _leapFingerTipBalls.clear(); @@ -312,21 +285,6 @@ void Hand::render(bool isMine) { renderLeapHands(isMine); } - if (isMine) { - // If hand/voxel collision has happened, render a little expanding sphere - if (_collisionAge > 0.f) { - float opacity = glm::clamp(1.f - (_collisionAge / _collisionDuration), 0.f, 1.f); - glColor4f(1, 0, 0, 0.5 * opacity); - glPushMatrix(); - glTranslatef(_collisionCenter.x, _collisionCenter.y, _collisionCenter.z); - glutSolidSphere(_collisionAge * 0.25f, 20, 20); - glPopMatrix(); - if (_collisionAge > _collisionDuration) { - _collisionAge = 0.f; - } - } - } - glEnable(GL_DEPTH_TEST); glEnable(GL_RESCALE_NORMAL); diff --git a/interface/src/avatar/Hand.h b/interface/src/avatar/Hand.h index a1b1875424..f6ee5b281f 100755 --- a/interface/src/avatar/Hand.h +++ b/interface/src/avatar/Hand.h @@ -22,7 +22,6 @@ #include "InterfaceConfig.h" #include "world.h" -#include "VoxelSystem.h" class Avatar; @@ -72,13 +71,6 @@ private: std::vector _leapFingerTipBalls; std::vector _leapFingerRootBalls; - glm::vec3 _lastFingerAddVoxel, _lastFingerDeleteVoxel; - VoxelDetail _collidingVoxel; - - glm::vec3 _collisionCenter; - float _collisionAge; - float _collisionDuration; - // private methods void setLeapHands(const std::vector& handPositions, const std::vector& handNormals); @@ -88,8 +80,6 @@ private: void calculateGeometry(); - void handleVoxelCollision(PalmData* palm, const glm::vec3& fingerTipPosition, VoxelTreeElement* voxel, float deltaTime); - void playSlaps(PalmData& palm, Avatar* avatar); }; diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 44001a2015..4a81df8b74 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -168,8 +168,8 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { _eyePosition = calculateAverageEyePosition(); } -void Head::render(float alpha) { - if (_faceModel.render(alpha) && _renderLookatVectors) { +void Head::render(float alpha, bool forShadowMap) { + if (_faceModel.render(alpha, forShadowMap) && _renderLookatVectors) { renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition); } } diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index a9ea9b4cc6..60730c8724 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -37,7 +37,7 @@ public: void init(); void reset(); void simulate(float deltaTime, bool isMine, bool billboard = false); - void render(float alpha); + void render(float alpha, bool forShadowMap); void setScale(float scale); void setPosition(glm::vec3 position) { _position = position; } void setGravity(glm::vec3 gravity) { _gravity = gravity; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 13e2d3321a..19d15fb803 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -26,7 +26,6 @@ #include "Menu.h" #include "MyAvatar.h" #include "Physics.h" -#include "VoxelSystem.h" #include "devices/Faceshift.h" #include "devices/OculusManager.h" #include "ui/TextRenderer.h" @@ -451,12 +450,12 @@ void MyAvatar::renderDebugBodyPoints() { } // virtual -void MyAvatar::render(const glm::vec3& cameraPosition, bool forShadowMapOrMirror) { +void MyAvatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { // don't render if we've been asked to disable local rendering if (!_shouldRender) { return; // exit early } - Avatar::render(cameraPosition, forShadowMapOrMirror); + Avatar::render(cameraPosition, renderMode); } void MyAvatar::renderHeadMouse() const { @@ -551,6 +550,14 @@ void MyAvatar::loadData(QSettings* settings) { settings->endGroup(); } +int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) { + qDebug() << "Error: ignoring update packet for MyAvatar" + << " packetLength = " << packet.size() + << " offset = " << offset; + // this packet is just bad, so we pretend that we unpacked it ALL + return packet.size() - offset; +} + void MyAvatar::sendKillAvatar() { QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar); NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer); @@ -631,20 +638,20 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _billboardValid = false; } -void MyAvatar::renderBody(bool forceRenderHead) { +void MyAvatar::renderBody(RenderMode renderMode) { if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { return; // wait until both models are loaded } // Render the body's voxels and head - _skeletonModel.render(1.0f); + _skeletonModel.render(1.0f, renderMode == SHADOW_RENDER_MODE); // Render head so long as the camera isn't inside it const float RENDER_HEAD_CUTOFF_DISTANCE = 0.40f; Camera* myCamera = Application::getInstance()->getCamera(); - if (forceRenderHead || (glm::length(myCamera->getPosition() - getHead()->calculateAverageEyePosition()) > + if (renderMode != NORMAL_RENDER_MODE || (glm::length(myCamera->getPosition() - getHead()->calculateAverageEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale)) { - getHead()->render(1.0f); + getHead()->render(1.0f, renderMode == SHADOW_RENDER_MODE); } getHand()->render(true); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d958103fa6..cbb625aa2f 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -35,8 +35,8 @@ public: void simulate(float deltaTime); void updateFromGyros(float deltaTime); - void render(const glm::vec3& cameraPosition, bool forShadowMapOrMirror = false); - void renderBody(bool forceRenderHead); + void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE); + void renderBody(RenderMode renderMode); void renderDebugBodyPoints(); void renderHeadMouse() const; @@ -71,6 +71,8 @@ public: void jump() { _shouldJump = true; }; bool isMyAvatar() { return true; } + + virtual int parseDataAtOffset(const QByteArray& packet, int offset); static void sendKillAvatar(); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 7173cb0b84..9e4740df15 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -62,17 +62,6 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { } } -bool SkeletonModel::render(float alpha) { - - if (_jointStates.isEmpty()) { - return false; - } - - Model::render(alpha); - - return true; -} - void SkeletonModel::getHandShapes(int jointIndex, QVector& shapes) const { if (jointIndex == -1) { return; diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 0bcbcef2ea..514b5daf1c 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -23,7 +23,6 @@ public: SkeletonModel(Avatar* owningAvatar); void simulate(float deltaTime, bool fullUpdate = true); - bool render(float alpha); /// \param jointIndex index of hand joint /// \param shapes[out] list in which is stored pointers to hand shapes diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 16b5c167d9..690b19ee5e 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -43,11 +43,14 @@ Model::~Model() { ProgramObject Model::_program; ProgramObject Model::_normalMapProgram; +ProgramObject Model::_shadowProgram; ProgramObject Model::_skinProgram; ProgramObject Model::_skinNormalMapProgram; +ProgramObject Model::_skinShadowProgram; int Model::_normalMapTangentLocation; Model::SkinLocations Model::_skinLocations; Model::SkinLocations Model::_skinNormalMapLocations; +Model::SkinLocations Model::_skinShadowLocations; void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) { program.bind(); @@ -93,6 +96,11 @@ void Model::init() { _normalMapTangentLocation = _normalMapProgram.attributeLocation("tangent"); _normalMapProgram.release(); + _shadowProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/model_shadow.vert"); + _shadowProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/model_shadow.frag"); + _shadowProgram.link(); + _skinProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/skin_model.vert"); _skinProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() @@ -108,6 +116,14 @@ void Model::init() { _skinNormalMapProgram.link(); initSkinProgram(_skinNormalMapProgram, _skinNormalMapLocations); + + _skinShadowProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/skin_model_shadow.vert"); + _skinShadowProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_shadow.frag"); + _skinShadowProgram.link(); + + initSkinProgram(_skinShadowProgram, _skinShadowLocations); } } @@ -167,7 +183,7 @@ void Model::simulate(float deltaTime, bool fullUpdate) { simulate(deltaTime, fullUpdate, updateGeometry()); } -bool Model::render(float alpha) { +bool Model::render(float alpha, bool forShadowMap) { // render the attachments foreach (Model* attachment, _attachments) { attachment->render(alpha); @@ -198,13 +214,13 @@ bool Model::render(float alpha) { glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.5f * alpha); - renderMeshes(alpha, false); + renderMeshes(alpha, forShadowMap, false); glDisable(GL_ALPHA_TEST); // render translucent meshes afterwards, with back face culling - renderMeshes(alpha, true); + renderMeshes(alpha, forShadowMap, true); glDisable(GL_CULL_FACE); @@ -960,7 +976,7 @@ void Model::deleteGeometry() { } } -void Model::renderMeshes(float alpha, bool translucent) { +void Model::renderMeshes(float alpha, bool forShadowMap, bool translucent) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& networkMeshes = _geometry->getMeshes(); @@ -985,7 +1001,12 @@ void Model::renderMeshes(float alpha, bool translucent) { ProgramObject* program = &_program; ProgramObject* skinProgram = &_skinProgram; SkinLocations* skinLocations = &_skinLocations; - if (!mesh.tangents.isEmpty()) { + if (forShadowMap) { + program = &_shadowProgram; + skinProgram = &_skinShadowProgram; + skinLocations = &_skinShadowLocations; + + } else if (!mesh.tangents.isEmpty()) { program = &_normalMapProgram; skinProgram = &_skinNormalMapProgram; skinLocations = &_skinNormalMapLocations; @@ -1018,7 +1039,7 @@ void Model::renderMeshes(float alpha, bool translucent) { } if (mesh.blendshapes.isEmpty()) { - if (!mesh.tangents.isEmpty()) { + if (!(mesh.tangents.isEmpty() || forShadowMap)) { activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3); activeProgram->enableAttributeArray(tangentLocation); } @@ -1028,7 +1049,7 @@ void Model::renderMeshes(float alpha, bool translucent) { (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3))); } else { - if (!mesh.tangents.isEmpty()) { + if (!(mesh.tangents.isEmpty() || forShadowMap)) { activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, 0, 3); activeProgram->enableAttributeArray(tangentLocation); } @@ -1057,31 +1078,33 @@ void Model::renderMeshes(float alpha, bool translucent) { continue; } // apply material properties - glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha); - glm::vec4 specular = glm::vec4(part.specularColor, alpha); - glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse); - glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse); - glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular); - glMaterialf(GL_FRONT, GL_SHININESS, part.shininess); - - Texture* diffuseMap = networkPart.diffuseTexture.data(); - if (mesh.isEye) { - if (diffuseMap) { + if (forShadowMap) { + glBindTexture(GL_TEXTURE_2D, 0); + + } else { + glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha); + glm::vec4 specular = glm::vec4(part.specularColor, alpha); + glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse); + glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse); + glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular); + glMaterialf(GL_FRONT, GL_SHININESS, part.shininess); + + Texture* diffuseMap = networkPart.diffuseTexture.data(); + if (mesh.isEye && diffuseMap) { diffuseMap = (_dilatedTextures[i][j] = static_cast(diffuseMap)->getDilatedTexture(_pupilDilation)).data(); } + glBindTexture(GL_TEXTURE_2D, !diffuseMap ? + Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID()); + + if (!mesh.tangents.isEmpty()) { + glActiveTexture(GL_TEXTURE1); + Texture* normalMap = networkPart.normalTexture.data(); + glBindTexture(GL_TEXTURE_2D, !normalMap ? + Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID()); + glActiveTexture(GL_TEXTURE0); + } } - glBindTexture(GL_TEXTURE_2D, !diffuseMap ? - Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID()); - - if (!mesh.tangents.isEmpty()) { - glActiveTexture(GL_TEXTURE1); - Texture* normalMap = networkPart.normalTexture.data(); - glBindTexture(GL_TEXTURE_2D, !normalMap ? - Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID()); - glActiveTexture(GL_TEXTURE0); - } - glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset); offset += part.quadIndices.size() * sizeof(int); glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(), @@ -1096,7 +1119,7 @@ void Model::renderMeshes(float alpha, bool translucent) { glDisableClientState(GL_TEXTURE_COORD_ARRAY); } - if (!mesh.tangents.isEmpty()) { + if (!(mesh.tangents.isEmpty() || forShadowMap)) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE0); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index f08a6b9fc2..b4f71f14d3 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -58,7 +58,7 @@ public: void createCollisionShapes(); void updateShapePositions(); void simulate(float deltaTime, bool fullUpdate = true); - bool render(float alpha); + bool render(float alpha = 1.0f, bool forShadowMap = false); /// Sets the URL of the model to render. /// \param fallback the URL of a fallback model to render if the requested model fails to load @@ -261,7 +261,7 @@ private: void applyNextGeometry(); void deleteGeometry(); - void renderMeshes(float alpha, bool translucent); + void renderMeshes(float alpha, bool forShadowMap, bool translucent); QSharedPointer _baseGeometry; ///< reference required to prevent collection of base QSharedPointer _nextBaseGeometry; @@ -283,8 +283,10 @@ private: static ProgramObject _program; static ProgramObject _normalMapProgram; + static ProgramObject _shadowProgram; static ProgramObject _skinProgram; static ProgramObject _skinNormalMapProgram; + static ProgramObject _skinShadowProgram; static int _normalMapTangentLocation; @@ -298,6 +300,7 @@ private: static SkinLocations _skinLocations; static SkinLocations _skinNormalMapLocations; + static SkinLocations _skinShadowLocations; static void initSkinProgram(ProgramObject& program, SkinLocations& locations); static QVector createJointStates(const FBXGeometry& geometry); diff --git a/interface/src/scripting/AudioDeviceScriptingInterface.cpp b/interface/src/scripting/AudioDeviceScriptingInterface.cpp new file mode 100644 index 0000000000..a184e8a2f6 --- /dev/null +++ b/interface/src/scripting/AudioDeviceScriptingInterface.cpp @@ -0,0 +1,69 @@ +// +// AudioDeviceScriptingInterface.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 3/23/14 +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#include "Application.h" +#include "AudioDeviceScriptingInterface.h" + + +AudioDeviceScriptingInterface* AudioDeviceScriptingInterface::getInstance() { + static AudioDeviceScriptingInterface sharedInstance; + return &sharedInstance; +} + +bool AudioDeviceScriptingInterface::setInputDevice(const QString& deviceName) { + bool result; + QMetaObject::invokeMethod(Application::getInstance()->getAudio(), "switchInputToAudioDevice", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, deviceName)); + + return result; +} + +bool AudioDeviceScriptingInterface::setOutputDevice(const QString& deviceName) { + bool result; + QMetaObject::invokeMethod(Application::getInstance()->getAudio(), "switchOutputToAudioDevice", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, deviceName)); + + return result; +} + +QString AudioDeviceScriptingInterface::getInputDevice() { + return Application::getInstance()->getAudio()->getDeviceName(QAudio::AudioInput); +} + +QString AudioDeviceScriptingInterface::getOutputDevice() { + return Application::getInstance()->getAudio()->getDeviceName(QAudio::AudioOutput); +} + +QString AudioDeviceScriptingInterface::getDefaultInputDevice() { + return Application::getInstance()->getAudio()->getDefaultDeviceName(QAudio::AudioInput); +} + +QString AudioDeviceScriptingInterface::getDefaultOutputDevice() { + return Application::getInstance()->getAudio()->getDefaultDeviceName(QAudio::AudioOutput); +} + +QVector AudioDeviceScriptingInterface::getInputDevices() { + return Application::getInstance()->getAudio()->getDeviceNames(QAudio::AudioInput); +} + +QVector AudioDeviceScriptingInterface::getOutputDevices() { + return Application::getInstance()->getAudio()->getDeviceNames(QAudio::AudioOutput); +} + + +float AudioDeviceScriptingInterface::getInputVolume() { + return Application::getInstance()->getAudio()->getInputVolume(); +} + +void AudioDeviceScriptingInterface::setInputVolume(float volume) { + Application::getInstance()->getAudio()->setInputVolume(volume); +} diff --git a/interface/src/scripting/AudioDeviceScriptingInterface.h b/interface/src/scripting/AudioDeviceScriptingInterface.h new file mode 100644 index 0000000000..adc86cb15c --- /dev/null +++ b/interface/src/scripting/AudioDeviceScriptingInterface.h @@ -0,0 +1,41 @@ +// +// AudioDeviceScriptingInterface.h +// hifi +// +// Created by Brad Hefta-Gaub on 3/22/14 +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__AudioDeviceScriptingInterface__ +#define __hifi__AudioDeviceScriptingInterface__ + +#include +#include +#include + +#include "Application.h" + +class AudioDeviceScriptingInterface : public QObject { + Q_OBJECT + AudioDeviceScriptingInterface() { }; +public: + static AudioDeviceScriptingInterface* getInstance(); + +public slots: + bool setInputDevice(const QString& deviceName); + bool setOutputDevice(const QString& deviceName); + + QString getInputDevice(); + QString getOutputDevice(); + + QString getDefaultInputDevice(); + QString getDefaultOutputDevice(); + + QVector getInputDevices(); + QVector getOutputDevices(); + + float getInputVolume(); + void setInputVolume(float volume); +}; + +#endif /* defined(__hifi__AudioDeviceScriptingInterface__) */ diff --git a/interface/src/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp similarity index 100% rename from interface/src/ClipboardScriptingInterface.cpp rename to interface/src/scripting/ClipboardScriptingInterface.cpp diff --git a/interface/src/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h similarity index 100% rename from interface/src/ClipboardScriptingInterface.h rename to interface/src/scripting/ClipboardScriptingInterface.h diff --git a/interface/src/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp similarity index 100% rename from interface/src/ControllerScriptingInterface.cpp rename to interface/src/scripting/ControllerScriptingInterface.cpp diff --git a/interface/src/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h similarity index 100% rename from interface/src/ControllerScriptingInterface.h rename to interface/src/scripting/ControllerScriptingInterface.h diff --git a/interface/src/MenuScriptingInterface.cpp b/interface/src/scripting/MenuScriptingInterface.cpp similarity index 100% rename from interface/src/MenuScriptingInterface.cpp rename to interface/src/scripting/MenuScriptingInterface.cpp diff --git a/interface/src/MenuScriptingInterface.h b/interface/src/scripting/MenuScriptingInterface.h similarity index 100% rename from interface/src/MenuScriptingInterface.h rename to interface/src/scripting/MenuScriptingInterface.h diff --git a/interface/src/scripting/SettingsScriptingInterface.cpp b/interface/src/scripting/SettingsScriptingInterface.cpp new file mode 100644 index 0000000000..2a788c2776 --- /dev/null +++ b/interface/src/scripting/SettingsScriptingInterface.cpp @@ -0,0 +1,36 @@ +// +// SettingsScriptingInterface.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 2/25/14 +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#include "Application.h" +#include "SettingsScriptingInterface.h" + + +SettingsScriptingInterface* SettingsScriptingInterface::getInstance() { + static SettingsScriptingInterface sharedInstance; + return &sharedInstance; +} + +QVariant SettingsScriptingInterface::getValue(const QString& setting) { + QSettings* settings = Application::getInstance()->lockSettings(); + QVariant value = settings->value(setting); + Application::getInstance()->unlockSettings(); + return value; +} + +QVariant SettingsScriptingInterface::getValue(const QString& setting, const QVariant& defaultValue) { + QSettings* settings = Application::getInstance()->lockSettings(); + QVariant value = settings->value(setting, defaultValue); + Application::getInstance()->unlockSettings(); + return value; +} + +void SettingsScriptingInterface::setValue(const QString& setting, const QVariant& value) { + QSettings* settings = Application::getInstance()->lockSettings(); + settings->setValue(setting, value); + Application::getInstance()->unlockSettings(); +} diff --git a/interface/src/scripting/SettingsScriptingInterface.h b/interface/src/scripting/SettingsScriptingInterface.h new file mode 100644 index 0000000000..12bda2173f --- /dev/null +++ b/interface/src/scripting/SettingsScriptingInterface.h @@ -0,0 +1,30 @@ +// +// SettingsScriptingInterface.h +// hifi +// +// Created by Brad Hefta-Gaub on 3/22/14 +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__SettingsScriptingInterface__ +#define __hifi__SettingsScriptingInterface__ + +#include +#include +#include + +#include "Application.h" + +class SettingsScriptingInterface : public QObject { + Q_OBJECT + SettingsScriptingInterface() { }; +public: + static SettingsScriptingInterface* getInstance(); + +public slots: + QVariant getValue(const QString& setting); + QVariant getValue(const QString& setting, const QVariant& defaultValue); + void setValue(const QString& setting, const QVariant& value); +}; + +#endif /* defined(__hifi__SettingsScriptingInterface__) */ diff --git a/interface/src/BandwidthMeter.cpp b/interface/src/ui/BandwidthMeter.cpp similarity index 100% rename from interface/src/BandwidthMeter.cpp rename to interface/src/ui/BandwidthMeter.cpp diff --git a/interface/src/BandwidthMeter.h b/interface/src/ui/BandwidthMeter.h similarity index 100% rename from interface/src/BandwidthMeter.h rename to interface/src/ui/BandwidthMeter.h diff --git a/interface/src/ImportDialog.cpp b/interface/src/ui/ImportDialog.cpp similarity index 100% rename from interface/src/ImportDialog.cpp rename to interface/src/ui/ImportDialog.cpp diff --git a/interface/src/ImportDialog.h b/interface/src/ui/ImportDialog.h similarity index 100% rename from interface/src/ImportDialog.h rename to interface/src/ui/ImportDialog.h diff --git a/interface/src/InfoView.cpp b/interface/src/ui/InfoView.cpp similarity index 89% rename from interface/src/InfoView.cpp rename to interface/src/ui/InfoView.cpp index fbf63666d8..8ed4da254c 100644 --- a/interface/src/InfoView.cpp +++ b/interface/src/ui/InfoView.cpp @@ -38,11 +38,12 @@ void InfoView::forcedShow() { } bool InfoView::shouldShow() { + bool shouldShow = false; if (_forced) { return true; } - QSettings* settings = Application::getInstance()->getSettings(); + QSettings* settings = Application::getInstance()->lockSettings(); QString lastVersion = settings->value(SETTINGS_VERSION_KEY).toString(); @@ -51,10 +52,12 @@ bool InfoView::shouldShow() { if (version != QString::null && (lastVersion == QString::null || lastVersion != version)) { settings->setValue(SETTINGS_VERSION_KEY, version); - return true; + shouldShow = true; } else { - return false; - } + shouldShow = false; + } + Application::getInstance()->unlockSettings(); + return shouldShow; } void InfoView::loaded(bool ok) { diff --git a/interface/src/InfoView.h b/interface/src/ui/InfoView.h similarity index 100% rename from interface/src/InfoView.h rename to interface/src/ui/InfoView.h diff --git a/interface/src/ModelBrowser.cpp b/interface/src/ui/ModelBrowser.cpp similarity index 100% rename from interface/src/ModelBrowser.cpp rename to interface/src/ui/ModelBrowser.cpp diff --git a/interface/src/ModelBrowser.h b/interface/src/ui/ModelBrowser.h similarity index 100% rename from interface/src/ModelBrowser.h rename to interface/src/ui/ModelBrowser.h diff --git a/interface/src/Oscilloscope.cpp b/interface/src/ui/Oscilloscope.cpp similarity index 100% rename from interface/src/Oscilloscope.cpp rename to interface/src/ui/Oscilloscope.cpp diff --git a/interface/src/Oscilloscope.h b/interface/src/ui/Oscilloscope.h similarity index 100% rename from interface/src/Oscilloscope.h rename to interface/src/ui/Oscilloscope.h diff --git a/interface/src/ui/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp similarity index 98% rename from interface/src/ui/Base3DOverlay.cpp rename to interface/src/ui/overlays/Base3DOverlay.cpp index 67e7ea25f2..bcd2ca1cd2 100644 --- a/interface/src/ui/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -12,7 +12,6 @@ #include #include "Base3DOverlay.h" -#include "TextRenderer.h" const glm::vec3 DEFAULT_POSITION = glm::vec3(0.0f, 0.0f, 0.0f); const float DEFAULT_LINE_WIDTH = 1.0f; diff --git a/interface/src/ui/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h similarity index 100% rename from interface/src/ui/Base3DOverlay.h rename to interface/src/ui/overlays/Base3DOverlay.h diff --git a/interface/src/ui/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp similarity index 100% rename from interface/src/ui/Cube3DOverlay.cpp rename to interface/src/ui/overlays/Cube3DOverlay.cpp diff --git a/interface/src/ui/Cube3DOverlay.h b/interface/src/ui/overlays/Cube3DOverlay.h similarity index 100% rename from interface/src/ui/Cube3DOverlay.h rename to interface/src/ui/overlays/Cube3DOverlay.h diff --git a/interface/src/ui/ImageOverlay.cpp b/interface/src/ui/overlays/ImageOverlay.cpp similarity index 100% rename from interface/src/ui/ImageOverlay.cpp rename to interface/src/ui/overlays/ImageOverlay.cpp diff --git a/interface/src/ui/ImageOverlay.h b/interface/src/ui/overlays/ImageOverlay.h similarity index 100% rename from interface/src/ui/ImageOverlay.h rename to interface/src/ui/overlays/ImageOverlay.h diff --git a/interface/src/ui/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp similarity index 100% rename from interface/src/ui/Line3DOverlay.cpp rename to interface/src/ui/overlays/Line3DOverlay.cpp diff --git a/interface/src/ui/Line3DOverlay.h b/interface/src/ui/overlays/Line3DOverlay.h similarity index 100% rename from interface/src/ui/Line3DOverlay.h rename to interface/src/ui/overlays/Line3DOverlay.h diff --git a/interface/src/ui/LocalVoxelsOverlay.cpp b/interface/src/ui/overlays/LocalVoxelsOverlay.cpp similarity index 98% rename from interface/src/ui/LocalVoxelsOverlay.cpp rename to interface/src/ui/overlays/LocalVoxelsOverlay.cpp index 7eaf9ed5c5..460f4eadb6 100644 --- a/interface/src/ui/LocalVoxelsOverlay.cpp +++ b/interface/src/ui/overlays/LocalVoxelsOverlay.cpp @@ -12,10 +12,10 @@ #include #include -#include #include #include "LocalVoxelsOverlay.h" +#include "voxels/VoxelSystem.h" QMap LocalVoxelsOverlay::_voxelSystemMap; diff --git a/interface/src/ui/LocalVoxelsOverlay.h b/interface/src/ui/overlays/LocalVoxelsOverlay.h similarity index 100% rename from interface/src/ui/LocalVoxelsOverlay.h rename to interface/src/ui/overlays/LocalVoxelsOverlay.h diff --git a/interface/src/ui/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp similarity index 100% rename from interface/src/ui/Overlay.cpp rename to interface/src/ui/overlays/Overlay.cpp diff --git a/interface/src/ui/Overlay.h b/interface/src/ui/overlays/Overlay.h similarity index 100% rename from interface/src/ui/Overlay.h rename to interface/src/ui/overlays/Overlay.h diff --git a/interface/src/ui/Overlay2D.cpp b/interface/src/ui/overlays/Overlay2D.cpp similarity index 100% rename from interface/src/ui/Overlay2D.cpp rename to interface/src/ui/overlays/Overlay2D.cpp diff --git a/interface/src/ui/Overlay2D.h b/interface/src/ui/overlays/Overlay2D.h similarity index 100% rename from interface/src/ui/Overlay2D.h rename to interface/src/ui/overlays/Overlay2D.h diff --git a/interface/src/ui/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp similarity index 100% rename from interface/src/ui/Overlays.cpp rename to interface/src/ui/overlays/Overlays.cpp diff --git a/interface/src/ui/Overlays.h b/interface/src/ui/overlays/Overlays.h similarity index 100% rename from interface/src/ui/Overlays.h rename to interface/src/ui/overlays/Overlays.h diff --git a/interface/src/ui/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp similarity index 100% rename from interface/src/ui/Sphere3DOverlay.cpp rename to interface/src/ui/overlays/Sphere3DOverlay.cpp diff --git a/interface/src/ui/Sphere3DOverlay.h b/interface/src/ui/overlays/Sphere3DOverlay.h similarity index 100% rename from interface/src/ui/Sphere3DOverlay.h rename to interface/src/ui/overlays/Sphere3DOverlay.h diff --git a/interface/src/ui/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp similarity index 98% rename from interface/src/ui/TextOverlay.cpp rename to interface/src/ui/overlays/TextOverlay.cpp index edaec6849a..1a6edb3ea2 100644 --- a/interface/src/ui/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -12,7 +12,7 @@ #include #include "TextOverlay.h" -#include "TextRenderer.h" +#include "ui/TextRenderer.h" TextOverlay::TextOverlay() : _leftMargin(DEFAULT_MARGIN), diff --git a/interface/src/ui/TextOverlay.h b/interface/src/ui/overlays/TextOverlay.h similarity index 100% rename from interface/src/ui/TextOverlay.h rename to interface/src/ui/overlays/TextOverlay.h diff --git a/interface/src/ui/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp similarity index 100% rename from interface/src/ui/Volume3DOverlay.cpp rename to interface/src/ui/overlays/Volume3DOverlay.cpp diff --git a/interface/src/ui/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h similarity index 100% rename from interface/src/ui/Volume3DOverlay.h rename to interface/src/ui/overlays/Volume3DOverlay.h diff --git a/interface/src/PrimitiveRenderer.cpp b/interface/src/voxels/PrimitiveRenderer.cpp similarity index 100% rename from interface/src/PrimitiveRenderer.cpp rename to interface/src/voxels/PrimitiveRenderer.cpp diff --git a/interface/src/PrimitiveRenderer.h b/interface/src/voxels/PrimitiveRenderer.h similarity index 100% rename from interface/src/PrimitiveRenderer.h rename to interface/src/voxels/PrimitiveRenderer.h diff --git a/interface/src/VoxelFade.cpp b/interface/src/voxels/VoxelFade.cpp similarity index 100% rename from interface/src/VoxelFade.cpp rename to interface/src/voxels/VoxelFade.cpp diff --git a/interface/src/VoxelFade.h b/interface/src/voxels/VoxelFade.h similarity index 100% rename from interface/src/VoxelFade.h rename to interface/src/voxels/VoxelFade.h diff --git a/interface/src/VoxelHideShowThread.cpp b/interface/src/voxels/VoxelHideShowThread.cpp similarity index 100% rename from interface/src/VoxelHideShowThread.cpp rename to interface/src/voxels/VoxelHideShowThread.cpp diff --git a/interface/src/VoxelHideShowThread.h b/interface/src/voxels/VoxelHideShowThread.h similarity index 100% rename from interface/src/VoxelHideShowThread.h rename to interface/src/voxels/VoxelHideShowThread.h diff --git a/interface/src/VoxelImporter.cpp b/interface/src/voxels/VoxelImporter.cpp similarity index 97% rename from interface/src/VoxelImporter.cpp rename to interface/src/voxels/VoxelImporter.cpp index 9d8b8ad811..d3c1b259ae 100644 --- a/interface/src/VoxelImporter.cpp +++ b/interface/src/voxels/VoxelImporter.cpp @@ -6,13 +6,17 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // -#include -#include -#include +// include this before QGLWidget, which includes an earlier version of OpenGL +#include "InterfaceConfig.h" #include #include +#include +#include + +#include "voxels/VoxelImporter.h" + const QString SETTINGS_GROUP_NAME = "VoxelImport"; const QString IMPORT_DIALOG_SETTINGS_KEY = "ImportDialogSettings"; diff --git a/interface/src/VoxelImporter.h b/interface/src/voxels/VoxelImporter.h similarity index 93% rename from interface/src/VoxelImporter.h rename to interface/src/voxels/VoxelImporter.h index e77abaf18d..9ebfc2eef2 100644 --- a/interface/src/VoxelImporter.h +++ b/interface/src/voxels/VoxelImporter.h @@ -9,12 +9,12 @@ #ifndef __hifi__VoxelImporter__ #define __hifi__VoxelImporter__ -#include -#include - #include #include +#include "ui/ImportDialog.h" +#include "voxels/VoxelSystem.h" + class ImportTask; class VoxelImporter : public QObject { diff --git a/interface/src/VoxelPacketProcessor.cpp b/interface/src/voxels/VoxelPacketProcessor.cpp similarity index 100% rename from interface/src/VoxelPacketProcessor.cpp rename to interface/src/voxels/VoxelPacketProcessor.cpp diff --git a/interface/src/VoxelPacketProcessor.h b/interface/src/voxels/VoxelPacketProcessor.h similarity index 100% rename from interface/src/VoxelPacketProcessor.h rename to interface/src/voxels/VoxelPacketProcessor.h diff --git a/interface/src/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp similarity index 100% rename from interface/src/VoxelSystem.cpp rename to interface/src/voxels/VoxelSystem.cpp diff --git a/interface/src/VoxelSystem.h b/interface/src/voxels/VoxelSystem.h similarity index 100% rename from interface/src/VoxelSystem.h rename to interface/src/voxels/VoxelSystem.h diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index cf35b6a20f..4732cc77d1 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -66,12 +66,21 @@ void AudioRingBuffer::updateAverageLoudnessForBoundarySamples(int numSamples) { nextLoudness /= numSamples; nextLoudness /= MAX_SAMPLE_VALUE; - + const int TRAILING_AVERAGE_FRAMES = 100; const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; - const float PREVIOUS_FRAMES_RATIO = 1 - CURRENT_FRAME_RATIO; + const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; + const float LOUDNESS_EPSILON = 0.01f; - _averageLoudness = (_averageLoudness * PREVIOUS_FRAMES_RATIO) + (CURRENT_FRAME_RATIO * nextLoudness); + if (nextLoudness >= _averageLoudness) { + _averageLoudness = nextLoudness; + } else { + _averageLoudness = (_averageLoudness * PREVIOUS_FRAMES_RATIO) + (CURRENT_FRAME_RATIO * nextLoudness); + + if (_averageLoudness < LOUDNESS_EPSILON) { + _averageLoudness = 0; + } + } } qint64 AudioRingBuffer::readSamples(int16_t* destination, qint64 maxSamples) { diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 8fb3d64e7d..d1729ddfef 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -16,12 +17,6 @@ #include "PositionalAudioRingBuffer.h" -#ifdef _WIN32 -int isnan(double value) { return _isnan(value); } -#else -int isnan(double value) { return std::isnan(value); } -#endif - PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type) : AudioRingBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL), _type(type), @@ -69,7 +64,7 @@ int PositionalAudioRingBuffer::parsePositionalData(const QByteArray& positionalB packetStream.readRawData(reinterpret_cast(&_orientation), sizeof(_orientation)); // if this node sent us a NaN for first float in orientation then don't consider this good audio and bail - if (isnan(_orientation.x)) { + if (glm::isnan(_orientation.x)) { reset(); return 0; } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 4e57e311eb..bb0fcd27e6 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -24,6 +25,8 @@ #include "AvatarData.h" +quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND; + using namespace std; QNetworkAccessManager* AvatarData::networkAccessManager = NULL; @@ -42,7 +45,8 @@ AvatarData::AvatarData() : _displayNameBoundingRect(), _displayNameTargetAlpha(0.0f), _displayNameAlpha(0.0f), - _billboard() + _billboard(), + _errorLogExpiry(0) { } @@ -174,9 +178,16 @@ QByteArray AvatarData::toByteArray() { return avatarDataByteArray.left(destinationBuffer - startPosition); } +bool AvatarData::shouldLogError(const quint64& now) { + if (now > _errorLogExpiry) { + _errorLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY; + return true; + } + return false; +} + // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { - // lazily allocate memory for HeadData in case we're not an Avatar instance if (!_headData) { _headData = new HeadData(this); @@ -189,102 +200,289 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { const unsigned char* startPosition = reinterpret_cast(packet.data()) + offset; const unsigned char* sourceBuffer = startPosition; + quint64 now = usecTimestampNow(); + + // The absolute minimum size of the update data is as follows: + // 50 bytes of "plain old data" { + // position = 12 bytes + // bodyYaw = 2 (compressed float) + // bodyPitch = 2 (compressed float) + // bodyRoll = 2 (compressed float) + // targetScale = 2 (compressed float) + // headYaw = 2 (compressed float) + // headPitch = 2 (compressed float) + // headRoll = 2 (compressed float) + // leanSideways = 4 + // leanForward = 4 + // lookAt = 12 + // audioLoudness = 4 + // } + // + 1 byte for messageSize (0) + // + 1 byte for pupilSize + // + 1 byte for numJoints (0) + // = 53 bytes + int minPossibleSize = 53; - // Body world position - memcpy(&_position, sourceBuffer, sizeof(float) * 3); - sourceBuffer += sizeof(float) * 3; - - // Body rotation (NOTE: This needs to become a quaternion to save two bytes) - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyYaw); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyPitch); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyRoll); - - // Body scale - sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale); - - // Head rotation (NOTE: This needs to become a quaternion to save two bytes) - float headYaw, headPitch, headRoll; - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll); - _headData->setYaw(headYaw); - _headData->setPitch(headPitch); - _headData->setRoll(headRoll); - - // Head position relative to pelvis - memcpy(&_headData->_leanSideways, sourceBuffer, sizeof(_headData->_leanSideways)); - sourceBuffer += sizeof(float); - memcpy(&_headData->_leanForward, sourceBuffer, sizeof(_headData->_leanForward)); - sourceBuffer += sizeof(_headData->_leanForward); - - // Lookat Position - memcpy(&_headData->_lookAtPosition, sourceBuffer, sizeof(_headData->_lookAtPosition)); - sourceBuffer += sizeof(_headData->_lookAtPosition); - - // Instantaneous audio loudness (used to drive facial animation) - memcpy(&_headData->_audioLoudness, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - - // the rest is a chat message - int chatMessageSize = *sourceBuffer++; - _chatMessage = string((char*)sourceBuffer, chatMessageSize); - sourceBuffer += chatMessageSize * sizeof(char); - - // voxel sending features... - unsigned char bitItems = 0; - bitItems = (unsigned char)*sourceBuffer++; - - // key state, stored as a semi-nibble in the bitItems - _keyState = (KeyState)getSemiNibbleAt(bitItems,KEY_STATE_START_BIT); - - // hand state, stored as a semi-nibble in the bitItems - _handState = getSemiNibbleAt(bitItems,HAND_STATE_START_BIT); - - _headData->_isFaceshiftConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED); - - _isChatCirclingEnabled = oneAtBit(bitItems, IS_CHAT_CIRCLING_ENABLED); - - // If it is connected, pack up the data - if (_headData->_isFaceshiftConnected) { - memcpy(&_headData->_leftEyeBlink, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - - memcpy(&_headData->_rightEyeBlink, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - - memcpy(&_headData->_averageLoudness, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - - memcpy(&_headData->_browAudioLift, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - - _headData->_blendshapeCoefficients.resize(*sourceBuffer++); - memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, - _headData->_blendshapeCoefficients.size() * sizeof(float)); - sourceBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); + int maxAvailableSize = packet.size() - offset; + if (minPossibleSize > maxAvailableSize) { + if (shouldLogError(now)) { + qDebug() << "Malformed AvatarData packet at the start; " + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; + } + // this packet is malformed so we report all bytes as consumed + return maxAvailableSize; } + + { // Body world position, rotation, and scale + // position + glm::vec3 position; + memcpy(&position, sourceBuffer, sizeof(position)); + sourceBuffer += sizeof(position); + + if (glm::isnan(position.x) || glm::isnan(position.y) || glm::isnan(position.z)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::position; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _position = position; + + // rotation (NOTE: This needs to become a quaternion to save two bytes) + float yaw, pitch, roll; + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &yaw); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &pitch); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &roll); + if (glm::isnan(yaw) || glm::isnan(pitch) || glm::isnan(roll)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::yaw,pitch,roll; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _bodyYaw = yaw; + _bodyPitch = pitch; + _bodyRoll = roll; + + // scale + float scale; + sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, scale); + if (glm::isnan(scale)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::scale; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _targetScale = scale; + } // 20 bytes - // pupil dilation - sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f); + { // Head rotation + //(NOTE: This needs to become a quaternion to save two bytes) + float headYaw, headPitch, headRoll; + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll); + if (glm::isnan(headYaw) || glm::isnan(headPitch) || glm::isnan(headRoll)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::headYaw,headPitch,headRoll; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _headData->setYaw(headYaw); + _headData->setPitch(headPitch); + _headData->setRoll(headRoll); + } // 6 bytes + + // Head lean (relative to pelvis) + { + float leanSideways, leanForward; + memcpy(&leanSideways, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); + memcpy(&leanForward, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); + if (glm::isnan(leanSideways) || glm::isnan(leanForward)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::leanSideways,leanForward; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _headData->_leanSideways = leanSideways; + _headData->_leanForward = leanForward; + } // 8 bytes + + { // Lookat Position + glm::vec3 lookAt; + memcpy(&lookAt, sourceBuffer, sizeof(lookAt)); + sourceBuffer += sizeof(lookAt); + if (glm::isnan(lookAt.x) || glm::isnan(lookAt.y) || glm::isnan(lookAt.z)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::lookAt; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _headData->_lookAtPosition = lookAt; + } // 12 bytes + + { // AudioLoudness + // Instantaneous audio loudness (used to drive facial animation) + float audioLoudness; + memcpy(&audioLoudness, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); + if (glm::isnan(audioLoudness)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::audioLoudness; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _headData->_audioLoudness = audioLoudness; + } // 4 bytes + + // chat + int chatMessageSize = *sourceBuffer++; + minPossibleSize += chatMessageSize; + if (minPossibleSize > maxAvailableSize) { + if (shouldLogError(now)) { + qDebug() << "Malformed AvatarData packet before ChatMessage;" + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; + } + return maxAvailableSize; + } + { // chat payload + _chatMessage = string((char*)sourceBuffer, chatMessageSize); + sourceBuffer += chatMessageSize * sizeof(char); + } // 1 + chatMessageSize bytes + + { // bitFlags and face data + unsigned char bitItems = 0; + bitItems = (unsigned char)*sourceBuffer++; + + // key state, stored as a semi-nibble in the bitItems + _keyState = (KeyState)getSemiNibbleAt(bitItems,KEY_STATE_START_BIT); + + // hand state, stored as a semi-nibble in the bitItems + _handState = getSemiNibbleAt(bitItems,HAND_STATE_START_BIT); + + _headData->_isFaceshiftConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED); + _isChatCirclingEnabled = oneAtBit(bitItems, IS_CHAT_CIRCLING_ENABLED); + + if (_headData->_isFaceshiftConnected) { + float leftEyeBlink, rightEyeBlink, averageLoudness, browAudioLift; + minPossibleSize += sizeof(leftEyeBlink) + sizeof(rightEyeBlink) + sizeof(averageLoudness) + sizeof(browAudioLift); + minPossibleSize++; // one byte for blendDataSize + if (minPossibleSize > maxAvailableSize) { + if (shouldLogError(now)) { + qDebug() << "Malformed AvatarData packet after BitItems;" + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; + } + return maxAvailableSize; + } + // unpack face data + memcpy(&leftEyeBlink, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); + + memcpy(&rightEyeBlink, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); + + memcpy(&averageLoudness, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); + + memcpy(&browAudioLift, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); + + if (glm::isnan(leftEyeBlink) || glm::isnan(rightEyeBlink) + || glm::isnan(averageLoudness) || glm::isnan(browAudioLift)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::faceData; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _headData->_leftEyeBlink = leftEyeBlink; + _headData->_rightEyeBlink = rightEyeBlink; + _headData->_averageLoudness = averageLoudness; + _headData->_browAudioLift = browAudioLift; + + int numCoefficients = (int)(*sourceBuffer++); + int blendDataSize = numCoefficients * sizeof(float); + minPossibleSize += blendDataSize; + if (minPossibleSize > maxAvailableSize) { + if (shouldLogError(now)) { + qDebug() << "Malformed AvatarData packet after Blendshapes;" + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; + } + return maxAvailableSize; + } + + _headData->_blendshapeCoefficients.resize(numCoefficients); + memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, blendDataSize); + sourceBuffer += numCoefficients * sizeof(float); + + //bitItemsDataSize = 4 * sizeof(float) + 1 + blendDataSize; + } + } // 1 + bitItemsDataSize bytes + + { // pupil dilation + sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f); + } // 1 byte // joint data - int jointCount = *sourceBuffer++; - _jointData.resize(jointCount); - unsigned char validity = 0; - int validityBit = 0; - for (int i = 0; i < jointCount; i++) { - if (validityBit == 0) { - validity = *sourceBuffer++; - } - _jointData[i].valid = (bool)(validity & (1 << validityBit)); - validityBit = (validityBit + 1) % BITS_IN_BYTE; + int numJoints = *sourceBuffer++; + int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); + minPossibleSize += bytesOfValidity; + if (minPossibleSize > maxAvailableSize) { + if (shouldLogError(now)) { + qDebug() << "Malformed AvatarData packet after JointValidityBits;" + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; + } + return maxAvailableSize; } - for (int i = 0; i < jointCount; i++) { - JointData& data = _jointData[i]; - if (data.valid) { - sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation); + int numValidJoints = 0; + _jointData.resize(numJoints); + { // validity bits + unsigned char validity = 0; + int validityBit = 0; + for (int i = 0; i < numJoints; i++) { + if (validityBit == 0) { + validity = *sourceBuffer++; + } + bool valid = (bool)(validity & (1 << validityBit)); + if (valid) { + ++numValidJoints; + } + _jointData[i].valid = valid; + validityBit = (validityBit + 1) % BITS_IN_BYTE; } } + // 1 + bytesOfValidity bytes + + // each joint rotation component is stored in two bytes (sizeof(uint16_t)) + int COMPONENTS_PER_QUATERNION = 4; + minPossibleSize += numValidJoints * COMPONENTS_PER_QUATERNION * sizeof(uint16_t); + if (minPossibleSize > maxAvailableSize) { + if (shouldLogError(now)) { + qDebug() << "Malformed AvatarData packet after JointData;" + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; + } + return maxAvailableSize; + } + + { // joint data + for (int i = 0; i < numJoints; i++) { + JointData& data = _jointData[i]; + if (data.valid) { + sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation); + } + } + } // numJoints * 8 bytes return sourceBuffer - startPosition; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index c7a93daef5..a89639d68d 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -33,7 +33,6 @@ typedef unsigned long long quint64; #include #include #include -#include #include #include #include @@ -106,6 +105,9 @@ public: QByteArray toByteArray(); + /// \return true if an error should be logged + bool shouldLogError(const quint64& now); + /// \param packet byte array of data /// \param offset number of bytes into packet where data starts /// \return number of bytes parsed @@ -256,6 +258,8 @@ protected: static QNetworkAccessManager* networkAccessManager; + quint64 _errorLogExpiry; ///< time in future when to log an error + private: // privatize the copy constructor and assignment operator so they cannot be called AvatarData(const AvatarData&); diff --git a/libraries/avatars/src/HandData.cpp b/libraries/avatars/src/HandData.cpp index 0f938b80b0..78ebf4b921 100644 --- a/libraries/avatars/src/HandData.cpp +++ b/libraries/avatars/src/HandData.cpp @@ -11,9 +11,10 @@ #include #include -#include "AvatarData.h" +#include "AvatarData.h" #include "HandData.h" + HandData::HandData(AvatarData* owningAvatar) : _owningAvatarData(owningAvatar) { diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 50ce72e0cd..8fc0a25dca 100755 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -180,7 +180,7 @@ void HTTPConnection::readHeaders() { QByteArray clength = _requestHeaders.value("Content-Length"); if (clength.isEmpty()) { - _parentManager->handleHTTPRequest(this, _requestUrl.path()); + _parentManager->handleHTTPRequest(this, _requestUrl); } else { _requestContent.resize(clength.toInt()); diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index a217555a78..d106b6df59 100755 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -15,15 +15,15 @@ #include "HTTPConnection.h" #include "HTTPManager.h" -bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QString& path) { - if (_requestHandler && _requestHandler->handleHTTPRequest(connection, path)) { +bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url) { + if (_requestHandler && _requestHandler->handleHTTPRequest(connection, url)) { // this request was handled by our _requestHandler object // so we don't need to attempt to do so in the document root return true; } // check to see if there is a file to serve from the document root for this path - QString subPath = path; + QString subPath = url.path(); // remove any slash at the beginning of the path if (subPath.startsWith('/')) { @@ -38,6 +38,10 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QString& p // this could be a directory with a trailing slash // send a redirect to the path with a slash so we can QString redirectLocation = '/' + subPath + '/'; + + if (!url.query().isEmpty()) { + redirectLocation += "?" + url.query(); + } QHash redirectHeader; redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8()); diff --git a/libraries/embedded-webserver/src/HTTPManager.h b/libraries/embedded-webserver/src/HTTPManager.h index e3b44e7cdc..a8f9d723fa 100755 --- a/libraries/embedded-webserver/src/HTTPManager.h +++ b/libraries/embedded-webserver/src/HTTPManager.h @@ -20,7 +20,7 @@ class HTTPConnection; class HTTPRequestHandler { public: /// Handles an HTTP request. - virtual bool handleHTTPRequest(HTTPConnection* connection, const QString& path) = 0; + virtual bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url) = 0; }; /// Handles HTTP connections @@ -30,7 +30,7 @@ public: /// Initializes the manager. HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = NULL, QObject* parent = 0); - bool handleHTTPRequest(HTTPConnection* connection, const QString& path); + bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); protected slots: /// Accepts all pending connections diff --git a/libraries/octree/src/ViewFrustum.cpp b/libraries/octree/src/ViewFrustum.cpp index da03aad697..fa6873b093 100644 --- a/libraries/octree/src/ViewFrustum.cpp +++ b/libraries/octree/src/ViewFrustum.cpp @@ -30,6 +30,9 @@ ViewFrustum::ViewFrustum() : _direction(IDENTITY_FRONT), _up(IDENTITY_UP), _right(IDENTITY_RIGHT), + _orthographic(false), + _width(1.0f), + _height(1.0f), _fieldOfView(0.0), _aspectRatio(1.0f), _nearClip(0.1f), @@ -62,6 +65,11 @@ void ViewFrustum::setOrientation(const glm::quat& orientationAsQuaternion) { // http://www.lighthouse3d.com/tutorials/view-frustum-culling/view-frustums-shape/ // void ViewFrustum::calculate() { + if (_orthographic) { + calculateOrthographic(); + return; + } + // compute the off-axis frustum parameters as we would for glFrustum float left, right, bottom, top, nearVal, farVal; glm::vec4 nearClipPlane, farClipPlane; @@ -133,6 +141,49 @@ void ViewFrustum::calculate() { _keyholeBoundingBox = AABox(corner,(_keyholeRadius * 2.0f)); } +void ViewFrustum::calculateOrthographic() { + float halfWidth = _width * 0.5f; + float halfHeight = _height * 0.5f; + + // find the corners of the view box in world space + glm::mat4 worldMatrix = glm::translate(_position) * glm::mat4(glm::mat3(_right, _up, -_direction)) * + glm::translate(_eyeOffsetPosition) * glm::mat4_cast(_eyeOffsetOrientation); + _farTopLeft = glm::vec3(worldMatrix * glm::vec4(-halfWidth, halfHeight, -_farClip, 1.0f)); + _farTopRight = glm::vec3(worldMatrix * glm::vec4(halfWidth, halfHeight, -_farClip, 1.0f)); + _farBottomLeft = glm::vec3(worldMatrix * glm::vec4(-halfWidth, -halfHeight, -_farClip, 1.0f)); + _farBottomRight = glm::vec3(worldMatrix * glm::vec4(halfWidth, -halfHeight, -_farClip, 1.0f)); + _nearTopLeft = glm::vec3(worldMatrix * glm::vec4(-halfWidth, halfHeight, -_nearClip, 1.0f)); + _nearTopRight = glm::vec3(worldMatrix * glm::vec4(halfWidth, halfHeight, -_nearClip, 1.0f)); + _nearBottomLeft = glm::vec3(worldMatrix * glm::vec4(-halfWidth, -halfHeight, -_nearClip, 1.0f)); + _nearBottomRight = glm::vec3(worldMatrix * glm::vec4(halfWidth, -halfHeight, -_nearClip, 1.0f)); + + // compute the offset position and axes in world space + _offsetPosition = glm::vec3(worldMatrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); + _offsetDirection = glm::vec3(worldMatrix * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f)); + _offsetUp = glm::vec3(worldMatrix * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)); + _offsetRight = glm::vec3(worldMatrix * glm::vec4(1.0f, 0.0f, 0.0f, 0.0f)); + + _planes[TOP_PLANE].set3Points(_nearTopRight, _nearTopLeft, _farTopLeft); + _planes[BOTTOM_PLANE].set3Points(_nearBottomLeft, _nearBottomRight, _farBottomRight); + _planes[LEFT_PLANE].set3Points(_nearBottomLeft, _farBottomLeft, _farTopLeft); + _planes[RIGHT_PLANE].set3Points(_farBottomRight, _nearBottomRight, _nearTopRight); + _planes[NEAR_PLANE].set3Points(_nearBottomRight, _nearBottomLeft, _nearTopLeft); + _planes[FAR_PLANE].set3Points(_farBottomLeft, _farBottomRight, _farTopRight); + + // Also calculate our projection matrix in case people want to project points... + // Projection matrix : Field of View, ratio, display range : near to far + glm::mat4 projection = glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight, _nearClip, _farClip); + glm::vec3 lookAt = _position + _direction; + glm::mat4 view = glm::lookAt(_position, lookAt, _up); + + // Our ModelViewProjection : multiplication of our 3 matrices (note: model is identity, so we can drop it) + _ourModelViewProjectionMatrix = projection * view; // Remember, matrix multiplication is the other way around + + // Set up our keyhole bounding box... + glm::vec3 corner = _position - _keyholeRadius; + _keyholeBoundingBox = AABox(corner, (_keyholeRadius * 2.0f)); +} + //enum { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE }; const char* ViewFrustum::debugPlaneName (int plane) const { switch (plane) { diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index a0b3a851aa..7a1c3b49ba 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -41,6 +41,9 @@ public: const glm::vec3& getRight() const { return _right; } // setters for lens attributes + void setOrthographic(bool orthographic) { _orthographic = orthographic; } + void setWidth(float width) { _width = width; } + void setHeight(float height) { _height = height; } void setFieldOfView(float f) { _fieldOfView = f; } void setAspectRatio(float a) { _aspectRatio = a; } void setNearClip(float n) { _nearClip = n; } @@ -50,6 +53,9 @@ public: void setEyeOffsetOrientation(const glm::quat& o) { _eyeOffsetOrientation = o; } // getters for lens attributes + bool isOrthographic() const { return _orthographic; } + float getWidth() const { return _width; } + float getHeight() const { return _height; } float getFieldOfView() const { return _fieldOfView; } float getAspectRatio() const { return _aspectRatio; } float getNearClip() const { return _nearClip; } @@ -114,6 +120,8 @@ private: ViewFrustum::location sphereInKeyhole(const glm::vec3& center, float radius) const; ViewFrustum::location boxInKeyhole(const AABox& box) const; + void calculateOrthographic(); + // camera location/orientation attributes glm::vec3 _position; // the position in TREE_SCALE glm::vec3 _positionVoxelScale; // the position in voxel scale @@ -125,6 +133,9 @@ private: glm::vec3 _right; // Lens attributes + bool _orthographic; + float _width; + float _height; float _fieldOfView; // degrees float _aspectRatio; float _nearClip; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c820347cab..38948071ff 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -144,6 +144,7 @@ void ScriptEngine::init() { qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue); qScriptRegisterSequenceMetaType >(&_engine); qScriptRegisterSequenceMetaType >(&_engine); + qScriptRegisterSequenceMetaType >(&_engine); QScriptValue soundConstructorValue = _engine.newFunction(soundConstructor); QScriptValue soundMetaObject = _engine.newQMetaObject(&Sound::staticMetaObject, soundConstructorValue); diff --git a/libraries/shared/src/Logging.cpp b/libraries/shared/src/Logging.cpp index 1713382fcf..f0dcd7b67b 100644 --- a/libraries/shared/src/Logging.cpp +++ b/libraries/shared/src/Logging.cpp @@ -86,7 +86,7 @@ const char* stringForLogType(QtMsgType msgType) { } // the following will produce 2000-10-02 13:55:36 -0700 -const char DATE_STRING_FORMAT[] = "%F %H:%M:%S %z"; +const char DATE_STRING_FORMAT[] = "%Y-%m-%d %H:%M:%S %z"; void Logging::verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { if (message.isEmpty()) { diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 6d699322de..95417f4f71 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -69,7 +69,10 @@ NodeList::NodeList(char newOwnerType, unsigned short int newSocketListenPort) : _assignmentServerSocket(), _publicSockAddr(), _hasCompletedInitialSTUNFailure(false), - _stunRequestsSinceSuccess(0) + _stunRequestsSinceSuccess(0), + _numCollectedPackets(0), + _numCollectedBytes(0), + _packetStatTimer() { _nodeSocket.bind(QHostAddress::AnyIPv4, newSocketListenPort); qDebug() << "NodeList socket is listening on" << _nodeSocket.localPort(); @@ -79,6 +82,8 @@ NodeList::NodeList(char newOwnerType, unsigned short int newSocketListenPort) : // clear our NodeList when logout is requested connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); + + _packetStatTimer.start(); } bool NodeList::packetVersionAndHashMatch(const QByteArray& packet) { @@ -161,7 +166,11 @@ qint64 NodeList::writeDatagram(const QByteArray& datagram, const HifiSockAddr& d // setup the MD5 hash for source verification in the header replaceHashInPacketGivenConnectionUUID(datagramCopy, connectionSecret); - + + // stat collection for packets + ++_numCollectedPackets; + _numCollectedBytes += datagram.size(); + return _nodeSocket.writeDatagram(datagramCopy, destinationSockAddr.getAddress(), destinationSockAddr.getPort()); } @@ -193,6 +202,15 @@ qint64 NodeList::writeDatagram(const char* data, qint64 size, const SharedNodePo return writeDatagram(QByteArray(data, size), destinationNode, overridenSockAddr); } +qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) { + QByteArray statsPacket = byteArrayWithPopulatedHeader(PacketTypeNodeJsonStats); + QDataStream statsPacketStream(&statsPacket, QIODevice::Append); + + statsPacketStream << statsObject.toVariantMap(); + + return writeDatagram(statsPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); +} + void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) { QDataStream packetStream(packet); packetStream.skipRawData(numBytesForPacketHeader(packet)); @@ -308,9 +326,18 @@ int NodeList::findNodeAndUpdateWithDataFromPacket(const QByteArray& packet) { return 0; } -SharedNodePointer NodeList::nodeWithUUID(const QUuid& nodeUUID) { - QMutexLocker locker(&_nodeHashMutex); - return _nodeHash.value(nodeUUID); +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::sendingNodeForPacket(const QByteArray& packet) { @@ -836,6 +863,17 @@ SharedNodePointer NodeList::soloNodeOfType(char nodeType) { return SharedNodePointer(); } +void NodeList::getPacketStats(float& packetsPerSecond, float& bytesPerSecond) { + packetsPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f); + bytesPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f); +} + +void NodeList::resetPacketStats() { + _numCollectedPackets = 0; + _numCollectedBytes = 0; + _packetStatTimer.restart(); +} + void NodeList::removeSilentNodes() { _nodeHashMutex.lock(); diff --git a/libraries/shared/src/NodeList.h b/libraries/shared/src/NodeList.h index 590a2ce83f..d892223f75 100644 --- a/libraries/shared/src/NodeList.h +++ b/libraries/shared/src/NodeList.h @@ -21,6 +21,7 @@ #include // not on windows, not needed for mac or windows #endif +#include #include #include #include @@ -78,6 +79,7 @@ public: const HifiSockAddr& overridenSockAddr = HifiSockAddr()); qint64 writeDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, const HifiSockAddr& overridenSockAddr = HifiSockAddr()); + qint64 sendStatsToDomainServer(const QJsonObject& statsObject); void(*linkedDataCreateCallback)(Node *); @@ -101,7 +103,8 @@ public: QByteArray constructPingReplyPacket(const QByteArray& pingPacket); void pingPublicAndLocalSocketsForInactiveNode(const SharedNodePointer& node); - SharedNodePointer nodeWithUUID(const QUuid& nodeUUID); + /// 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 sendingNodeForPacket(const QByteArray& packet); SharedNodePointer addOrUpdateNode(const QUuid& uuid, char nodeType, @@ -118,6 +121,9 @@ public: unsigned broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes); SharedNodePointer soloNodeOfType(char nodeType); + void getPacketStats(float &packetsPerSecond, float &bytesPerSecond); + void resetPacketStats(); + void loadData(QSettings* settings); void saveData(QSettings* settings); public slots: @@ -153,6 +159,8 @@ private: void processDomainServerAuthRequest(const QByteArray& packet); void requestAuthForDomainServer(); + void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); + void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode); NodeHash _nodeHash; QMutex _nodeHashMutex; @@ -166,9 +174,9 @@ private: HifiSockAddr _publicSockAddr; bool _hasCompletedInitialSTUNFailure; unsigned int _stunRequestsSinceSuccess; - - void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); - void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode); + int _numCollectedPackets; + int _numCollectedBytes; + QElapsedTimer _packetStatTimer; }; #endif /* defined(__hifi__NodeList__) */ diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/shared/src/PacketHeaders.h index c6ce6bdd6b..a9bc5d3763 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/shared/src/PacketHeaders.h @@ -58,7 +58,8 @@ enum PacketType { PacketTypeAvatarIdentity, PacketTypeAvatarBillboard, PacketTypeDomainConnectRequest, - PacketTypeDomainServerAuthRequest + PacketTypeDomainServerAuthRequest, + PacketTypeNodeJsonStats }; typedef char PacketVersion; diff --git a/libraries/shared/src/ThreadedAssignment.cpp b/libraries/shared/src/ThreadedAssignment.cpp index b3a54b1488..f4ea383399 100644 --- a/libraries/shared/src/ThreadedAssignment.cpp +++ b/libraries/shared/src/ThreadedAssignment.cpp @@ -7,6 +7,7 @@ // #include +#include #include #include "Logging.h" @@ -34,7 +35,7 @@ void ThreadedAssignment::setFinished(bool isFinished) { } } -void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeType) { +void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeType, bool shouldSendStats) { // change the logging target name while the assignment is running Logging::setTargetName(targetName); @@ -52,6 +53,31 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy QTimer* silentNodeRemovalTimer = new QTimer(this); connect(silentNodeRemovalTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeRemovalTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + + if (shouldSendStats) { + // send a stats packet every 1 second + QTimer* statsTimer = new QTimer(this); + connect(statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket); + statsTimer->start(1000); + } +} + +void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject &statsObject) { + NodeList* nodeList = NodeList::getInstance(); + + float packetsPerSecond, bytesPerSecond; + nodeList->getPacketStats(packetsPerSecond, bytesPerSecond); + nodeList->resetPacketStats(); + + statsObject["packets_per_second"] = packetsPerSecond; + statsObject["bytes_per_second"] = bytesPerSecond; + + nodeList->sendStatsToDomainServer(statsObject); +} + +void ThreadedAssignment::sendStatsPacket() { + QJsonObject statsObject; + addPacketStatsAndSendStatsPacket(statsObject); } void ThreadedAssignment::checkInWithDomainServerOrExit() { diff --git a/libraries/shared/src/ThreadedAssignment.h b/libraries/shared/src/ThreadedAssignment.h index d3502e9c4d..5b78eed56d 100644 --- a/libraries/shared/src/ThreadedAssignment.h +++ b/libraries/shared/src/ThreadedAssignment.h @@ -17,16 +17,18 @@ public: ThreadedAssignment(const QByteArray& packet); void setFinished(bool isFinished); virtual void aboutToFinish() { }; + void addPacketStatsAndSendStatsPacket(QJsonObject& statsObject); public slots: /// threaded run of assignment virtual void run() = 0; virtual void deleteLater(); virtual void readPendingDatagrams() = 0; + virtual void sendStatsPacket(); protected: bool readAvailableDatagram(QByteArray& destinationByteArray, HifiSockAddr& senderSockAddr); - void commonInit(const QString& targetName, NodeType_t nodeType); + void commonInit(const QString& targetName, NodeType_t nodeType, bool shouldSendStats = true); bool _isFinished; private slots: void checkInWithDomainServerOrExit();