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/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index d32d6a3fd7..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; diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index d6e82a3889..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 @@ -66,7 +67,11 @@ void attachNewBufferToNode(Node *newNode) { AudioMixer::AudioMixer(const QByteArray& packet) : ThreadedAssignment(packet), _trailingSleepRatio(1.0f), - _minAudibilityThreshold(LOUDNESS_TO_DISTANCE_RATIO / 2.0f) + _minAudibilityThreshold(LOUDNESS_TO_DISTANCE_RATIO / 2.0f), + _performanceThrottlingRatio(0.0f), + _numStatFrames(0), + _sumListeners(0), + _sumMixes(0) { } @@ -94,6 +99,8 @@ void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuf return; } + ++_sumMixes; + glm::quat inverseOrientation = glm::inverse(listeningNodeBuffer->getOrientation()); float distanceSquareToSource = glm::dot(relativePosition, relativePosition); @@ -349,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(); @@ -368,7 +395,6 @@ void AudioMixer::run() { + numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)]; int usecToSleep = BUFFER_SEND_INTERVAL_USECS; - float audabilityCutoffRatio = 0; const int TRAILING_AVERAGE_FRAMES = 100; int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES; @@ -396,47 +422,39 @@ void AudioMixer::run() { _trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) + (usecToSleep * CURRENT_FRAME_RATIO / (float) BUFFER_SEND_INTERVAL_USECS); - float lastCutoffRatio = audabilityCutoffRatio; + float lastCutoffRatio = _performanceThrottlingRatio; bool hasRatioChanged = false; if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) { - if (framesSinceCutoffEvent % TRAILING_AVERAGE_FRAMES == 0) { - qDebug() << "Current trailing sleep ratio:" << _trailingSleepRatio; - } - if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) { // we're struggling - change our min required loudness to reduce some load - audabilityCutoffRatio = audabilityCutoffRatio + (0.5f * (1.0f - audabilityCutoffRatio)); + _performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio)); qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" - << lastCutoffRatio << "and is now" << audabilityCutoffRatio; + << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; hasRatioChanged = true; - } else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && audabilityCutoffRatio != 0) { + } else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) { // we've recovered and can back off the required loudness - audabilityCutoffRatio = audabilityCutoffRatio - RATIO_BACK_OFF; + _performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF; - if (audabilityCutoffRatio < 0) { - audabilityCutoffRatio = 0; + if (_performanceThrottlingRatio < 0) { + _performanceThrottlingRatio = 0; } qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" - << lastCutoffRatio << "and is now" << audabilityCutoffRatio; + << 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 - audabilityCutoffRatio)); + _minAudibilityThreshold = LOUDNESS_TO_DISTANCE_RATIO / (2.0f * (1.0f - _performanceThrottlingRatio)); qDebug() << "Minimum audability required to be mixed is now" << _minAudibilityThreshold; framesSinceCutoffEvent = 0; } } - if (!hasRatioChanged) { - ++framesSinceCutoffEvent; - } - foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() && ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) { @@ -446,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; } } @@ -456,6 +476,8 @@ void AudioMixer::run() { } } + ++_numStatFrames; + QCoreApplication::processEvents(); if (_isFinished) { diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 9d731a5c9c..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, @@ -42,6 +44,10 @@ private: float _trailingSleepRatio; float _minAudibilityThreshold; + float _performanceThrottlingRatio; + int _numStatFrames; + int _sumListeners; + int _sumMixes; }; #endif /* defined(__hifi__AudioMixer__) */ 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/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/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index d4dfa80724..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) { @@ -988,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/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/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/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/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 2d2f4c1c07..beada5df43 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,51 +132,30 @@ QObject - - + + Import Voxels - + Loading ... - + Place voxels - + <b>Import</b> %1 as voxels - + Cancel - - SnapshotShareDialog - - - - Share with community - - - - - - Notes about this image - - - - - - Share - - - 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 e819422d46..a67736321b 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 @@ -3548,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 bc401c7de5..9692b5474b 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,14 +56,21 @@ #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/SnapshotShareDialog.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; @@ -156,6 +156,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; } @@ -172,7 +173,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(); } @@ -353,6 +358,7 @@ private: DatagramProcessor _datagramProcessor; QNetworkAccessManager* _networkAccessManager; + QMutex _settingsMutex; QSettings* _settings; glm::vec3 _gravity; @@ -386,6 +392,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..7adef1be1c 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; @@ -374,8 +374,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); @@ -404,11 +406,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); @@ -430,6 +438,9 @@ void Menu::saveSettings(QSettings* settings) { Application::getInstance()->getAvatar()->saveData(settings); NodeList::getInstance()->saveData(settings); + if (lockedSettings) { + Application::getInstance()->unlockSettings(); + } } void Menu::importSettings() { @@ -1403,9 +1414,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/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 638bff6e32..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); } @@ -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 a6a7f22896..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 { @@ -639,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 241286a721..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; 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/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/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 2b131ab7ff..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)); @@ -845,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 d6328a1303..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 *); @@ -119,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: @@ -154,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; @@ -167,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();