diff --git a/BUILD.md b/BUILD.md index caf7dae3d9..32c35a4c2b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -24,9 +24,12 @@ In order for CMake to find the Qt5 find modules, you will need to set an ENV var For example, a Qt5 5.2.0 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment). - export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.2.0/clang_64/lib/cmake/ +The path it needs to be set to will depend on where and how Qt5 was installed. e.g. + + export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.2.0/clang_64/lib/cmake/ + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.2.1/lib/cmake + export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake -The path it needs to be set to will depend on where and how Qt5 was installed. ####Generating build files Create a build directory in the root of your checkout and then run the CMake build from there. This will keep the rest of the directory clean. diff --git a/CMakeLists.txt b/CMakeLists.txt index 2451ab240a..b8566dd050 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,14 +32,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) -if (APPLE) - exec_program(uname ARGS -v OUTPUT_VARIABLE DARWIN_VERSION) - string(REGEX MATCH "[0-9]+" DARWIN_VERSION ${DARWIN_VERSION}) - if (DARWIN_VERSION GREATER 12) - set(CMAKE_CXX_FLAGS "-stdlib=libstdc++") - endif (DARWIN_VERSION GREATER 12) -endif (APPLE) - # targets not supported on windows if (NOT WIN32) add_subdirectory(animation-server) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 637bdca67f..0449e0d682 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -214,7 +214,6 @@ void Agent::run() { QNetworkDiskCache* cache = new QNetworkDiskCache(); QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "agentCache"); - cache->moveToThread(networkAccessManager.thread()); networkAccessManager.setCache(cache); qDebug() << "Downloading script at" << scriptURL.toString(); diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 6cdcaef133..459f8a4b59 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -405,7 +405,8 @@ void AudioMixer::readPendingDatagrams() { if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho || mixerPacketType == PacketTypeMicrophoneAudioWithEcho || mixerPacketType == PacketTypeInjectAudio - || mixerPacketType == PacketTypeSilentAudioFrame) { + || mixerPacketType == PacketTypeSilentAudioFrame + || mixerPacketType == PacketTypeAudioStreamStats) { nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket); } else if (mixerPacketType == PacketTypeMuteEnvironment) { @@ -640,9 +641,6 @@ void AudioMixer::run() { ++framesSinceCutoffEvent; } - - const quint64 TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS = 1 * USECS_PER_SECOND; - bool sendAudioStreamStats = false; quint64 now = usecTimestampNow(); if (now - _lastSendAudioStreamStatsTime > TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS) { diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 2c94f32edc..afab7d47dc 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -21,6 +21,8 @@ class AvatarAudioRingBuffer; const int SAMPLE_PHASE_DELAY_AT_90 = 20; +const quint64 TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS = 1 * USECS_PER_SECOND; + /// Handles assignments of type AudioMixer - mixing streams of audio and re-distributing to various clients. class AudioMixer : public ThreadedAssignment { Q_OBJECT diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index f6437f9c97..94bbdc6a6b 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -18,11 +18,15 @@ #include "AudioMixer.h" #include "AudioMixerClientData.h" +#include "MovingMinMaxAvg.h" + +const int INCOMING_SEQ_STATS_HISTORY_LENGTH = INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS / + (TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS / USECS_PER_SECOND); AudioMixerClientData::AudioMixerClientData() : _ringBuffers(), _outgoingMixedAudioSequenceNumber(0), - _incomingAvatarAudioSequenceNumberStats() + _incomingAvatarAudioSequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH) { } @@ -82,12 +86,15 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { // ask the AvatarAudioRingBuffer instance to parse the data avatarRingBuffer->parseData(packet); - } else { + } else if (packetType == PacketTypeInjectAudio) { // this is injected audio // grab the stream identifier for this injected audio QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(numBytesForPacketHeader(packet) + sizeof(quint16), NUM_BYTES_RFC4122_UUID)); + if (!_incomingInjectedAudioSequenceNumberStatsMap.contains(streamIdentifier)) { + _incomingInjectedAudioSequenceNumberStatsMap.insert(streamIdentifier, SequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH)); + } _incomingInjectedAudioSequenceNumberStatsMap[streamIdentifier].sequenceNumberReceived(sequence); InjectedAudioRingBuffer* matchingInjectedRingBuffer = NULL; @@ -106,6 +113,15 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { } matchingInjectedRingBuffer->parseData(packet); + } else if (packetType == PacketTypeAudioStreamStats) { + + const char* dataAt = packet.data(); + + // skip over header, appendFlag, and num stats packed + dataAt += (numBytesPacketHeader + sizeof(quint8) + sizeof(quint16)); + + // read the downstream audio stream stats + memcpy(&_downstreamAudioStreamStats, dataAt, sizeof(AudioStreamStats)); } return 0; @@ -138,11 +154,14 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() { // this was a used buffer, push the output pointer forwards PositionalAudioRingBuffer* audioBuffer = *i; + const int INJECTOR_CONSECUTIVE_NOT_MIXED_THRESHOLD = 100; + if (audioBuffer->willBeAddedToMix()) { audioBuffer->shiftReadPosition(audioBuffer->getSamplesPerFrame()); audioBuffer->setWillBeAddedToMix(false); } else if (audioBuffer->getType() == PositionalAudioRingBuffer::Injector - && audioBuffer->hasStarted() && audioBuffer->isStarved()) { + && audioBuffer->hasStarted() && audioBuffer->isStarved() + && audioBuffer->getConsecutiveNotMixedCount() > INJECTOR_CONSECUTIVE_NOT_MIXED_THRESHOLD) { // this is an empty audio buffer that has starved, safe to delete // also delete its sequence number stats QUuid streamIdentifier = ((InjectedAudioRingBuffer*)audioBuffer)->getStreamIdentifier(); @@ -156,31 +175,51 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() { } AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const { + AudioStreamStats streamStats; - SequenceNumberStats streamSequenceNumberStats; streamStats._streamType = ringBuffer->getType(); if (streamStats._streamType == PositionalAudioRingBuffer::Injector) { streamStats._streamIdentifier = ((InjectedAudioRingBuffer*)ringBuffer)->getStreamIdentifier(); - streamSequenceNumberStats = _incomingInjectedAudioSequenceNumberStatsMap.value(streamStats._streamIdentifier); + const SequenceNumberStats& sequenceNumberStats = _incomingInjectedAudioSequenceNumberStatsMap[streamStats._streamIdentifier]; + streamStats._packetStreamStats = sequenceNumberStats.getStats(); + streamStats._packetStreamWindowStats = sequenceNumberStats.getStatsForHistoryWindow(); } else { - streamSequenceNumberStats = _incomingAvatarAudioSequenceNumberStats; + streamStats._packetStreamStats = _incomingAvatarAudioSequenceNumberStats.getStats(); + streamStats._packetStreamWindowStats = _incomingAvatarAudioSequenceNumberStats.getStatsForHistoryWindow(); } - streamStats._jitterBufferFrames = ringBuffer->getCurrentJitterBufferFrames(); - - streamStats._packetsReceived = streamSequenceNumberStats.getNumReceived(); - streamStats._packetsUnreasonable = streamSequenceNumberStats.getNumUnreasonable(); - streamStats._packetsEarly = streamSequenceNumberStats.getNumEarly(); - streamStats._packetsLate = streamSequenceNumberStats.getNumLate(); - streamStats._packetsLost = streamSequenceNumberStats.getNumLost(); - streamStats._packetsRecovered = streamSequenceNumberStats.getNumRecovered(); - streamStats._packetsDuplicate = streamSequenceNumberStats.getNumDuplicate(); + + const MovingMinMaxAvg& timeGapStats = ringBuffer->getInterframeTimeGapStatsForStatsPacket(); + streamStats._timeGapMin = timeGapStats.getMin(); + streamStats._timeGapMax = timeGapStats.getMax(); + streamStats._timeGapAverage = timeGapStats.getAverage(); + streamStats._timeGapWindowMin = timeGapStats.getWindowMin(); + streamStats._timeGapWindowMax = timeGapStats.getWindowMax(); + streamStats._timeGapWindowAverage = timeGapStats.getWindowAverage(); + + streamStats._ringBufferFramesAvailable = ringBuffer->framesAvailable(); + streamStats._ringBufferCurrentJitterBufferFrames = ringBuffer->getCurrentJitterBufferFrames(); + streamStats._ringBufferDesiredJitterBufferFrames = ringBuffer->getDesiredJitterBufferFrames(); + streamStats._ringBufferStarveCount = ringBuffer->getStarveCount(); + streamStats._ringBufferConsecutiveNotMixedCount = ringBuffer->getConsecutiveNotMixedCount(); + streamStats._ringBufferOverflowCount = ringBuffer->getOverflowCount(); + streamStats._ringBufferSilentFramesDropped = ringBuffer->getSilentFramesDropped(); return streamStats; } -void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const { +void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) { + // have all the seq number stats of each audio stream push their current stats into their history, + // which moves that history window 1 second forward (since that's how long since the last stats were pushed into history) + _incomingAvatarAudioSequenceNumberStats.pushStatsToHistory(); + QHash::Iterator i = _incomingInjectedAudioSequenceNumberStatsMap.begin(); + QHash::Iterator end = _incomingInjectedAudioSequenceNumberStatsMap.end(); + while (i != end) { + i.value().pushStatsToHistory(); + i++; + } + char packet[MAX_PACKET_SIZE]; NodeList* nodeList = NodeList::getInstance(); @@ -231,46 +270,63 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& QString AudioMixerClientData::getAudioStreamStatsString() const { QString result; + AudioStreamStats streamStats = _downstreamAudioStreamStats; + result += "DOWNSTREAM.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + + " current: ?" + + " available:" + QString::number(streamStats._ringBufferFramesAvailable) + + " starves:" + QString::number(streamStats._ringBufferStarveCount) + + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + + " silents_dropped: ?" + + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2) + + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2) + + " min_gap:" + QString::number(streamStats._timeGapMin) + + " max_gap:" + QString::number(streamStats._timeGapMax) + + " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) + + " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin) + + " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax) + + " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); + AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer(); if (avatarRingBuffer) { - int desiredJitterBuffer = avatarRingBuffer->getDesiredJitterBufferFrames(); - int calculatedJitterBuffer = avatarRingBuffer->getCalculatedDesiredJitterBufferFrames(); - int currentJitterBuffer = avatarRingBuffer->getCurrentJitterBufferFrames(); - int overflowCount = avatarRingBuffer->getOverflowCount(); - int samplesAvailable = avatarRingBuffer->samplesAvailable(); - int framesAvailable = (samplesAvailable / avatarRingBuffer->getSamplesPerFrame()); AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer); - result += "mic.desired:" + QString::number(desiredJitterBuffer) - + " calculated:" + QString::number(calculatedJitterBuffer) - + " current:" + QString::number(currentJitterBuffer) - + " available:" + QString::number(framesAvailable) - + " samples:" + QString::number(samplesAvailable) - + " overflows:" + QString::number(overflowCount) - + " early:" + QString::number(streamStats._packetsEarly) - + " late:" + QString::number(streamStats._packetsLate) - + " lost:" + QString::number(streamStats._packetsLost); + result += " UPSTREAM.mic.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + + " current:" + QString::number(streamStats._ringBufferCurrentJitterBufferFrames) + + " available:" + QString::number(streamStats._ringBufferFramesAvailable) + + " starves:" + QString::number(streamStats._ringBufferStarveCount) + + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + + " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) + + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2) + + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2) + + " min_gap:" + QString::number(streamStats._timeGapMin) + + " max_gap:" + QString::number(streamStats._timeGapMax) + + " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) + + " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin) + + " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax) + + " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); } else { result = "mic unknown"; } for (int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) { - int desiredJitterBuffer = _ringBuffers[i]->getDesiredJitterBufferFrames(); - int calculatedJitterBuffer = _ringBuffers[i]->getCalculatedDesiredJitterBufferFrames(); - int currentJitterBuffer = _ringBuffers[i]->getCurrentJitterBufferFrames(); - int overflowCount = _ringBuffers[i]->getOverflowCount(); - int samplesAvailable = _ringBuffers[i]->samplesAvailable(); - int framesAvailable = (samplesAvailable / _ringBuffers[i]->getSamplesPerFrame()); AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]); - result += "| injected[" + QString::number(i) + "].desired:" + QString::number(desiredJitterBuffer) - + " calculated:" + QString::number(calculatedJitterBuffer) - + " current:" + QString::number(currentJitterBuffer) - + " available:" + QString::number(framesAvailable) - + " samples:" + QString::number(samplesAvailable) - + " overflows:" + QString::number(overflowCount) - + " early:" + QString::number(streamStats._packetsEarly) - + " late:" + QString::number(streamStats._packetsLate) - + " lost:" + QString::number(streamStats._packetsLost); + result += " UPSTREAM.inj.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + + " current:" + QString::number(streamStats._ringBufferCurrentJitterBufferFrames) + + " available:" + QString::number(streamStats._ringBufferFramesAvailable) + + " starves:" + QString::number(streamStats._ringBufferStarveCount) + + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + + " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) + + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2) + + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2) + + " min_gap:" + QString::number(streamStats._timeGapMin) + + " max_gap:" + QString::number(streamStats._timeGapMax) + + " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) + + " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin) + + " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax) + + " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); } } return result; diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 65fd4b3da3..7475c0a60e 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -20,6 +20,9 @@ #include "AudioStreamStats.h" #include "SequenceNumberStats.h" + +const int INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS = 30; + class AudioMixerClientData : public NodeData { public: AudioMixerClientData(); @@ -35,7 +38,7 @@ public: AudioStreamStats getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const; QString getAudioStreamStatsString() const; - void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const; + void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode); void incrementOutgoingMixedAudioSequenceNumber() { _outgoingMixedAudioSequenceNumber++; } quint16 getOutgoingSequenceNumber() const { return _outgoingMixedAudioSequenceNumber; } @@ -46,6 +49,8 @@ private: quint16 _outgoingMixedAudioSequenceNumber; SequenceNumberStats _incomingAvatarAudioSequenceNumberStats; QHash _incomingInjectedAudioSequenceNumberStatsMap; + + AudioStreamStats _downstreamAudioStreamStats; }; #endif // hifi_AudioMixerClientData_h diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp index 9c6cc32f57..3fa9f64cff 100644 --- a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp +++ b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp @@ -19,7 +19,7 @@ AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo, bool dynamicJitterBu } int AvatarAudioRingBuffer::parseData(const QByteArray& packet) { - _interframeTimeGapStats.frameReceived(); + timeGapStatsFrameReceived(); updateDesiredJitterBufferFrames(); _shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho); diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index d0c0d4c781..4d9c45ed6c 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -10,6 +10,9 @@ // #include +#include +#include +#include #include @@ -21,7 +24,8 @@ const int SEND_INTERVAL = 50; MetavoxelServer::MetavoxelServer(const QByteArray& packet) : - ThreadedAssignment(packet) { + ThreadedAssignment(packet), + _sendTimer(this) { _sendTimer.setSingleShot(true); connect(&_sendTimer, SIGNAL(timeout()), SLOT(sendDeltas())); @@ -43,6 +47,18 @@ void MetavoxelServer::run() { _lastSend = QDateTime::currentMSecsSinceEpoch(); _sendTimer.start(SEND_INTERVAL); + + // initialize Bitstream before using it in multiple threads + Bitstream::preThreadingInit(); + + // create the persister and start it in its own thread + _persister = new MetavoxelPersister(this); + QThread* persistenceThread = new QThread(this); + _persister->moveToThread(persistenceThread); + persistenceThread->start(); + + // queue up the load + QMetaObject::invokeMethod(_persister, "load"); } void MetavoxelServer::readPendingDatagrams() { @@ -66,6 +82,12 @@ void MetavoxelServer::readPendingDatagrams() { } } +void MetavoxelServer::aboutToFinish() { + QMetaObject::invokeMethod(_persister, "save", Q_ARG(const MetavoxelData&, _data)); + _persister->thread()->quit(); + _persister->thread()->wait(); +} + void MetavoxelServer::maybeAttachSession(const SharedNodePointer& node) { if (node->getType() == NodeType::Agent) { QMutexLocker locker(&node->getMutex()); @@ -86,29 +108,62 @@ void MetavoxelServer::sendDeltas() { int elapsed = now - _lastSend; _lastSend = now; - _sendTimer.start(qMax(0, 2 * SEND_INTERVAL - elapsed)); + _sendTimer.start(qMax(0, 2 * SEND_INTERVAL - qMax(elapsed, SEND_INTERVAL))); } MetavoxelSession::MetavoxelSession(const SharedNodePointer& node, MetavoxelServer* server) : Endpoint(node, new PacketRecord(), NULL), - _server(server) { + _server(server), + _reliableDeltaChannel(NULL), + _reliableDeltaID(0) { connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleMessage(const QVariant&))); - connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)), - SLOT(handleMessage(const QVariant&))); + connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(checkReliableDeltaReceived())); + connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&, Bitstream&)), + SLOT(handleMessage(const QVariant&, Bitstream&))); } void MetavoxelSession::update() { - // wait until we have a valid lod - if (_lod.isValid()) { - Endpoint::update(); + // wait until we have a valid lod before sending + if (!_lod.isValid()) { + return; } -} - -void MetavoxelSession::writeUpdateMessage(Bitstream& out) { + // if we're sending a reliable delta, wait until it's acknowledged + if (_reliableDeltaChannel) { + sendPacketGroup(); + return; + } + Bitstream& out = _sequencer.startPacket(); + int start = _sequencer.getOutputStream().getUnderlying().device()->pos(); out << QVariant::fromValue(MetavoxelDeltaMessage()); PacketRecord* sendRecord = getLastAcknowledgedSendRecord(); _server->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod); + out.flush(); + int end = _sequencer.getOutputStream().getUnderlying().device()->pos(); + if (end > _sequencer.getMaxPacketSize()) { + // we need to send the delta on the reliable channel + _reliableDeltaChannel = _sequencer.getReliableOutputChannel(RELIABLE_DELTA_CHANNEL_INDEX); + _reliableDeltaChannel->startMessage(); + _reliableDeltaChannel->getBuffer().write(_sequencer.getOutgoingPacketData().constData() + start, end - start); + _reliableDeltaChannel->endMessage(); + + _reliableDeltaWriteMappings = out.getAndResetWriteMappings(); + _reliableDeltaReceivedOffset = _reliableDeltaChannel->getBytesWritten(); + _reliableDeltaData = _server->getData(); + _reliableDeltaLOD = _lod; + + // go back to the beginning with the current packet and note that there's a delta pending + _sequencer.getOutputStream().getUnderlying().device()->seek(start); + MetavoxelDeltaPendingMessage msg = { ++_reliableDeltaID }; + out << QVariant::fromValue(msg); + _sequencer.endPacket(); + + } else { + _sequencer.endPacket(); + } + + // perhaps send additional packets to fill out the group + sendPacketGroup(1); } void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) { @@ -116,7 +171,8 @@ void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) { } PacketRecord* MetavoxelSession::maybeCreateSendRecord() const { - return new PacketRecord(_lod, _server->getData()); + return _reliableDeltaChannel ? new PacketRecord(_reliableDeltaLOD, _reliableDeltaData) : + new PacketRecord(_lod, _server->getData()); } void MetavoxelSession::handleMessage(const QVariant& message) { @@ -134,3 +190,68 @@ void MetavoxelSession::handleMessage(const QVariant& message) { } } } + +void MetavoxelSession::checkReliableDeltaReceived() { + if (!_reliableDeltaChannel || _reliableDeltaChannel->getOffset() < _reliableDeltaReceivedOffset) { + return; + } + _sequencer.getOutputStream().persistWriteMappings(_reliableDeltaWriteMappings); + _reliableDeltaWriteMappings = Bitstream::WriteMappings(); + _reliableDeltaData = MetavoxelData(); + _reliableDeltaChannel = NULL; +} + +void MetavoxelSession::sendPacketGroup(int alreadySent) { + int additionalPackets = _sequencer.notePacketGroup() - alreadySent; + for (int i = 0; i < additionalPackets; i++) { + Bitstream& out = _sequencer.startPacket(); + if (_reliableDeltaChannel) { + MetavoxelDeltaPendingMessage msg = { _reliableDeltaID }; + out << QVariant::fromValue(msg); + } else { + out << QVariant(); + } + _sequencer.endPacket(); + } +} + +MetavoxelPersister::MetavoxelPersister(MetavoxelServer* server) : + _server(server) { +} + +const char* SAVE_FILE = "metavoxels.dat"; + +void MetavoxelPersister::load() { + QFile file(SAVE_FILE); + if (!file.exists()) { + return; + } + MetavoxelData data; + { + QDebug debug = qDebug() << "Reading from" << SAVE_FILE << "..."; + file.open(QIODevice::ReadOnly); + QDataStream inStream(&file); + Bitstream in(inStream); + try { + in >> data; + } catch (const BitstreamException& e) { + debug << "failed, " << e.getDescription(); + return; + } + QMetaObject::invokeMethod(_server, "setData", Q_ARG(const MetavoxelData&, data)); + debug << "done."; + } + data.dumpStats(); +} + +void MetavoxelPersister::save(const MetavoxelData& data) { + QDebug debug = qDebug() << "Writing to" << SAVE_FILE << "..."; + QSaveFile file(SAVE_FILE); + file.open(QIODevice::WriteOnly); + QDataStream outStream(&file); + Bitstream out(outStream); + out << data; + out.flush(); + file.commit(); + debug << "done."; +} diff --git a/assignment-client/src/metavoxels/MetavoxelServer.h b/assignment-client/src/metavoxels/MetavoxelServer.h index d9b010e282..9ed765f65f 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.h +++ b/assignment-client/src/metavoxels/MetavoxelServer.h @@ -20,6 +20,7 @@ #include class MetavoxelEditMessage; +class MetavoxelPersister; class MetavoxelSession; /// Maintains a shared metavoxel system, accepting change requests and broadcasting updates. @@ -33,11 +34,15 @@ public: void applyEdit(const MetavoxelEditMessage& edit); const MetavoxelData& getData() const { return _data; } + + Q_INVOKABLE void setData(const MetavoxelData& data) { _data = data; } virtual void run(); virtual void readPendingDatagrams(); + virtual void aboutToFinish(); + private slots: void maybeAttachSession(const SharedNodePointer& node); @@ -45,6 +50,8 @@ private slots: private: + MetavoxelPersister* _persister; + QTimer _sendTimer; qint64 _lastSend; @@ -63,7 +70,6 @@ public: protected: - virtual void writeUpdateMessage(Bitstream& out); virtual void handleMessage(const QVariant& message, Bitstream& in); virtual PacketRecord* maybeCreateSendRecord() const; @@ -71,12 +77,38 @@ protected: private slots: void handleMessage(const QVariant& message); + void checkReliableDeltaReceived(); private: + void sendPacketGroup(int alreadySent = 0); + MetavoxelServer* _server; MetavoxelLOD _lod; + + ReliableChannel* _reliableDeltaChannel; + int _reliableDeltaReceivedOffset; + MetavoxelData _reliableDeltaData; + MetavoxelLOD _reliableDeltaLOD; + Bitstream::WriteMappings _reliableDeltaWriteMappings; + int _reliableDeltaID; +}; + +/// Handles persistence in a separate thread. +class MetavoxelPersister : public QObject { + Q_OBJECT + +public: + + MetavoxelPersister(MetavoxelServer* server); + + Q_INVOKABLE void load(); + Q_INVOKABLE void save(const MetavoxelData& data); + +private: + + MetavoxelServer* _server; }; #endif // hifi_MetavoxelServer_h diff --git a/cmake/modules/FindRtMidi.cmake b/cmake/modules/FindRtMidi.cmake index a54cc483e1..ad1167c5d6 100644 --- a/cmake/modules/FindRtMidi.cmake +++ b/cmake/modules/FindRtMidi.cmake @@ -26,8 +26,8 @@ else () set(RTMIDI_SEARCH_DIRS "${RTMIDI_ROOT_DIR}" "$ENV{HIFI_LIB_DIR}/rtmidi") find_path(RTMIDI_INCLUDE_DIR RtMidi.h PATH_SUFFIXES include HINTS ${RTMIDI_SEARCH_DIRS}) - find_file(RTMIDI_CPP NAMES RtMidi.cpp PATH_SUFFIXES src HINTS ${RTMIDI_SEARCH_DIRS}) + find_library(RTMIDI_LIBRARY NAMES rtmidi PATH_SUFFIXES lib HINTS ${RTMIDI_SEARCH_DIRS}) include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(RTMIDI DEFAULT_MSG RTMIDI_INCLUDE_DIR RTMIDI_CPP) + find_package_handle_standard_args(RTMIDI DEFAULT_MSG RTMIDI_INCLUDE_DIR RTMIDI_LIBRARY) endif () \ No newline at end of file diff --git a/examples/avatarLocalLight.js b/examples/avatarLocalLight.js new file mode 100644 index 0000000000..57f9d84ffe --- /dev/null +++ b/examples/avatarLocalLight.js @@ -0,0 +1,126 @@ +// +// avatarLocalLight.js +// +// Created by Tony Peng on July 2nd, 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Set the local light direction and color on the avatar +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var localLightDirections = [ {x: 1.0, y:0.0, z: 0.0}, {x: 0.0, y:1.0, z: 1.0}, {x: 0.0, y:0.0, z: 1.0}, {x: 1.0, y:1.0, z: 1.0} ]; +var localLightColors = [ {x: 0.0, y:0.0, z: 0.0}, {x: 0.0, y:0.0, z: 0.0}, {x: 0.0, y:0.0, z: 0.0}, {x: 0.0, y:0.0, z: 0.0} ]; + +var currentSelection = 0; +var currentNumLights = 1; +var maxNumLights = 2; + +function keyPressEvent(event) { + + var choice = parseInt(event.text); + + if (event.text == "1") { + currentSelection = 0; + print("light election = " + currentSelection); + } + else if (event.text == "2" ) { + currentSelection = 1; + print("light selection = " + currentSelection); + } + else if (event.text == "3" ) { + currentSelection = 2; + print("light selection = " + currentSelection); + } + else if (event.text == "4" ) { + currentSelection = 3; + print("light selection = " + currentSelection); + } + else if (event.text == "5" ) { + localLightColors[currentSelection].x += 0.01; + if ( localLightColors[currentSelection].x > 1.0) { + localLightColors[currentSelection].x = 0.0; + } + + MyAvatar.setLocalLightColor(localLightColors[currentSelection], currentSelection); + } + else if (event.text == "6" ) { + localLightColors[currentSelection].y += 0.01; + if ( localLightColors[currentSelection].y > 1.0) { + localLightColors[currentSelection].y = 0.0; + } + + MyAvatar.setLocalLightColor(localLightColors[currentSelection], currentSelection); + } + else if (event.text == "7" ) { + localLightColors[currentSelection].z += 0.01; + if ( localLightColors[currentSelection].z > 1.0) { + localLightColors[currentSelection].z = 0.0; + } + + MyAvatar.setLocalLightColor(localLightColors[currentSelection], currentSelection); + } + else if (event.text == "8" ) { + localLightDirections[currentSelection].x += 0.01; + if (localLightDirections[currentSelection].x > 1.0) { + localLightDirections[currentSelection].x = -1.0; + } + + MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection); + } + else if (event.text == "9" ) { + localLightDirections[currentSelection].x -= 0.01; + if (localLightDirections[currentSelection].x < -1.0) { + localLightDirections[currentSelection].x = 1.0; + } + + MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection); + } + else if (event.text == "[" ) { + localLightDirections[currentSelection].y += 0.01; + if (localLightDirections[currentSelection].y > 1.0) { + localLightDirections[currentSelection].y = -1.0; + } + + MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection); + } + else if (event.text == "]" ) { + localLightDirections[currentSelection].y -= 0.01; + if (localLightDirections[currentSelection].y < -1.0) { + localLightDirections[currentSelection].y = 1.0; + } + + MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection); + } + else if (event.text == "," ) { + if (currentNumLights + 1 <= maxNumLights) { + var darkGrayColor = {x:0.3, y:0.3, z:0.3}; + + // default light + localLightColors[currentNumLights].x = darkGrayColor.x; + localLightColors[currentNumLights].y = darkGrayColor.y; + localLightColors[currentNumLights].z = darkGrayColor.z; + + MyAvatar.addLocalLight(); + MyAvatar.setLocalLightColor(localLightColors[currentNumLights], currentNumLights); + MyAvatar.setLocalLightDirection(localLightDirections[currentNumLights], currentNumLights); + + ++currentNumLights; + } + } + else if (event.text == "." ) { + if (currentNumLights - 1 >= 0 ) { + + // no light contribution + localLightColors[currentNumLights - 1].x = 0.0; + localLightColors[currentNumLights - 1].y = 0.0; + localLightColors[currentNumLights - 1].z = 0.0; + + MyAvatar.removeLocalLight(); + --currentNumLights; + } + } +} + +Controller.keyPressEvent.connect(keyPressEvent); diff --git a/examples/clap.js b/examples/clap.js index 9da36ba094..28835c1f00 100644 --- a/examples/clap.js +++ b/examples/clap.js @@ -1,72 +1,147 @@ // -// cameraExample.js +// clap.js // examples // // Copyright 2014 High Fidelity, Inc. // -// This sample script watches your hydra hands and makes clapping sound when they come close together fast +// This sample script watches your hydra hands and makes clapping sound when they come close together fast, +// and also watches for the 'shift' key and claps when that key is pressed. Clapping multiple times by pressing +// the shift key again makes the animation and sound match your pace of clapping. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -function length(v) { - return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); -} +var clapAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/ClapAnimations/ClapHands_Standing.fbx"; +var ANIMATION_FRAMES_PER_CLAP = 10.0; +var startEndFrames = []; +startEndFrames.push({ start: 0, end: 10}); +startEndFrames.push({ start: 10, end: 20}); +startEndFrames.push({ start: 20, end: 30}); +startEndFrames.push({ start: 30, end: 40}); +startEndFrames.push({ start: 41, end: 51}); +startEndFrames.push({ start: 53, end: 0}); +var lastClapFrame = 0; +var lastAnimFrame = 0; -function printVector(v) { - print(v.x + ", " + v.y + ", " + v.z + "\n"); -} +var claps = []; +claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap1Rvb.wav")); +claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap2Rvb.wav")); +claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap3Rvb.wav")); +claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap4Rvb.wav")); +claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap5Rvb.wav")); +claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap6Rvb.wav")); +claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap7Rvb.wav")); +claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap8Rvb.wav")); +claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap9Rvb.wav")); +claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap10Rvb.wav")); +var numberOfSounds = claps.length; -function vMinus(a, b) { - var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; - return rval; -} +var clappingNow = false; +var collectedClicks = 0; -// First, load the clap sound from a URL -var clap1 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap1.raw"); -var clap2 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap2.raw"); -var clap3 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap3.raw"); -var clap4 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap4.raw"); -var clap5 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap5.raw"); -var clap6 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap6.raw"); - -var clapping = new Array(); -clapping[0] = false; -clapping[1] = false; +var clickStartTime, clickEndTime; +var clickClappingNow = false; +var CLAP_START_RATE = 15.0; +var clapRate = CLAP_START_RATE; +var startedTimer = false; function maybePlaySound(deltaTime) { // Set the location and other info for the sound to play - var palm1Position = Controller.getSpatialControlPosition(0); - var palm2Position = Controller.getSpatialControlPosition(2); - var distanceBetween = length(vMinus(palm1Position, palm2Position)); - for (var palm = 0; palm < 2; palm++) { - var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1); - var speed = length(palmVelocity); - - const CLAP_SPEED = 0.2; - const CLAP_DISTANCE = 0.2; + var animationDetails = MyAvatar.getAnimationDetails(clapAnimation); - if (!clapping[palm] && (distanceBetween < CLAP_DISTANCE) && (speed > CLAP_SPEED)) { - var options = new AudioInjectionOptions(); - options.position = palm1Position; - options.volume = speed / 2.0; - if (options.volume > 1.0) options.volume = 1.0; - which = Math.floor((Math.random() * 6) + 1); - if (which == 1) { Audio.playSound(clap1, options); } - else if (which == 2) { Audio.playSound(clap2, options); } - else if (which == 3) { Audio.playSound(clap3, options); } - else if (which == 4) { Audio.playSound(clap4, options); } - else if (which == 5) { Audio.playSound(clap5, options); } - else { Audio.playSound(clap6, options); } - Audio.playSound(clap, options); - clapping[palm] = true; - } else if (clapping[palm] && (speed < (CLAP_SPEED / 4.0))) { - clapping[palm] = false; - } + var frame = Math.floor(animationDetails.frameIndex); + + if (frame != lastAnimFrame) { + lastAnimFrame = frame; } + + for (var i = 0; i < startEndFrames.length; i++) { + if (frame == startEndFrames[i].start && (frame != lastClapFrame)) { + playClap(1.0, Camera.getPosition()); + lastClapFrame = frame; + } + } + + var palm1Position = MyAvatar.getLeftPalmPosition(); + var palm2Position = MyAvatar.getRightPalmPosition(); + var distanceBetween = Vec3.length(Vec3.subtract(palm1Position, palm2Position)); + + var palm1Velocity = Controller.getSpatialControlVelocity(1); + var palm2Velocity = Controller.getSpatialControlVelocity(3); + var closingVelocity = Vec3.length(Vec3.subtract(palm1Velocity, palm2Velocity)); + + const CLAP_SPEED = 0.7; + const CLAP_DISTANCE = 0.15; + + if ((closingVelocity > CLAP_SPEED) && (distanceBetween < CLAP_DISTANCE) && !clappingNow) { + var volume = closingVelocity / 2.0; + if (volume > 1.0) volume = 1.0; + playClap(volume, palm1Position); + clappingNow = true; + } else if (clappingNow && (distanceBetween > CLAP_DISTANCE * 1.2)) { + clappingNow = false; + } +} + +function playClap(volume, position) { + var options = new AudioInjectionOptions(); + options.position = position; + options.volume = 1.0; + var clip = Math.floor(Math.random() * numberOfSounds); + Audio.playSound(claps[clip], options); +} + +var FASTEST_CLAP_INTERVAL = 100.0; +var SLOWEST_CLAP_INTERVAL = 2000.0; + +Controller.keyPressEvent.connect(function(event) { + if(event.text == "SHIFT") { + if (!clickClappingNow) { + clickClappingNow = true; + clickStartTime = new Date(); + playClap(1.0, Camera.getPosition()); + lastClapFrame = 0; + MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false); + } else { + // Adjust animation speed for measured clicking interval + clickEndTime = new Date(); + var milliseconds = clickEndTime - clickStartTime; + clickStartTime = new Date(); + if ((milliseconds < SLOWEST_CLAP_INTERVAL) && (milliseconds > FASTEST_CLAP_INTERVAL)) { + clapRate = ANIMATION_FRAMES_PER_CLAP * (1000.0 / milliseconds); + playClap(1.0, Camera.getPosition()); + MyAvatar.stopAnimation(clapAnimation); + MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false); + } + collectedClicks = collectedClicks + 1; + } + } +}); + +var CLAP_END_WAIT_MSECS = 300; +Controller.keyReleaseEvent.connect(function(event) { + if (event.text == "SHIFT") { + collectedClicks = 0; + if (!startedTimer) { + collectedClicks = 0; + Script.setTimeout(stopClapping, CLAP_END_WAIT_MSECS); + startedTimer = true; + } + } +}); + +function stopClapping() { + if (collectedClicks == 0) { + startedTimer = false; + MyAvatar.stopAnimation(clapAnimation); + clapRate = CLAP_START_RATE; + clickClappingNow = false; + } else { + startedTimer = false; + } } // Connect a call back that happens every frame diff --git a/examples/concertCamera_kims.js b/examples/concertCamera_kims.js new file mode 100644 index 0000000000..3017d3c008 --- /dev/null +++ b/examples/concertCamera_kims.js @@ -0,0 +1,72 @@ +// +// concertCamera.js +// +// Created by Philip Rosedale on June 24, 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Move a camera through a series of pre-set locations by pressing number keys +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var oldMode; +var avatarPosition; + +var cameraNumber = 0; +var freeCamera = false; + +var cameraLocations = [ {x: 8027.5, y: 237.5, z: 7305.7}, {x: 8027.5, y: 237.5, z: 7306.6}, {x: 8027.5, y: 237.5, z: 7308.0}, {x: 8027.5, y: 237.5, z: 7303.0}, {x: 8030.8, y: 238.6, z: 7311.4}, {x: 8030.9, y: 237.1, z: 7308.0} ]; +var cameraLookAts = [ {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7305.7}, {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7304.0} ]; + +function saveCameraState() { + oldMode = Camera.getMode(); + avatarPosition = MyAvatar.position; + Camera.setModeShiftPeriod(0.0); + Camera.setMode("independent"); +} + +function restoreCameraState() { + Camera.stopLooking(); + Camera.setMode(oldMode); +} + +function update(deltaTime) { + if (freeCamera) { + var delta = Vec3.subtract(MyAvatar.position, avatarPosition); + if (Vec3.length(delta) > 0.05) { + cameraNumber = 0; + freeCamera = false; + restoreCameraState(); + } + } +} + +function keyPressEvent(event) { + + var choice = parseInt(event.text); + + if ((choice > 0) && (choice <= cameraLocations.length)) { + print("camera " + choice); + if (!freeCamera) { + saveCameraState(); + freeCamera = true; + } + Camera.setMode("independent"); + Camera.setPosition(cameraLocations[choice - 1]); + Camera.keepLookingAt(cameraLookAts[choice - 1]); + } + if (event.text == "ESC") { + cameraNumber = 0; + freeCamera = false; + restoreCameraState(); + } + if (event.text == "0") { + // Show camera location in log + var cameraLocation = Camera.getPosition(); + print(cameraLocation.x + ", " + cameraLocation.y + ", " + cameraLocation.z); + } +} + +Script.update.connect(update); +Controller.keyPressEvent.connect(keyPressEvent); diff --git a/examples/concertCamera_kyrs.js b/examples/concertCamera_kyrs.js new file mode 100644 index 0000000000..2b37a84f9e --- /dev/null +++ b/examples/concertCamera_kyrs.js @@ -0,0 +1,72 @@ +// +// concertCamera.js +// +// Created by Philip Rosedale on June 24, 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Move a camera through a series of pre-set locations by pressing number keys +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var oldMode; +var avatarPosition; + +var cameraNumber = 0; +var freeCamera = false; + +var cameraLocations = [ {x: 2921.5, y: 251.3, z: 8254.8}, {x: 2921.5, y: 251.3, z: 8254.4}, {x: 2921.5, y: 251.3, z: 8252.2}, {x: 2921.5, y: 251.3, z: 8247.2}, {x: 2921.4, y: 251.3, z: 8255.7} ]; +var cameraLookAts = [ {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.4 , y: 251.3, z: 8255.1} ]; + +function saveCameraState() { + oldMode = Camera.getMode(); + avatarPosition = MyAvatar.position; + Camera.setModeShiftPeriod(0.0); + Camera.setMode("independent"); +} + +function restoreCameraState() { + Camera.stopLooking(); + Camera.setMode(oldMode); +} + +function update(deltaTime) { + if (freeCamera) { + var delta = Vec3.subtract(MyAvatar.position, avatarPosition); + if (Vec3.length(delta) > 0.05) { + cameraNumber = 0; + freeCamera = false; + restoreCameraState(); + } + } +} + +function keyPressEvent(event) { + + var choice = parseInt(event.text); + + if ((choice > 0) && (choice <= cameraLocations.length)) { + print("camera " + choice); + if (!freeCamera) { + saveCameraState(); + freeCamera = true; + } + Camera.setMode("independent"); + Camera.setPosition(cameraLocations[choice - 1]); + Camera.keepLookingAt(cameraLookAts[choice - 1]); + } + if (event.text == "ESC") { + cameraNumber = 0; + freeCamera = false; + restoreCameraState(); + } + if (event.text == "0") { + // Show camera location in log + var cameraLocation = Camera.getPosition(); + print(cameraLocation.x + ", " + cameraLocation.y + ", " + cameraLocation.z); + } +} + +Script.update.connect(update); +Controller.keyPressEvent.connect(keyPressEvent); diff --git a/examples/editModels.js b/examples/editModels.js index 64c203534c..9e1581e205 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -31,8 +31,7 @@ var toolWidth = 50; var LASER_WIDTH = 4; var LASER_COLOR = { red: 255, green: 0, blue: 0 }; -var LASER_LENGTH_FACTOR = 500 -; +var LASER_LENGTH_FACTOR = 500; var MIN_ANGULAR_SIZE = 2; var MAX_ANGULAR_SIZE = 45; diff --git a/examples/flockingBirds.js b/examples/flockingBirds.js index 3fa8681abe..0eb348b7b9 100644 --- a/examples/flockingBirds.js +++ b/examples/flockingBirds.js @@ -31,7 +31,7 @@ var count=0; // iterations var enableFlyTowardPoints = true; // some birds have a point they want to fly to var enabledClustedFlyTowardPoints = true; // the flyToward points will be generally near each other -var flyToFrames = 10; // number of frames the bird would like to attempt to fly to it's flyTo point +var flyToFrames = 100; // number of frames the bird would like to attempt to fly to it's flyTo point var PROBABILITY_OF_FLY_TOWARD_CHANGE = 0.01; // chance the bird will decide to change its flyTo point var PROBABILITY_EACH_BIRD_WILL_FLY_TOWARD = 0.2; // chance the bird will decide to flyTo, otherwise it follows var flyingToCount = 0; // count of birds currently flying to someplace @@ -56,11 +56,11 @@ var PROBABILITY_TO_LEAD = 0.1; // probability a bird will choose to lead var birds = new Array(); // array of bird state data -var flockStartPosition = { x: 100, y: 10, z: 100}; +var flockStartPosition = MyAvatar.position; var flockStartVelocity = { x: 0, y: 0, z: 0}; var flockStartThrust = { x: 0, y: 0, z: 0}; // slightly upward against gravity var INITIAL_XY_VELOCITY_SCALE = 2; -var birdRadius = 0.0625; +var birdRadius = 0.0925; var baseBirdColor = { red: 0, green: 255, blue: 255 }; var glidingColor = { red: 255, green: 0, blue: 0 }; var thrustUpwardColor = { red: 0, green: 255, blue: 0 }; diff --git a/examples/locationsMenu.js b/examples/locationsMenu.js index 6f4a28fe38..24b0dabf46 100644 --- a/examples/locationsMenu.js +++ b/examples/locationsMenu.js @@ -57,7 +57,7 @@ var LocationMenu = function(opts) { y: 0, width: menuWidth + 10, height: (menuHeight * (pageSize + 1)) + 10, - color: { red: 0, green: 0, blue: 0}, + backgroundColor: { red: 0, green: 0, blue: 0}, topMargin: 4, leftMargin: 4, text: "", @@ -71,7 +71,7 @@ var LocationMenu = function(opts) { y: 0, width: menuWidth, height: menuHeight, - color: inactiveColor, + backgroundColor: inactiveColor, topMargin: margin, leftMargin: margin, text: (i == 0) ? "Loading..." : "", @@ -85,7 +85,7 @@ var LocationMenu = function(opts) { y: 0, width: menuWidth / 2, height: menuHeight, - color: disabledColor, + backgroundColor: disabledColor, topMargin: margin, leftMargin: margin, text: "Previous", @@ -97,7 +97,7 @@ var LocationMenu = function(opts) { y: 0, width: menuWidth / 2, height: menuHeight, - color: disabledColor, + backgroundColor: disabledColor, topMargin: margin, leftMargin: margin, text: "Next", @@ -175,10 +175,10 @@ var LocationMenu = function(opts) { if (start + i < this.locations.length) { location = this.locations[start + i]; update.text = (start + i + 1) + ". " + location.username; - update.color = inactiveColor; + update.backgroundColor = inactiveColor; } else { update.text = ""; - update.color = disabledColor; + update.backgroundColor = disabledColor; } Overlays.editOverlay(this.menuItems[i].overlay, update); this.menuItems[i].location = location; @@ -187,8 +187,8 @@ var LocationMenu = function(opts) { this.previousEnabled = pageNumber > 0; this.nextEnabled = pageNumber < (this.numPages - 1); - Overlays.editOverlay(this.previousButton, { color: this.previousEnabled ? prevNextColor : disabledColor}); - Overlays.editOverlay(this.nextButton, { color: this.nextEnabled ? prevNextColor : disabledColor }); + Overlays.editOverlay(this.previousButton, { backgroundColor: this.previousEnabled ? prevNextColor : disabledColor}); + Overlays.editOverlay(this.nextButton, { backgroundColor: this.nextEnabled ? prevNextColor : disabledColor }); } this.mousePressEvent = function(event) { @@ -198,17 +198,17 @@ var LocationMenu = function(opts) { self.toggleMenu(); } else if (clickedOverlay == self.previousButton) { if (self.previousEnabled) { - Overlays.editOverlay(clickedOverlay, { color: activeColor }); + Overlays.editOverlay(clickedOverlay, { backgroundColor: activeColor }); } } else if (clickedOverlay == self.nextButton) { if (self.nextEnabled) { - Overlays.editOverlay(clickedOverlay, { color: activeColor }); + Overlays.editOverlay(clickedOverlay, { backgroundColor: activeColor }); } } else { for (var i = 0; i < self.menuItems.length; i++) { if (clickedOverlay == self.menuItems[i].overlay) { if (self.menuItems[i].location != null) { - Overlays.editOverlay(clickedOverlay, { color: activeColor }); + Overlays.editOverlay(clickedOverlay, { backgroundColor: activeColor }); } break; } @@ -221,19 +221,19 @@ var LocationMenu = function(opts) { if (clickedOverlay == self.previousButton) { if (self.previousEnabled) { - Overlays.editOverlay(clickedOverlay, { color: inactiveColor }); + Overlays.editOverlay(clickedOverlay, { backgroundColor: inactiveColor }); self.goToPage(self.page - 1); } } else if (clickedOverlay == self.nextButton) { if (self.nextEnabled) { - Overlays.editOverlay(clickedOverlay, { color: inactiveColor }); + Overlays.editOverlay(clickedOverlay, { backgroundColor: inactiveColor }); self.goToPage(self.page + 1); } } else { for (var i = 0; i < self.menuItems.length; i++) { if (clickedOverlay == self.menuItems[i].overlay) { if (self.menuItems[i].location != null) { - Overlays.editOverlay(clickedOverlay, { color: inactiveColor }); + Overlays.editOverlay(clickedOverlay, { backgroundColor: inactiveColor }); var location = self.menuItems[i].location; Window.location = "hifi://" + location.domain + "/" + location.x + "," + location.y + "," + location.z; diff --git a/examples/sit.js b/examples/sit.js index 056a65fbf1..c157d4854d 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -43,7 +43,10 @@ var animationLenght = 2.0; var avatarOldPosition = { x: 0, y: 0, z: 0 }; -var sitting = false; +var sittingSettingsHandle = "SitJsSittingPosition"; +var sitting = Settings.getValue(sittingSettingsHandle, false) == "true"; +print("Original sitting status: " + sitting); +var frame = 0; var seat = new Object(); var hiddingSeats = false; @@ -123,10 +126,12 @@ var goToSeatAnimation = function(deltaTime) { function sitDown() { sitting = true; + Settings.setValue(sittingSettingsHandle, sitting); + print("sitDown sitting status: " + Settings.getValue(sittingSettingsHandle, false)); passedTime = 0.0; startPosition = MyAvatar.position; storeStartPoseAndTransition(); - try{ + try { Script.update.disconnect(standingUpAnimation); } catch(e){ // no need to handle. if it wasn't connected no harm done @@ -138,6 +143,8 @@ function sitDown() { function standUp() { sitting = false; + Settings.setValue(sittingSettingsHandle, sitting); + print("standUp sitting status: " + Settings.getValue(sittingSettingsHandle, false)); passedTime = 0.0; startPosition = MyAvatar.position; try{ @@ -159,14 +166,16 @@ function SeatIndicator(modelProperties, seatIndex) { modelProperties.sittingPoints[seatIndex].rotation); this.scale = MyAvatar.scale / 12; - this.sphere = Overlays.addOverlay("sphere", { - position: this.position, - size: this.scale, - solid: true, - color: { red: 0, green: 0, blue: 255 }, - alpha: 0.3, - visible: true - }); + this.sphere = Overlays.addOverlay("billboard", { + subImage: { x: 0, y: buttonHeight, width: buttonWidth, height: buttonHeight}, + url: buttonImageUrl, + position: this.position, + scale: this.scale * 4, + solid: true, + color: { red: 0, green: 0, blue: 255 }, + alpha: 0.3, + visible: true + }); this.show = function(doShow) { Overlays.editOverlay(this.sphere, { visible: doShow }); @@ -218,33 +227,6 @@ Controller.mousePressEvent.connect(function(event) { try{ Script.update.disconnect(sittingDownAnimation); } catch(e){} Script.update.connect(goToSeatAnimation); } - - - - return; - var intersection = Models.findRayIntersection(pickRay); - - if (intersection.accurate && intersection.intersects && false) { - var properties = intersection.modelProperties; - print("Intersecting with model, let's check for seats."); - - if (properties.sittingPoints.length > 0) { - print("Available seats, going to the first one: " + properties.sittingPoints[0].name); - seat.position = Vec3.sum(properties.position, Vec3.multiplyQbyV(properties.modelRotation, properties.sittingPoints[0].position)); - Vec3.print("Seat position: ", seat.position); - seat.rotation = Quat.multiply(properties.modelRotation, properties.sittingPoints[0].rotation); - Quat.print("Seat rotation: ", seat.rotation); - - passedTime = 0.0; - startPosition = MyAvatar.position; - startRotation = MyAvatar.orientation; - try{ Script.update.disconnect(standingUpAnimation); } catch(e){} - try{ Script.update.disconnect(sittingDownAnimation); } catch(e){} - Script.update.connect(goToSeatAnimation); - } else { - print ("Sorry, no seats here."); - } - } } }) @@ -257,13 +239,29 @@ function update(deltaTime){ Overlays.editOverlay( standUpButton, {x: newX, y: newY} ); Overlays.editOverlay( sitDownButton, {x: newX, y: newY} ); } + + // For a weird reason avatar joint don't update till the 10th frame + // Set the update frame to 20 to be safe + var UPDATE_FRAME = 20; + if (frame <= UPDATE_FRAME) { + if (frame == UPDATE_FRAME) { + if (sitting == true) { + print("Was seated: " + sitting); + storeStartPoseAndTransition(); + updateJoints(1.0); + Overlays.editOverlay(sitDownButton, { visible: false }); + Overlays.editOverlay(standUpButton, { visible: true }); + } + } + frame++; + } if (MyAvatar.position.x != avatarOldPosition.x && MyAvatar.position.y != avatarOldPosition.y && MyAvatar.position.z != avatarOldPosition.z) { avatarOldPosition = MyAvatar.position; - var SEARCH_RADIUS = 5; + var SEARCH_RADIUS = 10; var foundModels = Models.findModels(MyAvatar.position, SEARCH_RADIUS); // Let's remove indicator that got out of radius for (model in models) { diff --git a/examples/squeezeHands.js b/examples/squeezeHands.js index da720734e1..f920692a3b 100644 --- a/examples/squeezeHands.js +++ b/examples/squeezeHands.js @@ -18,13 +18,16 @@ var RIGHT = 1; var lastLeftFrame = 0; var lastRightFrame = 0; -var LAST_FRAME = 11.0; // What is the number of the last frame we want to use in the animation? -var SMOOTH_FACTOR = 0.80; +var leftDirection = true; +var rightDirection = true; +var LAST_FRAME = 15.0; // What is the number of the last frame we want to use in the animation? +var SMOOTH_FACTOR = 0.0; +var MAX_FRAMES = 30.0; Script.update.connect(function(deltaTime) { - var leftTriggerValue = Math.sqrt(Controller.getTriggerValue(LEFT)); - var rightTriggerValue = Math.sqrt(Controller.getTriggerValue(RIGHT)); + var leftTriggerValue = Controller.getTriggerValue(LEFT); + var rightTriggerValue = Controller.getTriggerValue(RIGHT); var leftFrame, rightFrame; @@ -32,10 +35,31 @@ Script.update.connect(function(deltaTime) { leftFrame = (leftTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastLeftFrame * SMOOTH_FACTOR; rightFrame = (rightTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastRightFrame * SMOOTH_FACTOR; - + if (!leftDirection) { + leftFrame = MAX_FRAMES - leftFrame; + } + if (!rightDirection) { + rightFrame = MAX_FRAMES - rightFrame; + } + + if ((leftTriggerValue == 1.0) && (leftDirection == true)) { + leftDirection = false; + lastLeftFrame = MAX_FRAMES - leftFrame; + } else if ((leftTriggerValue == 0.0) && (leftDirection == false)) { + leftDirection = true; + lastLeftFrame = leftFrame; + } + if ((rightTriggerValue == 1.0) && (rightDirection == true)) { + rightDirection = false; + lastRightFrame = MAX_FRAMES - rightFrame; + } else if ((rightTriggerValue == 0.0) && (rightDirection == false)) { + rightDirection = true; + lastRightFrame = rightFrame; + } + if ((leftFrame != lastLeftFrame) && leftHandAnimation.length){ MyAvatar.stopAnimation(leftHandAnimation); - MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame); + MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame); } if ((rightFrame != lastRightFrame) && rightHandAnimation.length) { MyAvatar.stopAnimation(rightHandAnimation); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 81071abd94..a5d1b0193e 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -113,16 +113,6 @@ if (APPLE) SET(INTERFACE_SRCS ${INTERFACE_SRCS} "${CMAKE_CURRENT_SOURCE_DIR}/interface.icns") endif() -# RtMidi for scripted MIDI control -find_package(RtMidi) - -if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI) - add_definitions(-DHAVE_RTMIDI) - include_directories(SYSTEM ${RTMIDI_INCLUDE_DIR}) - - set(INTERFACE_SRCS ${INTERFACE_SRCS} "${RTMIDI_CPP}") -endif () - # create the executable, make it a bundle on OS X add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM}) @@ -154,6 +144,7 @@ find_package(Visage) find_package(LeapMotion) find_package(ZLIB) find_package(Qxmpp) +find_package(RtMidi) # include the Sixense library for Razer Hydra if available if (SIXENSE_FOUND AND NOT DISABLE_SIXENSE) @@ -237,11 +228,18 @@ if (QXMPP_FOUND AND NOT DISABLE_QXMPP) target_link_libraries(${TARGET_NAME} "${QXMPP_LIBRARY}") endif (QXMPP_FOUND AND NOT DISABLE_QXMPP) -# link CoreMIDI if we're using RtMidi -if (RTMIDI_FOUND AND APPLE) - find_library(CoreMIDI CoreMIDI) - add_definitions(-D__MACOSX_CORE__) - target_link_libraries(${TARGET_NAME} ${CoreMIDI}) +# and with RtMidi for RtMidi control +if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI) + + add_definitions(-DHAVE_RTMIDI) + include_directories(SYSTEM ${RTMIDI_INCLUDE_DIR}) + target_link_libraries(${TARGET_NAME} "${RTMIDI_LIBRARY}") + + if (APPLE) + find_library(CoreMIDI CoreMIDI) + add_definitions(-D__MACOSX_CORE__) + target_link_libraries(${TARGET_NAME} ${CoreMIDI}) + endif() endif() # include headers for interface and InterfaceConfig. diff --git a/interface/external/rtmidi/readme.txt b/interface/external/rtmidi/readme.txt index d83d0c293e..d0480fce4a 100644 --- a/interface/external/rtmidi/readme.txt +++ b/interface/external/rtmidi/readme.txt @@ -7,7 +7,9 @@ Stephen Birarda, June 30, 2014 2. Copy RtMidi.h to externals/rtmidi/include. -3. Copy RtMidi.cpp to externals/rtmidi/src +3. Compile the RtMidi library. + +3. Copy either librtmidi.dylib (dynamic) or librtmidi.a (static) to externals/rtmidi/lib 4. Delete your build directory, run cmake and build, and you should be all set. diff --git a/interface/interface.icns b/interface/interface.icns index f32590eb21..6e7a748d4d 100644 Binary files a/interface/interface.icns and b/interface/interface.icns differ diff --git a/interface/resources/shaders/model.frag b/interface/resources/shaders/model.frag index 488736abf9..468a892686 100644 --- a/interface/resources/shaders/model.frag +++ b/interface/resources/shaders/model.frag @@ -11,9 +11,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + // the diffuse texture uniform sampler2D diffuseMap; +// local lights +const int MAX_LOCAL_LIGHTS = 2; // 2 lights for now, will probably need more later on +uniform int numLocalLights; +uniform vec3 localLightDirections[MAX_LOCAL_LIGHTS]; +uniform vec3 localLightColors[MAX_LOCAL_LIGHTS]; + // the interpolated position varying vec4 position; @@ -25,8 +32,19 @@ void main(void) { vec4 normalizedNormal = normalize(normal); float diffuse = dot(normalizedNormal, gl_LightSource[0].position); float facingLight = step(0.0, diffuse); + + // the local light that is always present + vec4 totalLocalLight = vec4(0.0, 0.0, 0.0, 1.0); + for (int i = 0; i < numLocalLights; i++) { + float localDiffuse = dot(normalizedNormal, vec4(localLightDirections[i], 1.0)); + float localLight = step(0.0, localDiffuse); + float localLightVal = localDiffuse * localLight; + + totalLocalLight += (localLightVal * vec4( localLightColors[i], 0.0)); + } + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + - gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight) + totalLocalLight); // compute the specular component (sans exponent) float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - normalize(vec4(position.xyz, 0.0))), diff --git a/interface/resources/shaders/model.vert b/interface/resources/shaders/model.vert index da7e9640d9..13eee2fa3d 100644 --- a/interface/resources/shaders/model.vert +++ b/interface/resources/shaders/model.vert @@ -11,6 +11,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +const int MAX_LOCAL_LIGHTS = 4; + // the interpolated position varying vec4 position; @@ -37,3 +39,4 @@ void main(void) { // use standard pipeline transform gl_Position = ftransform(); } + diff --git a/interface/resources/shaders/model_normal_map.frag b/interface/resources/shaders/model_normal_map.frag index 8444f2d6ea..ca0201f6ab 100644 --- a/interface/resources/shaders/model_normal_map.frag +++ b/interface/resources/shaders/model_normal_map.frag @@ -37,9 +37,14 @@ void main(void) { normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); float diffuse = dot(viewNormal, gl_LightSource[0].position); float facingLight = step(0.0, diffuse); + float localDiffuse = dot(viewNormal, gl_LightSource[1].position); + float localLight = step(0.0, localDiffuse); vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + - gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight) + gl_FrontLightProduct[1].diffuse * (localDiffuse * localLight)); + + + // compute the specular component (sans exponent) float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - normalize(vec4(vec3(interpolatedPosition), 0.0))), viewNormal)); diff --git a/interface/resources/shaders/model_specular_map.frag b/interface/resources/shaders/model_specular_map.frag index a07324cd1b..329da65e9e 100644 --- a/interface/resources/shaders/model_specular_map.frag +++ b/interface/resources/shaders/model_specular_map.frag @@ -10,6 +10,11 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +const int MAX_LOCAL_LIGHTS = 2; + +uniform int numLocalLights; +uniform vec3 localLightDirections[MAX_LOCAL_LIGHTS]; +uniform vec3 localLightColors[MAX_LOCAL_LIGHTS]; // the diffuse texture uniform sampler2D diffuseMap; @@ -28,8 +33,19 @@ void main(void) { vec4 normalizedNormal = normalize(normal); float diffuse = dot(normalizedNormal, gl_LightSource[0].position); float facingLight = step(0.0, diffuse); + + // the local light that is always present + vec4 totalLocalLight = vec4(0.0, 0.0, 0.0, 1.0); + for (int i = 0; i < numLocalLights; i++) { + float localDiffuse = dot(normalizedNormal, vec4(localLightDirections[i], 1.0)); + float localLight = step(0.0, localDiffuse); + float localLightVal = localDiffuse * localLight; + + totalLocalLight += (localLightVal * vec4( localLightColors[i], 0.0)); + } + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + - gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight) + totalLocalLight); // compute the specular component (sans exponent) float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - normalize(vec4(position.xyz, 0.0))), @@ -38,4 +54,5 @@ void main(void) { // modulate texture by base color and add specular contribution gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0); + } diff --git a/interface/resources/shaders/skin_model.vert b/interface/resources/shaders/skin_model.vert index d68347d33d..943daf9061 100644 --- a/interface/resources/shaders/skin_model.vert +++ b/interface/resources/shaders/skin_model.vert @@ -34,6 +34,7 @@ void main(void) { position += clusterMatrix * gl_Vertex * clusterWeight; normal += clusterMatrix * vec4(gl_Normal, 0.0) * clusterWeight; } + position = gl_ModelViewMatrix * position; normal = normalize(gl_ModelViewMatrix * normal); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0eb108e982..f9dcf12c10 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -174,7 +174,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _runningScriptsWidget(NULL), _runningScriptsWidgetWasVisible(false), _trayIcon(new QSystemTrayIcon(_window)), - _lastNackTime(usecTimestampNow()) + _lastNackTime(usecTimestampNow()), + _lastSendDownstreamAudioStats(usecTimestampNow()) { // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); @@ -318,12 +319,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - - // Make sure cache on same thread than its parent (NetworkAccessManager) QNetworkDiskCache* cache = new QNetworkDiskCache(); - cache->moveToThread(networkAccessManager.thread()); - cache->setParent(&networkAccessManager); - cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache"); networkAccessManager.setCache(cache); @@ -361,6 +357,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // Set the sixense filtering _sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense)); + + // Set hand controller velocity filtering + _sixenseManager.setLowVelocityFilter(Menu::getInstance()->isOptionChecked(MenuOption::LowVelocityFilter)); checkVersion(); @@ -608,9 +607,19 @@ void Application::paintGL() { } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _myCamera.setTightness(0.0f); - _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _scaleMirror); - _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); - _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition() + glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0)); + //Only behave like a true mirror when in the OR + if (OculusManager::isConnected()) { + _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _scaleMirror); + _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); + _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition() + glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0)); + } else { + _myCamera.setTightness(0.0f); + glm::vec3 eyePosition = _myAvatar->getHead()->calculateAverageEyePosition(); + float headHeight = eyePosition.y - _myAvatar->getPosition().y; + _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _scaleMirror); + _myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight + (_raiseMirror * _myAvatar->getScale()), 0)); + _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); + } } // Update camera position @@ -689,13 +698,10 @@ void Application::paintGL() { } { - PerformanceTimer perfTimer("paintGL/renderOverlay"); - //If alpha is 1, we can render directly to the screen. - if (_applicationOverlay.getAlpha() == 1.0f) { - _applicationOverlay.renderOverlay(); - } else { - //Render to to texture so we can fade it - _applicationOverlay.renderOverlay(true); + PerformanceTimer perfTimer("renderOverlay"); + // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() + _applicationOverlay.renderOverlay(true); + if (Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) { _applicationOverlay.displayOverlayTexture(); } } @@ -1017,6 +1023,9 @@ void Application::keyPressEvent(QKeyEvent* event) { Menu::getInstance()->triggerOption(MenuOption::FullscreenMirror); } break; + case Qt::Key_Slash: + Menu::getInstance()->triggerOption(MenuOption::UserInterface); + break; case Qt::Key_F: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::DisplayFrustum); @@ -1036,7 +1045,7 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; break; - case Qt::Key_Slash: + case Qt::Key_Percent: Menu::getInstance()->triggerOption(MenuOption::Stats); break; case Qt::Key_Plus: @@ -1362,18 +1371,18 @@ void Application::idle() { if (timeSinceLastUpdate > IDLE_SIMULATE_MSECS) { _lastTimeUpdated.start(); { - PerformanceTimer perfTimer("idle/update"); + PerformanceTimer perfTimer("update"); PerformanceWarning warn(showWarnings, "Application::idle()... update()"); const float BIGGEST_DELTA_TIME_SECS = 0.25f; update(glm::clamp((float)timeSinceLastUpdate / 1000.f, 0.f, BIGGEST_DELTA_TIME_SECS)); } { - PerformanceTimer perfTimer("idle/updateGL"); + PerformanceTimer perfTimer("updateGL"); PerformanceWarning warn(showWarnings, "Application::idle()... updateGL()"); _glWidget->updateGL(); } { - PerformanceTimer perfTimer("idle/rest"); + PerformanceTimer perfTimer("rest"); PerformanceWarning warn(showWarnings, "Application::idle()... rest of it"); _idleLoopStdev.addValue(timeSinceLastUpdate); @@ -1385,7 +1394,7 @@ void Application::idle() { } if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) { - PerformanceTimer perfTimer("idle/rest/_buckyBalls"); + PerformanceTimer perfTimer("buckyBalls"); _buckyBalls.simulate(timeSinceLastUpdate / 1000.f, Application::getInstance()->getAvatar()->getHandData()); } @@ -1433,6 +1442,10 @@ void Application::setRenderVoxels(bool voxelRender) { } } +void Application::setLowVelocityFilter(bool lowVelocityFilter) { + getSixenseManager()->setLowVelocityFilter(lowVelocityFilter); +} + void Application::doKillLocalVoxels() { _wantToKillLocalVoxels = true; } @@ -1801,7 +1814,7 @@ bool Application::isLookingAtMyAvatar(Avatar* avatar) { } void Application::updateLOD() { - PerformanceTimer perfTimer("idle/update/updateLOD"); + PerformanceTimer perfTimer("LOD"); // adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableAutoAdjustLOD) && !isThrottleRendering()) { Menu::getInstance()->autoAdjustLOD(_fps); @@ -1811,7 +1824,7 @@ void Application::updateLOD() { } void Application::updateMouseRay() { - PerformanceTimer perfTimer("idle/update/updateMouseRay"); + PerformanceTimer perfTimer("mouseRay"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMouseRay()"); @@ -1844,8 +1857,6 @@ void Application::updateMouseRay() { } void Application::updateFaceshift() { - PerformanceTimer perfTimer("idle/update/updateFaceshift"); - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateFaceshift()"); @@ -1859,8 +1870,6 @@ void Application::updateFaceshift() { } void Application::updateVisage() { - PerformanceTimer perfTimer("idle/update/updateVisage"); - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateVisage()"); @@ -1869,11 +1878,11 @@ void Application::updateVisage() { } void Application::updateMyAvatarLookAtPosition() { - PerformanceTimer perfTimer("idle/update/updateMyAvatarLookAtPosition"); - + PerformanceTimer perfTimer("lookAt"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()"); + _myAvatar->updateLookAtTargetAvatar(); FaceTracker* tracker = getActiveFaceTracker(); bool isLookingAtSomeone = false; @@ -1936,7 +1945,7 @@ void Application::updateMyAvatarLookAtPosition() { } void Application::updateThreads(float deltaTime) { - PerformanceTimer perfTimer("idle/update/updateThreads"); + PerformanceTimer perfTimer("updateThreads"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateThreads()"); @@ -1951,7 +1960,7 @@ void Application::updateThreads(float deltaTime) { } void Application::updateMetavoxels(float deltaTime) { - PerformanceTimer perfTimer("idle/update/updateMetavoxels"); + PerformanceTimer perfTimer("updateMetavoxels"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMetavoxels()"); @@ -1981,7 +1990,7 @@ void Application::cameraMenuChanged() { } void Application::updateCamera(float deltaTime) { - PerformanceTimer perfTimer("idle/update/updateCamera"); + PerformanceTimer perfTimer("updateCamera"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateCamera()"); @@ -1999,7 +2008,7 @@ void Application::updateCamera(float deltaTime) { } void Application::updateDialogs(float deltaTime) { - PerformanceTimer perfTimer("idle/update/updateDialogs"); + PerformanceTimer perfTimer("updateDialogs"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateDialogs()"); @@ -2016,7 +2025,7 @@ void Application::updateDialogs(float deltaTime) { } void Application::updateCursor(float deltaTime) { - PerformanceTimer perfTimer("idle/update/updateCursor"); + PerformanceTimer perfTimer("updateCursor"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateCursor()"); @@ -2041,34 +2050,24 @@ void Application::updateCursor(float deltaTime) { } void Application::update(float deltaTime) { - //PerformanceTimer perfTimer("idle/update"); // NOTE: we track this above in Application::idle() - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::update()"); updateLOD(); updateMouseRay(); // check what's under the mouse and update the mouse voxel - - DeviceTracker::updateAll(); - - updateFaceshift(); - updateVisage(); - { - PerformanceTimer perfTimer("idle/update/updateLookAtTargetAvatar"); - _myAvatar->updateLookAtTargetAvatar(); - } - updateMyAvatarLookAtPosition(); - { - PerformanceTimer perfTimer("idle/update/sixense,joystick,prioVR"); + PerformanceTimer perfTimer("devices"); + DeviceTracker::updateAll(); + updateFaceshift(); + updateVisage(); _sixenseManager.update(deltaTime); _joystickManager.update(); _prioVR.update(deltaTime); } - { - PerformanceTimer perfTimer("idle/update/updateMyAvatar"); + PerformanceTimer perfTimer("myAvatar"); + updateMyAvatarLookAtPosition(); updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes } @@ -2078,54 +2077,48 @@ void Application::update(float deltaTime) { updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... - { - PerformanceTimer perfTimer("idle/update/_avatarManager"); - _avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them... - } + _avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them... + updateMetavoxels(deltaTime); // update metavoxels updateCamera(deltaTime); // handle various camera tweaks like off axis projection updateDialogs(deltaTime); // update various stats dialogs if present updateCursor(deltaTime); // Handle cursor updates { - PerformanceTimer perfTimer("idle/update/_particles"); + PerformanceTimer perfTimer("particles"); _particles.update(); // update the particles... - } - { - PerformanceTimer perfTimer("idle/update/_particleCollisionSystem"); - _particleCollisionSystem.update(); // collide the particles... + { + PerformanceTimer perfTimer("collisions"); + _particleCollisionSystem.update(); // collide the particles... + } } { - PerformanceTimer perfTimer("idle/update/_models"); + PerformanceTimer perfTimer("models"); _models.update(); // update the models... } { - PerformanceTimer perfTimer("idle/update/_overlays"); + PerformanceTimer perfTimer("overlays"); _overlays.update(deltaTime); } { - PerformanceTimer perfTimer("idle/update/emit simulating"); + PerformanceTimer perfTimer("emitSimulating"); // let external parties know we're updating emit simulating(deltaTime); } } void Application::updateMyAvatar(float deltaTime) { - PerformanceTimer perfTimer("updateMyAvatar"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMyAvatar()"); - { - PerformanceTimer perfTimer("updateMyAvatar/_myAvatar->update()"); - _myAvatar->update(deltaTime); - } + _myAvatar->update(deltaTime); { // send head/hand data to the avatar mixer and voxel server - PerformanceTimer perfTimer("updateMyAvatar/sendToAvatarMixer"); + PerformanceTimer perfTimer("send"); QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarData); packet.append(_myAvatar->toByteArray()); controlledBroadcastToNodes(packet, NodeSet() << NodeType::AvatarMixer); @@ -2138,14 +2131,15 @@ void Application::updateMyAvatar(float deltaTime) { // actually need to calculate the view frustum planes to send these details // to the server. { - PerformanceTimer perfTimer("updateMyAvatar/loadViewFrustum"); + PerformanceTimer perfTimer("loadViewFrustum"); loadViewFrustum(_myCamera, _viewFrustum); } + quint64 now = usecTimestampNow(); + // Update my voxel servers with my current voxel query... { - PerformanceTimer perfTimer("updateMyAvatar/queryOctree"); - quint64 now = usecTimestampNow(); + PerformanceTimer perfTimer("queryOctree"); quint64 sinceLastQuery = now - _lastQueriedTime; const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; @@ -2163,7 +2157,6 @@ void Application::updateMyAvatar(float deltaTime) { // sent nack packets containing missing sequence numbers of received packets from nodes { - quint64 now = usecTimestampNow(); quint64 sinceLastNack = now - _lastNackTime; const quint64 TOO_LONG_SINCE_LAST_NACK = 1 * USECS_PER_SECOND; if (sinceLastNack > TOO_LONG_SINCE_LAST_NACK) { @@ -2171,6 +2164,15 @@ void Application::updateMyAvatar(float deltaTime) { sendNackPackets(); } } + + { + quint64 sinceLastNack = now - _lastSendDownstreamAudioStats; + if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) { + _lastSendDownstreamAudioStats = now; + + QMetaObject::invokeMethod(&_audio, "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); + } + } } int Application::sendNackPackets() { @@ -2477,7 +2479,7 @@ glm::vec3 Application::getSunDirection() { } void Application::updateShadowMap() { - PerformanceTimer perfTimer("paintGL/updateShadowMap"); + PerformanceTimer perfTimer("shadowMap"); QOpenGLFramebufferObject* fbo = _textureCache.getShadowFramebufferObject(); fbo->bind(); glEnable(GL_DEPTH_TEST); @@ -2639,7 +2641,7 @@ QImage Application::renderAvatarBillboard() { } void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { - PerformanceTimer perfTimer("paintGL/displaySide"); + PerformanceTimer perfTimer("display"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()"); // transform by eye offset @@ -2673,7 +2675,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // Setup 3D lights (after the camera transform, so that they are positioned in world space) { - PerformanceTimer perfTimer("paintGL/displaySide/setupWorldLight"); + PerformanceTimer perfTimer("lights"); setupWorldLight(); } @@ -2692,7 +2694,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { } if (!selfAvatarOnly && Menu::getInstance()->isOptionChecked(MenuOption::Stars)) { - PerformanceTimer perfTimer("paintGL/displaySide/stars"); + PerformanceTimer perfTimer("stars"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... stars..."); if (!_stars.isStarsLoaded()) { @@ -2721,7 +2723,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // draw the sky dome if (!selfAvatarOnly && Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) { - PerformanceTimer perfTimer("paintGL/displaySide/atmosphere"); + PerformanceTimer perfTimer("atmosphere"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... atmosphere..."); _environment.renderAtmospheres(whichCamera); @@ -2742,13 +2744,13 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // draw the audio reflector overlay { - PerformanceTimer perfTimer("paintGL/displaySide/audioReflector"); + PerformanceTimer perfTimer("audio"); _audioReflector.render(); } // Draw voxels if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { - PerformanceTimer perfTimer("paintGL/displaySide/voxels"); + PerformanceTimer perfTimer("voxels"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... voxels..."); _voxels.render(); @@ -2756,14 +2758,14 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // also, metavoxels if (Menu::getInstance()->isOptionChecked(MenuOption::Metavoxels)) { - PerformanceTimer perfTimer("paintGL/displaySide/metavoxels"); + PerformanceTimer perfTimer("metavoxels"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... metavoxels..."); _metavoxels.render(); } if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) { - PerformanceTimer perfTimer("paintGL/displaySide/buckyBalls"); + PerformanceTimer perfTimer("buckyBalls"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... bucky balls..."); _buckyBalls.render(); @@ -2771,7 +2773,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // render particles... if (Menu::getInstance()->isOptionChecked(MenuOption::Particles)) { - PerformanceTimer perfTimer("paintGL/displaySide/particles"); + PerformanceTimer perfTimer("particles"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... particles..."); _particles.render(); @@ -2779,7 +2781,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // render models... if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) { - PerformanceTimer perfTimer("paintGL/displaySide/models"); + PerformanceTimer perfTimer("models"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... models..."); _models.render(); @@ -2787,7 +2789,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // render the ambient occlusion effect if enabled if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) { - PerformanceTimer perfTimer("paintGL/displaySide/AmbientOcclusion"); + PerformanceTimer perfTimer("ambientOcclusion"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... AmbientOcclusion..."); _ambientOcclusionEffect.render(); @@ -2802,20 +2804,22 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { bool mirrorMode = (whichCamera.getInterpolatedMode() == CAMERA_MODE_MIRROR); { - PerformanceTimer perfTimer("paintGL/displaySide/renderAvatars"); + PerformanceTimer perfTimer("avatars"); + _avatarManager.renderAvatars(mirrorMode ? Avatar::MIRROR_RENDER_MODE : Avatar::NORMAL_RENDER_MODE, selfAvatarOnly); } if (!selfAvatarOnly) { // Render the world box - if (whichCamera.getMode() != CAMERA_MODE_MIRROR && Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { - PerformanceTimer perfTimer("paintGL/displaySide/renderWorldBox"); + if (whichCamera.getMode() != CAMERA_MODE_MIRROR && Menu::getInstance()->isOptionChecked(MenuOption::Stats) && + Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) { + PerformanceTimer perfTimer("worldBox"); renderWorldBox(); } // view frustum for debugging if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayFrustum) && whichCamera.getMode() != CAMERA_MODE_MIRROR) { - PerformanceTimer perfTimer("paintGL/displaySide/ViewFrustum"); + PerformanceTimer perfTimer("viewFrustum"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... renderViewFrustum..."); renderViewFrustum(_viewFrustum); @@ -2823,7 +2827,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // render voxel fades if they exist if (_voxelFades.size() > 0) { - PerformanceTimer perfTimer("paintGL/displaySide/voxel fades"); + PerformanceTimer perfTimer("voxelFades"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... voxel fades..."); _voxelFadesLock.lockForWrite(); @@ -2840,13 +2844,13 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // give external parties a change to hook in { - PerformanceTimer perfTimer("paintGL/displaySide/inWorldInterface"); + PerformanceTimer perfTimer("inWorldInterface"); emit renderingInWorldInterface(); } // render JS/scriptable overlays { - PerformanceTimer perfTimer("paintGL/displaySide/3dOverlays"); + PerformanceTimer perfTimer("3dOverlays"); _overlays.render3D(); } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 11f406abf0..d956a949ac 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -125,6 +125,8 @@ static const float MIRROR_REARVIEW_DISTANCE = 0.65f; static const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f; static const float MIRROR_FIELD_OF_VIEW = 30.0f; +static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND; + class Application : public QApplication { Q_OBJECT @@ -317,6 +319,7 @@ public slots: void nudgeVoxelsByVector(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec); void setRenderVoxels(bool renderVoxels); + void setLowVelocityFilter(bool lowVelocityFilter); void doKillLocalVoxels(); void loadDialog(); void loadScriptURLDialog(); @@ -585,6 +588,7 @@ private: QSystemTrayIcon* _trayIcon; quint64 _lastNackTime; + quint64 _lastSendDownstreamAudioStats; }; #endif // hifi_Application_h diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 6bbd769d25..3689ff0143 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -48,9 +48,18 @@ static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_ static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300; +// audio frames time gap stats (min/max/avg) for last ~30 seconds are recalculated every ~1 second +static const int TIME_GAPS_STATS_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS; +static const int TIME_GAP_STATS_WINDOW_INTERVALS = 30; + +// incoming sequence number stats history will cover last 30s +static const int INCOMING_SEQ_STATS_HISTORY_LENGTH = INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS / + (TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS / USECS_PER_SECOND); + // Mute icon configration static const int MUTE_ICON_SIZE = 24; + Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) : AbstractAudioInterface(parent), _audioInput(NULL), @@ -67,7 +76,7 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) : _proceduralAudioOutput(NULL), _proceduralOutputDevice(NULL), _inputRingBuffer(0), - _ringBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO), + _ringBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO, false, 100), _isStereoInput(false), _averagedLatency(0.0), _measuredJitter(0), @@ -103,8 +112,12 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) : _scopeInput(0), _scopeOutputLeft(0), _scopeOutputRight(0), - _audioMixerAvatarStreamStats(), - _outgoingAvatarAudioSequenceNumber(0) + _audioMixerAvatarStreamAudioStats(), + _outgoingAvatarAudioSequenceNumber(0), + _incomingMixedAudioSequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH), + _interframeTimeGapStats(TIME_GAPS_STATS_INTERVAL_SAMPLES, TIME_GAP_STATS_WINDOW_INTERVALS), + _starveCount(0), + _consecutiveNotMixedCount(0) { // clear the array of locally injected samples memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); @@ -120,8 +133,14 @@ void Audio::init(QGLWidget *parent) { void Audio::reset() { _ringBuffer.reset(); + + _starveCount = 0; + _consecutiveNotMixedCount = 0; + + _audioMixerAvatarStreamAudioStats = AudioStreamStats(); + _audioMixerInjectedStreamAudioStatsMap.clear(); + _outgoingAvatarAudioSequenceNumber = 0; - _audioMixerInjectedStreamStatsMap.clear(); _incomingMixedAudioSequenceNumberStats.reset(); } @@ -689,7 +708,9 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { _totalPacketsReceived++; - double timeDiff = (double)_timeSinceLastReceived.nsecsElapsed() / 1000000.0; // ns to ms + double timeDiff = (double)_timeSinceLastReceived.nsecsElapsed() / 1000.0; // ns to us + _interframeTimeGapStats.update((quint64)timeDiff); + timeDiff /= USECS_PER_MSEC; // us to ms _timeSinceLastReceived.start(); // Discard first few received packets for computing jitter (often they pile up on start) @@ -726,7 +747,7 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { quint8 appendFlag = *(reinterpret_cast(dataAt)); dataAt += sizeof(quint8); if (!appendFlag) { - _audioMixerInjectedStreamStatsMap.clear(); + _audioMixerInjectedStreamAudioStatsMap.clear(); } // parse the number of stream stats structs to follow @@ -740,13 +761,72 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { dataAt += sizeof(AudioStreamStats); if (streamStats._streamType == PositionalAudioRingBuffer::Microphone) { - _audioMixerAvatarStreamStats = streamStats; + _audioMixerAvatarStreamAudioStats = streamStats; } else { - _audioMixerInjectedStreamStatsMap[streamStats._streamIdentifier] = streamStats; + _audioMixerInjectedStreamAudioStatsMap[streamStats._streamIdentifier] = streamStats; } } } +AudioStreamStats Audio::getDownstreamAudioStreamStats() const { + + AudioStreamStats stats; + stats._streamType = PositionalAudioRingBuffer::Microphone; + + stats._timeGapMin = _interframeTimeGapStats.getMin(); + stats._timeGapMax = _interframeTimeGapStats.getMax(); + stats._timeGapAverage = _interframeTimeGapStats.getAverage(); + stats._timeGapWindowMin = _interframeTimeGapStats.getWindowMin(); + stats._timeGapWindowMax = _interframeTimeGapStats.getWindowMax(); + stats._timeGapWindowAverage = _interframeTimeGapStats.getWindowAverage(); + + stats._ringBufferFramesAvailable = _ringBuffer.framesAvailable(); + stats._ringBufferCurrentJitterBufferFrames = 0; + stats._ringBufferDesiredJitterBufferFrames = getDesiredJitterBufferFrames(); + stats._ringBufferStarveCount = _starveCount; + stats._ringBufferConsecutiveNotMixedCount = _consecutiveNotMixedCount; + stats._ringBufferOverflowCount = _ringBuffer.getOverflowCount(); + stats._ringBufferSilentFramesDropped = 0; + + stats._packetStreamStats = _incomingMixedAudioSequenceNumberStats.getStats(); + stats._packetStreamWindowStats = _incomingMixedAudioSequenceNumberStats.getStatsForHistoryWindow(); + + return stats; +} + +void Audio::sendDownstreamAudioStatsPacket() { + + // push the current seq number stats into history, which moves the history window forward 1s + // (since that's how often pushStatsToHistory() is called) + _incomingMixedAudioSequenceNumberStats.pushStatsToHistory(); + + char packet[MAX_PACKET_SIZE]; + + // pack header + int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeAudioStreamStats); + char* dataAt = packet + numBytesPacketHeader; + + // pack append flag + quint8 appendFlag = 0; + memcpy(dataAt, &appendFlag, sizeof(quint8)); + dataAt += sizeof(quint8); + + // pack number of stats packed + quint16 numStreamStatsToPack = 1; + memcpy(dataAt, &numStreamStatsToPack, sizeof(quint16)); + dataAt += sizeof(quint16); + + // pack downstream audio stream stats + AudioStreamStats stats = getDownstreamAudioStreamStats(); + memcpy(dataAt, &stats, sizeof(AudioStreamStats)); + dataAt += sizeof(AudioStreamStats); + + // send packet + NodeList* nodeList = NodeList::getInstance(); + SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); + nodeList->writeDatagram(packet, dataAt - packet, audioMixer); +} + // NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo // data we know that we will have 2x samples for each stereo time sample at the format's sample rate void Audio::addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples) { @@ -867,16 +947,21 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) { //qDebug() << "Audio output just starved."; _ringBuffer.setIsStarved(true); _numFramesDisplayStarve = 10; + + _starveCount++; + _consecutiveNotMixedCount = 0; } - // if there is anything in the ring buffer, decide what to do - if (_ringBuffer.samplesAvailable() > 0) { - - int numNetworkOutputSamples = _ringBuffer.samplesAvailable(); - int numDeviceOutputSamples = numNetworkOutputSamples / networkOutputToOutputRatio; - - QByteArray outputBuffer; - outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t)); + int numNetworkOutputSamples; + if (Menu::getInstance()->isOptionChecked(MenuOption::DisableQAudioOutputOverflowCheck)) { + numNetworkOutputSamples = _ringBuffer.samplesAvailable(); + } else { + int numSamplesAudioOutputRoomFor = _audioOutput->bytesFree() / sizeof(int16_t); + numNetworkOutputSamples = std::min(_ringBuffer.samplesAvailable(), (int)(numSamplesAudioOutputRoomFor * networkOutputToOutputRatio)); + } + + // if there is data in the ring buffer and room in the audio output, decide what to do + if (numNetworkOutputSamples > 0) { int numSamplesNeededToStartPlayback = std::min(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (_jitterBufferSamples * 2), _ringBuffer.getSampleCapacity()); @@ -884,7 +969,13 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) { if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(numSamplesNeededToStartPlayback)) { // We are still waiting for enough samples to begin playback // qDebug() << numNetworkOutputSamples << " samples so far, waiting for " << numSamplesNeededToStartPlayback; + _consecutiveNotMixedCount++; } else { + int numDeviceOutputSamples = numNetworkOutputSamples / networkOutputToOutputRatio; + + QByteArray outputBuffer; + outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t)); + // We are either already playing back, or we have enough audio to start playing back. //qDebug() << "pushing " << numNetworkOutputSamples; _ringBuffer.setIsStarved(false); diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 9f04e5cb03..b40355e714 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -17,6 +17,8 @@ #include "InterfaceConfig.h" #include "AudioStreamStats.h" +#include "RingBufferHistory.h" +#include "MovingMinMaxAvg.h" #include #include @@ -34,6 +36,8 @@ static const int NUM_AUDIO_CHANNELS = 2; +static const int INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS = 30; + class QAudioInput; class QAudioOutput; class QIODevice; @@ -97,6 +101,9 @@ public slots: virtual void handleAudioByteArray(const QByteArray& audioByteArray); + AudioStreamStats getDownstreamAudioStreamStats() const; + void sendDownstreamAudioStatsPacket(); + bool switchInputToAudioDevice(const QString& inputDeviceName); bool switchOutputToAudioDevice(const QString& outputDeviceName); QString getDeviceName(QAudio::Mode mode) const { return (mode == QAudio::AudioInput) ? @@ -107,8 +114,16 @@ public slots: float getInputVolume() const { return (_audioInput) ? _audioInput->volume() : 0.0f; } void setInputVolume(float volume) { if (_audioInput) _audioInput->setVolume(volume); } - const AudioStreamStats& getAudioMixerAvatarStreamStats() const { return _audioMixerAvatarStreamStats; } - const QHash& getAudioMixerInjectedStreamStatsMap() const { return _audioMixerInjectedStreamStatsMap; } + const AudioRingBuffer& getDownstreamRingBuffer() const { return _ringBuffer; } + + int getDesiredJitterBufferFrames() const { return _jitterBufferSamples / _ringBuffer.getNumFrameSamples(); } + + int getStarveCount() const { return _starveCount; } + int getConsecutiveNotMixedCount() const { return _consecutiveNotMixedCount; } + + const AudioStreamStats& getAudioMixerAvatarStreamAudioStats() const { return _audioMixerAvatarStreamAudioStats; } + const QHash& getAudioMixerInjectedStreamAudioStatsMap() const { return _audioMixerInjectedStreamAudioStatsMap; } + const MovingMinMaxAvg& getInterframeTimeGapStats() const { return _interframeTimeGapStats; } signals: bool muteToggled(); @@ -241,11 +256,16 @@ private: QByteArray* _scopeOutputLeft; QByteArray* _scopeOutputRight; - AudioStreamStats _audioMixerAvatarStreamStats; - QHash _audioMixerInjectedStreamStatsMap; + int _starveCount; + int _consecutiveNotMixedCount; + + AudioStreamStats _audioMixerAvatarStreamAudioStats; + QHash _audioMixerInjectedStreamAudioStatsMap; quint16 _outgoingAvatarAudioSequenceNumber; SequenceNumberStats _incomingMixedAudioSequenceNumberStats; + + MovingMinMaxAvg _interframeTimeGapStats; }; diff --git a/interface/src/BuckyBalls.cpp b/interface/src/BuckyBalls.cpp index 68d1167071..1bc093283a 100644 --- a/interface/src/BuckyBalls.cpp +++ b/interface/src/BuckyBalls.cpp @@ -60,7 +60,7 @@ BuckyBalls::BuckyBalls() { void BuckyBalls::grab(PalmData& palm, float deltaTime) { float penetration; - glm::vec3 fingerTipPosition = palm.getFingerTipPosition(); + glm::vec3 fingerTipPosition = palm.getTipPosition(); if (palm.getControllerButtons() & BUTTON_FWD) { if (!_bballIsGrabbed[palm.getSixenseID()]) { diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 17026b5d5c..cde1890e6d 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -23,6 +23,11 @@ GLCanvas::GLCanvas() : QGLWidget(QGLFormat(QGL::NoDepthBuffer)), _throttleRendering(false), _idleRenderInterval(MSECS_PER_FRAME_WHEN_THROTTLED) { +#ifdef Q_OS_LINUX + // Cause GLCanvas::eventFilter to be called. + // It wouldn't hurt to do this on Mac and PC too; but apparently it's only needed on linux. + qApp->installEventFilter(this); +#endif } bool GLCanvas::isThrottleRendering() const { @@ -162,3 +167,35 @@ void GLCanvas::dragEnterEvent(QDragEnterEvent* event) { void GLCanvas::dropEvent(QDropEvent* event) { Application::getInstance()->dropEvent(event); } + +// Pressing Alt (and Meta) key alone activates the menubar because its style inherits the +// SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to +// receive keyPress events for the Alt (and Meta) key in a reliable manner. +// +// This filter catches events before QMenuBar can steal the keyboard focus. +// The idea was borrowed from +// http://www.archivum.info/qt-interest@trolltech.com/2006-09/00053/Re-(Qt4)-Alt-key-focus-QMenuBar-(solved).html + +bool GLCanvas::eventFilter(QObject*, QEvent* event) { + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::ShortcutOverride: + { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta) { + if (event->type() == QEvent::KeyPress) { + keyPressEvent(keyEvent); + } else if (event->type() == QEvent::KeyRelease) { + keyReleaseEvent(keyEvent); + } else { + QGLWidget::event(event); + } + return true; + } + } + default: + break; + } + return false; +} diff --git a/interface/src/GLCanvas.h b/interface/src/GLCanvas.h index 024cd615ae..773fcb5c27 100644 --- a/interface/src/GLCanvas.h +++ b/interface/src/GLCanvas.h @@ -50,6 +50,7 @@ protected: private slots: void activeChanged(Qt::ApplicationState state); void throttleRender(); + bool eventFilter(QObject*, QEvent* event); }; #endif // hifi_GLCanvas_h diff --git a/interface/src/Hair.cpp b/interface/src/Hair.cpp new file mode 100644 index 0000000000..a23312eba1 --- /dev/null +++ b/interface/src/Hair.cpp @@ -0,0 +1,35 @@ +// +// Hair.cpp +// interface/src +// +// Created by Philip on June 26, 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// Creates single flexible vertlet-integrated strands that can be used for hair/fur/grass + +#include "Hair.h" + +#include "Util.h" +#include "world.h" + + +Hair::Hair() { + qDebug() << "Creating Hair"; + } + +void Hair::simulate(float deltaTime) { +} + +void Hair::render() { + // + // Before calling this function, translate/rotate to the origin of the owning object + glPushMatrix(); + glColor3f(1.0f, 1.0f, 0.0f); + glutSolidSphere(1.0f, 15, 15); + glPopMatrix(); +} + + diff --git a/interface/src/Hair.h b/interface/src/Hair.h new file mode 100644 index 0000000000..e4dc3778c3 --- /dev/null +++ b/interface/src/Hair.h @@ -0,0 +1,35 @@ +// +// Hair.h +// interface/src +// +// Created by Philip on June 26, 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_Hair_h +#define hifi_Hair_h + +#include + +#include +#include + +#include "GeometryUtil.h" +#include "InterfaceConfig.h" +#include "Util.h" + + +class Hair { +public: + Hair(); + void simulate(float deltaTime); + void render(); + +private: + + }; + +#endif // hifi_Hair_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index dba5feca9e..f18dde7cbf 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -276,6 +276,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H, false, appInstance, SLOT(cameraMenuChanged())); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::UserInterface, Qt::Key_Slash, true); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::EnableVRMode, 0, false, @@ -326,7 +327,7 @@ Menu::Menu() : addDisabledActionAndSeparator(viewMenu, "Stats"); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Slash); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Percent); addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, Qt::CTRL | Qt::Key_L, appInstance, SLOT(toggleLogDialog())); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Bandwidth, 0, true); addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, this, SLOT(bandwidthDetails())); @@ -406,9 +407,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::GlowWhenSpeaking, 0, true); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false); - - QMenu* oculusOptionsMenu = developerMenu->addMenu("Oculus Options"); - addCheckableActionToQMenuAndActionHash(oculusOptionsMenu, MenuOption::DisplayOculusOverlays, 0, true); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::FocusIndicators, 0, false); QMenu* sixenseOptionsMenu = developerMenu->addMenu("Sixense Options"); addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseMouseInput, 0, true); @@ -421,6 +420,13 @@ Menu::Menu() : true, appInstance->getSixenseManager(), SLOT(setFilter(bool))); + addCheckableActionToQMenuAndActionHash(handOptionsMenu, + MenuOption::LowVelocityFilter, + 0, + true, + appInstance, + SLOT(setLowVelocityFilter(bool))); + addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandsCollideWithSelf, 0, false); @@ -435,13 +441,11 @@ Menu::Menu() : QMenu* timingMenu = developerMenu->addMenu("Timing and Statistics Tools"); QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer"); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayTimingDetails, 0, true); - addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandDisplaySideTiming, 0, false); - addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandAvatarSimulateTiming, 0, false); - addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandAvatarUpdateTiming, 0, false); - addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMiscAvatarTiming, 0, false); - addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandIdleTiming, 0, false); - addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandPaintGLTiming, 0, false); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMyAvatarTiming, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMyAvatarSimulateTiming, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandOtherAvatarTiming, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandPaintGLTiming, 0, false); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::TestPing, 0, true); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::FrameTimer); @@ -575,6 +579,8 @@ Menu::Menu() : Qt::CTRL | Qt::SHIFT | Qt::Key_U, false); + addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::DisableQAudioOutputOverflowCheck, 0, false); + addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel, Qt::CTRL | Qt::SHIFT | Qt::Key_V, this, @@ -1006,7 +1012,6 @@ void Menu::goToDomainDialog() { domainDialog.setWindowTitle("Go to Domain"); domainDialog.setLabelText("Domain server:"); domainDialog.setTextValue(currentDomainHostname); - domainDialog.setWindowFlags(Qt::Sheet); domainDialog.resize(domainDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, domainDialog.size().height()); int dialogReturn = domainDialog.exec(); @@ -1044,7 +1049,6 @@ void Menu::goTo() { QString destination = QString(); gotoDialog.setTextValue(destination); - gotoDialog.setWindowFlags(Qt::Sheet); gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height()); int dialogReturn = gotoDialog.exec(); @@ -1160,7 +1164,6 @@ void Menu::goToLocation() { coordinateDialog.setWindowTitle("Go to Location"); coordinateDialog.setLabelText("Coordinate as x,y,z:"); coordinateDialog.setTextValue(currentLocation); - coordinateDialog.setWindowFlags(Qt::Sheet); coordinateDialog.resize(coordinateDialog.parentWidget()->size().width() * 0.30, coordinateDialog.size().height()); int dialogReturn = coordinateDialog.exec(); @@ -1225,7 +1228,6 @@ void Menu::nameLocation() { "(wherever you are standing and looking now) as you.\n\n" "Location name:"); - nameDialog.setWindowFlags(Qt::Sheet); nameDialog.resize((int) (nameDialog.parentWidget()->size().width() * 0.30), nameDialog.size().height()); if (nameDialog.exec() == QDialog::Accepted) { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 06b5c5c9f4..651cf3288a 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -343,13 +343,13 @@ namespace MenuOption { const QString DecreaseVoxelSize = "Decrease Voxel Size"; const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD"; const QString DisableNackPackets = "Disable NACK Packets"; + const QString DisableQAudioOutputOverflowCheck = "Disable QAudioOutput Overflow Check"; const QString DisplayFrustum = "Display Frustum"; const QString DisplayHands = "Display Hands"; const QString DisplayHandTargets = "Display Hand Targets"; const QString DisplayModelBounds = "Display Model Bounds"; const QString DisplayModelElementProxy = "Display Model Element Bounds"; const QString DisplayModelElementChildProxies = "Display Model Element Children"; - const QString DisplayOculusOverlays = "Display Oculus Overlays"; const QString DisplayTimingDetails = "Display Timing Details"; const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes"; const QString EchoLocalAudio = "Echo Local Audio"; @@ -357,17 +357,17 @@ namespace MenuOption { const QString EnableGlowEffect = "Enable Glow Effect (Warning: Poor Oculus Performance)"; const QString Enable3DTVMode = "Enable 3DTV Mode"; const QString EnableVRMode = "Enable VR Mode"; - const QString ExpandMiscAvatarTiming = "Expand Misc MyAvatar Timing"; - const QString ExpandAvatarUpdateTiming = "Expand MyAvatar update Timing"; - const QString ExpandAvatarSimulateTiming = "Expand MyAvatar simulate Timing"; - const QString ExpandDisplaySideTiming = "Expand Display Side Timing"; - const QString ExpandIdleTiming = "Expand Idle Timing"; - const QString ExpandPaintGLTiming = "Expand PaintGL Timing"; - const QString ExpandUpdateTiming = "Expand Update Timing"; + const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; + const QString ExpandMyAvatarTiming = "Expand /myAvatar"; + const QString ExpandOtherAvatarTiming = "Expand /otherAvatar"; + const QString ExpandPaintGLTiming = "Expand /paintGL"; + const QString ExpandUpdateTiming = "Expand /update"; const QString Faceplus = "Faceplus"; const QString Faceshift = "Faceshift"; const QString FilterSixense = "Smooth Sixense Movement"; + const QString LowVelocityFilter = "Low Velocity Filter"; const QString FirstPerson = "First Person"; + const QString FocusIndicators = "Focus Indicators"; const QString FrameTimer = "Show Timer"; const QString FrustumRenderMode = "Render Mode"; const QString Fullscreen = "Fullscreen"; @@ -438,6 +438,7 @@ namespace MenuOption { const QString UploadAttachment = "Upload Attachment Model"; const QString UploadHead = "Upload Head Model"; const QString UploadSkeleton = "Upload Skeleton Model"; + const QString UserInterface = "User Interface"; const QString Visage = "Visage"; const QString VoxelMode = "Cycle Voxel Mode"; const QString Voxels = "Voxels"; diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 7a5119a62d..66933643ae 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -48,8 +48,10 @@ void MetavoxelSystem::init() { } MetavoxelLOD MetavoxelSystem::getLOD() const { - const float FIXED_LOD_THRESHOLD = 0.01f; - return MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(), FIXED_LOD_THRESHOLD); + // the LOD threshold is temporarily tied to the avatar LOD parameter + const float BASE_LOD_THRESHOLD = 0.01f; + return MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(), + BASE_LOD_THRESHOLD * Menu::getInstance()->getAvatarLODDistanceMultiplier()); } void MetavoxelSystem::simulate(float deltaTime) { diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 9b136980f4..57b558706e 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -14,12 +14,13 @@ #include #include #include - -#include -#include -#include +#include #include +#include +#include +#include +#include #include "Application.h" #include "Avatar.h" @@ -59,6 +60,7 @@ Avatar::Avatar() : _mouseRayDirection(0.0f, 0.0f, 0.0f), _moving(false), _collisionGroups(0), + _numLocalLights(2), _initialized(false), _shouldRenderBillboard(true) { @@ -81,6 +83,23 @@ void Avatar::init() { _initialized = true; _shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE); initializeHair(); + + for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) { + _localLightColors[i] = glm::vec3(0.0f, 0.0f, 0.0f); + _localLightDirections[i] = glm::vec3(0.0f, 0.0f, 0.0f); + } + + glm::vec3 darkGrayColor(0.4f, 0.4f, 0.4f); + glm::vec3 greenColor(0.0f, 1.0f, 0.0f); + glm::vec3 directionX(1.0f, 0.0f, 0.0f); + glm::vec3 directionY(0.0f, 1.0f, 0.0f); + + // initialize local lights + _localLightColors[0] = darkGrayColor; + _localLightColors[1] = darkGrayColor; + + _localLightDirections[0] = directionX; + _localLightDirections[1] = directionY; } glm::vec3 Avatar::getChestPosition() const { @@ -99,6 +118,7 @@ float Avatar::getLODDistance() const { } void Avatar::simulate(float deltaTime) { + PerformanceTimer perfTimer("simulate"); if (_scale != _targetScale) { setScale(_targetScale); } @@ -118,31 +138,43 @@ void Avatar::simulate(float deltaTime) { bool inViewFrustum = Application::getInstance()->getViewFrustum()->sphereInFrustum(_position, boundingRadius) != ViewFrustum::OUTSIDE; - getHand()->simulate(deltaTime, false); + { + PerformanceTimer perfTimer("hand"); + getHand()->simulate(deltaTime, false); + } _skeletonModel.setLODDistance(getLODDistance()); if (!_shouldRenderBillboard && inViewFrustum) { - if (_hasNewJointRotations) { - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData.at(i); - _skeletonModel.setJointState(i, data.valid, data.rotation); + { + PerformanceTimer perfTimer("skeleton"); + if (_hasNewJointRotations) { + for (int i = 0; i < _jointData.size(); i++) { + const JointData& data = _jointData.at(i); + _skeletonModel.setJointState(i, data.valid, data.rotation); + } } - _skeletonModel.simulate(deltaTime); + _skeletonModel.simulate(deltaTime, _hasNewJointRotations); + simulateAttachments(deltaTime); + _hasNewJointRotations = false; + } + { + PerformanceTimer perfTimer("head"); + glm::vec3 headPosition = _position; + _skeletonModel.getHeadPosition(headPosition); + Head* head = getHead(); + head->setPosition(headPosition); + head->setScale(_scale); + head->simulate(deltaTime, false, _shouldRenderBillboard); } - _skeletonModel.simulate(deltaTime, _hasNewJointRotations); - simulateAttachments(deltaTime); - _hasNewJointRotations = false; - - glm::vec3 headPosition = _position; - _skeletonModel.getHeadPosition(headPosition); - Head* head = getHead(); - head->setPosition(headPosition); - head->setScale(_scale); - head->simulate(deltaTime, false, _shouldRenderBillboard); - if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { + PerformanceTimer perfTimer("hair"); simulateHair(deltaTime); } + + foreach (Hair* hair, _hairs) { + hair->simulate(deltaTime); + } + } // update position by velocity, and subtract the change added earlier for gravity @@ -219,7 +251,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { const float GLOW_DISTANCE = 20.0f; const float GLOW_MAX_LOUDNESS = 2500.0f; const float MAX_GLOW = 0.5f; - + float GLOW_FROM_AVERAGE_LOUDNESS = ((this == Application::getInstance()->getAvatar()) ? 0.0f : MAX_GLOW * getHeadData()->getAudioLoudness() / GLOW_MAX_LOUDNESS); @@ -230,7 +262,23 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { float glowLevel = _moving && distanceToTarget > GLOW_DISTANCE && renderMode == NORMAL_RENDER_MODE ? 1.0f : GLOW_FROM_AVERAGE_LOUDNESS; - + + + // local lights directions and colors + getSkeletonModel().setNumLocalLights(_numLocalLights); + getHead()->getFaceModel().setNumLocalLights(_numLocalLights); + for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) { + glm::vec3 normalized = glm::normalize(_localLightDirections[i]); + + // body + getSkeletonModel().setLocalLightColor(_localLightColors[i], i); + getSkeletonModel().setLocalLightDirection(normalized, i); + + // head + getHead()->getFaceModel().setLocalLightColor(_localLightColors[i], i); + getHead()->getFaceModel().setLocalLightDirection(_localLightDirections[i], i); + } + // render body if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { renderBody(renderMode, glowLevel); @@ -252,7 +300,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { } // If this is the avatar being looked at, render a little ball above their head - if (_isLookAtTarget) { + if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::FocusIndicators)) { const float LOOK_AT_INDICATOR_RADIUS = 0.03f; const float LOOK_AT_INDICATOR_OFFSET = 0.22f; const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.75f }; @@ -380,6 +428,9 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) { getHead()->render(1.0f, modelRenderMode); if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { renderHair(); + foreach (Hair* hair, _hairs) { + hair->render(); + } } } @@ -603,7 +654,6 @@ void Avatar::initializeHair() { } } - qDebug() << "Initialize Hair"; } bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const { @@ -920,6 +970,11 @@ glm::quat Avatar::getJointCombinedRotation(const QString& name) const { return rotation; } +void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const { + //Scale a world space vector as if it was relative to the position + positionToScale = _position + _scale * (positionToScale - _position); +} + void Avatar::setFaceModelURL(const QUrl& faceModelURL) { AvatarData::setFaceModelURL(faceModelURL); const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile(Application::resourcesPath() + "meshes/defaultAvatar_head.fst"); @@ -1107,3 +1162,29 @@ void Avatar::setShowDisplayName(bool showDisplayName) { } +void Avatar::setLocalLightDirection(const glm::vec3& direction, int lightIndex) { + _localLightDirections[lightIndex] = direction; + qDebug( "set light %d direction ( %f, %f, %f )\n", lightIndex, direction.x, direction.y, direction.z ); +} + +void Avatar::setLocalLightColor(const glm::vec3& color, int lightIndex) { + _localLightColors[lightIndex] = color; + qDebug( "set light %d color ( %f, %f, %f )\n", lightIndex, color.x, color.y, color.z ); +} + +void Avatar::addLocalLight() { + if (_numLocalLights + 1 <= MAX_LOCAL_LIGHTS) { + ++_numLocalLights; + } + + qDebug("ADD LOCAL LIGHT (numLocalLights = %d)\n", _numLocalLights); +} + +void Avatar::removeLocalLight() { + if (_numLocalLights - 1 >= 0) { + --_numLocalLights; + } + + qDebug("REMOVE LOCAL LIGHT (numLocalLights = %d)\n", _numLocalLights); +} + diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index f20db1019d..6fb7991c3c 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -19,6 +19,7 @@ #include +#include "Hair.h" #include "Hand.h" #include "Head.h" #include "InterfaceConfig.h" @@ -151,14 +152,23 @@ public: glm::vec3 getAcceleration() const { return _acceleration; } glm::vec3 getAngularVelocity() const { return _angularVelocity; } + + /// Scales a world space position vector relative to the avatar position and scale + /// \param vector position to be scaled. Will store the result + void scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const; public slots: void updateCollisionGroups(); - + void setLocalLightDirection(const glm::vec3& direction, int lightIndex); + void setLocalLightColor(const glm::vec3& color, int lightIndex); + void addLocalLight(); + void removeLocalLight(); + signals: void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision); protected: + QVector _hairs; SkeletonModel _skeletonModel; QVector _attachmentModels; float _bodyYawDelta; @@ -174,9 +184,14 @@ protected: glm::vec3 _mouseRayDirection; float _stringLength; bool _moving; ///< set when position is changing - + quint32 _collisionGroups; - + + // always-present local lighting for the avatar + glm::vec3 _localLightDirections[MAX_LOCAL_LIGHTS]; + glm::vec3 _localLightColors[MAX_LOCAL_LIGHTS]; + int _numLocalLights; + // protected methods... glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; } glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 59f31388f8..debe6489ea 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -41,9 +41,13 @@ void AvatarManager::init() { } void AvatarManager::updateOtherAvatars(float deltaTime) { + if (_avatarHash.size() < 2) { + return; + } bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateAvatars()"); + PerformanceTimer perfTimer("otherAvatars"); Application* applicationInstance = Application::getInstance(); glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin(); glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection(); diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index 59e5b08cc0..203dbf2283 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -49,9 +49,9 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) { 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(glm::quat()); - glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInParentFrame()) * + glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInConstrainedFrame()) * joint.preTransform * glm::mat4_cast(joint.preRotation))); - state.setRotationInParentFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2])) + state.setRotationInConstrainedFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2])) * glm::angleAxis(RADIANS_PER_DEGREE * _owningHead->getFinalYaw(), glm::normalize(inverse * axes[1])) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalPitch(), glm::normalize(inverse * axes[0])) * joint.rotation); @@ -61,14 +61,14 @@ void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJ // likewise with the eye joints // NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual. glm::mat4 inverse = glm::inverse(glm::mat4_cast(_rotation) * parentState.getTransform() * - glm::translate(state.getDefaultTranslationInParentFrame()) * + glm::translate(state.getDefaultTranslationInConstrainedFrame()) * joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation)); glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f)); glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() + _owningHead->getSaccade() - _translation, 1.0f)); glm::quat between = rotationBetween(front, lookAt); const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; - state.setRotationInParentFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) * + state.setRotationInConstrainedFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) * joint.rotation); } diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 94f734ba06..08e1cf83df 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -130,6 +130,9 @@ void Hand::render(bool isMine, Model::RenderMode renderMode) { void Hand::renderHandTargets(bool isMine) { glPushMatrix(); + MyAvatar* myAvatar = Application::getInstance()->getAvatar(); + const float avatarScale = Application::getInstance()->getAvatar()->getScale(); + const float alpha = 1.0f; const glm::vec3 handColor(1.0, 0.0, 0.0); // Color the hand targets red to be different than skin @@ -142,7 +145,7 @@ void Hand::renderHandTargets(bool isMine) { if (!palm.isActive()) { continue; } - glm::vec3 targetPosition = palm.getFingerTipPosition(); + glm::vec3 targetPosition = palm.getTipPosition(); glPushMatrix(); glTranslatef(targetPosition.x, targetPosition.y, targetPosition.z); @@ -153,18 +156,23 @@ void Hand::renderHandTargets(bool isMine) { } } - const float PALM_BALL_RADIUS = 0.03f; - const float PALM_DISK_RADIUS = 0.06f; - const float PALM_DISK_THICKNESS = 0.01f; - const float PALM_FINGER_ROD_RADIUS = 0.003f; + const float PALM_BALL_RADIUS = 0.03f * avatarScale; + const float PALM_DISK_RADIUS = 0.06f * avatarScale; + const float PALM_DISK_THICKNESS = 0.01f * avatarScale; + const float PALM_FINGER_ROD_RADIUS = 0.003f * avatarScale; // Draw the palm ball and disk for (size_t i = 0; i < getNumPalms(); ++i) { PalmData& palm = getPalms()[i]; if (palm.isActive()) { glColor4f(handColor.r, handColor.g, handColor.b, alpha); - glm::vec3 tip = palm.getFingerTipPosition(); + glm::vec3 tip = palm.getTipPosition(); glm::vec3 root = palm.getPosition(); + + //Scale the positions based on avatar scale + myAvatar->scaleVectorRelativeToPosition(tip); + myAvatar->scaleVectorRelativeToPosition(root); + Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS); // Render sphere at palm/finger root glm::vec3 offsetFromPalm = root + palm.getNormal() * PALM_DISK_THICKNESS; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6cb6ba6840..04e7b628c9 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -108,15 +108,10 @@ void MyAvatar::reset() { } void MyAvatar::update(float deltaTime) { - PerformanceTimer perfTimer("MyAvatar::update/"); Head* head = getHead(); head->relaxLean(deltaTime); - { - PerformanceTimer perfTimer("MyAvatar::update/updateFromTrackers"); - updateFromTrackers(deltaTime); - } + updateFromTrackers(deltaTime); if (Menu::getInstance()->isOptionChecked(MenuOption::MoveWithLean)) { - PerformanceTimer perfTimer("MyAvatar::update/moveWithLean"); // Faceshift drive is enabled, set the avatar drive based on the head position moveWithLean(); } @@ -127,19 +122,14 @@ void MyAvatar::update(float deltaTime) { head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); if (_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY) { - PerformanceTimer perfTimer("MyAvatar::update/gravityWork"); setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition())); } - { - PerformanceTimer perfTimer("MyAvatar::update/simulate"); - simulate(deltaTime); - } + simulate(deltaTime); } void MyAvatar::simulate(float deltaTime) { - PerformanceTimer perfTimer("MyAvatar::simulate"); - + PerformanceTimer perfTimer("simulate"); if (_scale != _targetScale) { float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale; setScale(scale); @@ -150,31 +140,28 @@ void MyAvatar::simulate(float deltaTime) { _handState = HAND_STATE_NULL; { - PerformanceTimer perfTimer("MyAvatar::simulate/updateOrientation"); + PerformanceTimer perfTimer("transform"); updateOrientation(deltaTime); - } - { - PerformanceTimer perfTimer("MyAvatar::simulate/updatePosition"); updatePosition(deltaTime); } { - PerformanceTimer perfTimer("MyAvatar::simulate/hand Collision,simulate"); + PerformanceTimer perfTimer("hand"); // update avatar skeleton and simulate hand and head getHand()->simulate(deltaTime, true); } { - PerformanceTimer perfTimer("MyAvatar::simulate/_skeletonModel.simulate()"); + PerformanceTimer perfTimer("skeleton"); _skeletonModel.simulate(deltaTime); } { - PerformanceTimer perfTimer("MyAvatar::simulate/simulateAttachments"); + PerformanceTimer perfTimer("attachments"); simulateAttachments(deltaTime); } { - PerformanceTimer perfTimer("MyAvatar::simulate/copy joints"); + PerformanceTimer perfTimer("joints"); // copy out the skeleton joints from the model _jointData.resize(_skeletonModel.getJointStateCount()); for (int i = 0; i < _jointData.size(); i++) { @@ -184,7 +171,7 @@ void MyAvatar::simulate(float deltaTime) { } { - PerformanceTimer perfTimer("MyAvatar::simulate/head Simulate"); + PerformanceTimer perfTimer("head"); Head* head = getHead(); glm::vec3 headPosition; if (!_skeletonModel.getHeadPosition(headPosition)) { @@ -196,14 +183,17 @@ void MyAvatar::simulate(float deltaTime) { } { - PerformanceTimer perfTimer("MyAvatar::simulate/hair Simulate"); + PerformanceTimer perfTimer("hair"); if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { simulateHair(deltaTime); + foreach (Hair* hair, _hairs) { + hair->simulate(deltaTime); + } } } { - PerformanceTimer perfTimer("MyAvatar::simulate/ragdoll"); + PerformanceTimer perfTimer("ragdoll"); if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) { const int minError = 0.01f; const float maxIterations = 10; @@ -216,7 +206,7 @@ void MyAvatar::simulate(float deltaTime) { // now that we're done stepping the avatar forward in time, compute new collisions if (_collisionGroups != 0) { - PerformanceTimer perfTimer("MyAvatar::simulate/_collisionGroups"); + PerformanceTimer perfTimer("collisions"); Camera* myCamera = Application::getInstance()->getCamera(); float radius = getSkeletonHeight() * COLLISION_RADIUS_SCALE; @@ -225,18 +215,18 @@ void MyAvatar::simulate(float deltaTime) { radius *= COLLISION_RADIUS_SCALAR; } if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) { - PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithEnvironment"); + PerformanceTimer perfTimer("environment"); updateCollisionWithEnvironment(deltaTime, radius); } if (_collisionGroups & COLLISION_GROUP_VOXELS) { - PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithVoxels"); + PerformanceTimer perfTimer("voxels"); updateCollisionWithVoxels(deltaTime, radius); } else { _trapDuration = 0.0f; } /* TODO: Andrew to make this work if (_collisionGroups & COLLISION_GROUP_AVATARS) { - PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithAvatars"); + PerformanceTimer perfTimer("avatars"); updateCollisionWithAvatars(deltaTime); } */ @@ -896,6 +886,9 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) { getHead()->render(1.0f, modelRenderMode); if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { renderHair(); + foreach (Hair* hair, _hairs) { + hair->render(); + } } } getHand()->render(true, modelRenderMode); @@ -910,7 +903,6 @@ bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode rend } float MyAvatar::computeDistanceToFloor(const glm::vec3& startPoint) { - PerformanceTimer perfTimer("MyAvatar::computeDistanceToFloor()"); glm::vec3 direction = -_worldUpDirection; OctreeElement* elementHit; // output from findRayIntersection float distance = FLT_MAX; // output from findRayIntersection @@ -976,7 +968,6 @@ void MyAvatar::updateOrientation(float deltaTime) { const float NEARBY_FLOOR_THRESHOLD = 5.0f; void MyAvatar::updatePosition(float deltaTime) { - PerformanceTimer perfTimer("MyAvatar::updatePosition"); float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) + fabsf(_driveKeys[UP] - _driveKeys[DOWN]); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 2f35b96181..d9b2340a90 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -219,7 +219,7 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { JointState& parentState = _jointStates[parentJointIndex]; parentState.setRotationFromBindFrame(palmRotation, PALM_PRIORITY); // lock hand to forearm by slamming its rotation (in parent-frame) to identity - _jointStates[jointIndex].setRotationInParentFrame(glm::quat()); + _jointStates[jointIndex].setRotationInConstrainedFrame(glm::quat()); } else { inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY); } @@ -255,9 +255,9 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const } // get the rotation axes in joint space and use them to adjust the rotation glm::mat3 axes = glm::mat3_cast(glm::quat()); - glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInParentFrame()) * + glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInConstrainedFrame()) * joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation))); - state.setRotationInParentFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), + state.setRotationInConstrainedFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), glm::normalize(inverse * axes[2])) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), glm::normalize(inverse * axes[0])) * joint.rotation); } diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 76d0d45efa..7a88d890ac 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -109,6 +109,7 @@ public: void resetShapePositionsToDefaultPose(); // DEBUG method void renderRagdoll(); + protected: // virtual overrrides from Ragdoll diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index 59f2c245df..a7d50814e2 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -11,6 +11,7 @@ #include +#include #include #include "Application.h" @@ -75,6 +76,7 @@ void Faceshift::update() { if (!isActive()) { return; } + PerformanceTimer perfTimer("faceshift"); // get the euler angles relative to the window glm::vec3 eulers = glm::degrees(safeEulerAngles(_headRotation * glm::quat(glm::radians(glm::vec3( (_eyeGazeLeftPitch + _eyeGazeRightPitch) / 2.0f, (_eyeGazeLeftYaw + _eyeGazeRightYaw) / 2.0f, 0.0f))))); diff --git a/interface/src/devices/JoystickManager.cpp b/interface/src/devices/JoystickManager.cpp index 005505441c..8169c6d06e 100644 --- a/interface/src/devices/JoystickManager.cpp +++ b/interface/src/devices/JoystickManager.cpp @@ -12,9 +12,10 @@ #include #include - #include +#include + #include "JoystickManager.h" using namespace std; @@ -46,6 +47,7 @@ JoystickManager::~JoystickManager() { void JoystickManager::update() { #ifdef HAVE_SDL + PerformanceTimer perfTimer("joystick"); SDL_JoystickUpdate(); for (int i = 0; i < _joystickStates.size(); i++) { diff --git a/interface/src/devices/MIDIManager.cpp b/interface/src/devices/MIDIManager.cpp index 38dd6a9760..52ae2eb645 100644 --- a/interface/src/devices/MIDIManager.cpp +++ b/interface/src/devices/MIDIManager.cpp @@ -13,14 +13,13 @@ #include "MIDIManager.h" -#ifdef HAVE_RTMIDI - MIDIManager& MIDIManager::getInstance() { static MIDIManager sharedInstance; return sharedInstance; } void MIDIManager::midiCallback(double deltaTime, std::vector* message, void* userData) { +#ifdef HAVE_RTMIDI MIDIEvent callbackEvent; callbackEvent.deltaTime = deltaTime; @@ -36,15 +35,19 @@ void MIDIManager::midiCallback(double deltaTime, std::vector* mes } emit getInstance().midiEvent(callbackEvent); +#endif } MIDIManager::~MIDIManager() { +#ifdef HAVE_RTMIDI delete _midiInput; +#endif } const int DEFAULT_MIDI_PORT = 0; void MIDIManager::openDefaultPort() { +#ifdef HAVE_RTMIDI if (!_midiInput) { _midiInput = new RtMidiIn(); @@ -63,6 +66,5 @@ void MIDIManager::openDefaultPort() { _midiInput = NULL; } } -} - -#endif \ No newline at end of file +#endif +} \ No newline at end of file diff --git a/interface/src/devices/MIDIManager.h b/interface/src/devices/MIDIManager.h index 886b0adb00..9fc55d11da 100644 --- a/interface/src/devices/MIDIManager.h +++ b/interface/src/devices/MIDIManager.h @@ -12,14 +12,14 @@ #ifndef hifi_MIDIManager_h #define hifi_MIDIManager_h -#ifdef HAVE_RTMIDI - #include #include #include +#ifdef HAVE_RTMIDI #include +#endif class MIDIManager : public QObject { Q_OBJECT @@ -36,7 +36,9 @@ public: ~MIDIManager(); void openDefaultPort(); +#ifdef HAVE_RTMIDI bool hasDevice() const { return !!_midiInput; } +#endif public slots: unsigned int NoteOn() const { return 144; } unsigned int NoteOff() const { return 128; } @@ -46,10 +48,10 @@ signals: void midiEvent(const MIDIEvent& event); private: +#ifdef HAVE_RTMIDI RtMidiIn* _midiInput; -}; - #endif +}; #endif // hifi_MIDIManager_h diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 199c313119..beb18bfc72 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -269,8 +269,7 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p // We only need to render the overlays to a texture once, then we just render the texture on the hemisphere // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() applicationOverlay.renderOverlay(true); - const bool displayOverlays = Menu::getInstance()->isOptionChecked(MenuOption::DisplayOculusOverlays); - + //Bind our framebuffer object. If we are rendering the glow effect, we let the glow effect shader take care of it if (Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect)) { Application::getInstance()->getGlowEffect()->prepare(); @@ -325,9 +324,7 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p Application::getInstance()->displaySide(*_camera); - if (displayOverlays) { - applicationOverlay.displayOverlayTextureOculus(*_camera); - } + applicationOverlay.displayOverlayTextureOculus(*_camera); } //Wait till time-warp to reduce latency @@ -443,7 +440,15 @@ void OculusManager::getEulerAngles(float& yaw, float& pitch, float& roll) { ovrPosef pose = ss.Predicted.Pose; Quatf orientation = Quatf(pose.Orientation); orientation.GetEulerAngles(&yaw, &pitch, &roll); + } else { + yaw = 0.0f; + pitch = 0.0f; + roll = 0.0f; } +#else + yaw = 0.0f; + pitch = 0.0f; + roll = 0.0f; #endif } @@ -459,3 +464,50 @@ QSize OculusManager::getRenderTargetSize() { #endif } +//Renders sixense laser pointers for UI selection in the oculus +void OculusManager::renderLaserPointers() { +#ifdef HAVE_LIBOVR + const float PALM_TIP_ROD_RADIUS = 0.002f; + + MyAvatar* myAvatar = Application::getInstance()->getAvatar(); + + //If the Oculus is enabled, we will draw a blue cursor ray + + for (size_t i = 0; i < myAvatar->getHand()->getNumPalms(); ++i) { + PalmData& palm = myAvatar->getHand()->getPalms()[i]; + if (palm.isActive()) { + glColor4f(0, 1, 1, 1); + glm::vec3 tip = getLaserPointerTipPosition(&palm); + glm::vec3 root = palm.getPosition(); + + //Scale the root vector with the avatar scale + myAvatar->scaleVectorRelativeToPosition(root); + + Avatar::renderJointConnectingCone(root, tip, PALM_TIP_ROD_RADIUS, PALM_TIP_ROD_RADIUS); + } + } +#endif +} + +//Gets the tip position for the laser pointer +glm::vec3 OculusManager::getLaserPointerTipPosition(const PalmData* palm) { +#ifdef HAVE_LIBOVR + const ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay(); + const float PALM_TIP_ROD_LENGTH_MULT = 40.0f; + + glm::vec3 direction = glm::normalize(palm->getTipPosition() - palm->getPosition()); + + glm::vec3 position = palm->getPosition(); + //scale the position with the avatar + Application::getInstance()->getAvatar()->scaleVectorRelativeToPosition(position); + + + glm::vec3 result; + if (applicationOverlay.calculateRayUICollisionPoint(position, direction, result)) { + return result; + } + + return palm->getPosition(); +#endif + return glm::vec3(0.0f); +} diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h index 7798875c2c..871e5d649a 100644 --- a/interface/src/devices/OculusManager.h +++ b/interface/src/devices/OculusManager.h @@ -23,6 +23,7 @@ const float DEFAULT_OCULUS_UI_ANGULAR_SIZE = 72.0f; class Camera; +class PalmData; /// Handles interaction with the Oculus Rift. class OculusManager { @@ -41,6 +42,10 @@ public: /// param \roll[out] roll in radians static void getEulerAngles(float& yaw, float& pitch, float& roll); static QSize getRenderTargetSize(); + + /// Renders a laser pointer for UI picking + static void renderLaserPointers(); + static glm::vec3 getLaserPointerTipPosition(const PalmData* palm); private: #ifdef HAVE_LIBOVR diff --git a/interface/src/devices/PrioVR.cpp b/interface/src/devices/PrioVR.cpp index e96f4f04d5..195de5705d 100644 --- a/interface/src/devices/PrioVR.cpp +++ b/interface/src/devices/PrioVR.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "Application.h" #include "PrioVR.h" @@ -166,6 +167,7 @@ void PrioVR::update(float deltaTime) { if (!_skeletalDevice) { return; } + PerformanceTimer perfTimer("PrioVR"); unsigned int timestamp; yei_getLastStreamDataAll(_skeletalDevice, (char*)_jointRotations.data(), _jointRotations.size() * sizeof(glm::quat), ×tamp); diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index c50fc887d6..f145b8d5be 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -11,8 +11,11 @@ #include +#include + #include "Application.h" #include "SixenseManager.h" +#include "devices/OculusManager.h" #include "UserActivityLogger.h" #ifdef HAVE_SIXENSE @@ -32,6 +35,7 @@ SixenseManager::SixenseManager() { #ifdef HAVE_SIXENSE _lastMovement = 0; _amountMoved = glm::vec3(0.0f); + _lowVelocityFilter = false; _calibrationState = CALIBRATION_STATE_IDLE; // By default we assume the _neckBase (in orb frame) is as high above the orb @@ -60,10 +64,8 @@ SixenseManager::~SixenseManager() { void SixenseManager::setFilter(bool filter) { #ifdef HAVE_SIXENSE if (filter) { - qDebug("Sixense Filter ON"); sixenseSetFilterEnabled(1); } else { - qDebug("Sixense Filter OFF"); sixenseSetFilterEnabled(0); } #endif @@ -84,7 +86,10 @@ void SixenseManager::update(float deltaTime) { if (sixenseGetNumActiveControllers() == 0) { _hydrasConnected = false; return; - } else if (!_hydrasConnected) { + } + + PerformanceTimer perfTimer("sixense"); + if (!_hydrasConnected) { _hydrasConnected = true; UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra"); } @@ -160,17 +165,23 @@ void SixenseManager::update(float deltaTime) { } palm->setRawVelocity(rawVelocity); // meters/sec - // Use a velocity sensitive filter to damp small motions and preserve large ones with - // no latency. - float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f); - palm->setRawPosition(palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter)); - // adjustment for hydra controllers fit into hands float sign = (i == 0) ? -1.0f : 1.0f; rotation *= glm::angleAxis(sign * PI/4.0f, glm::vec3(0.0f, 0.0f, 1.0f)); - palm->setRawRotation(safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter)); - + if (_lowVelocityFilter) { + // Use a velocity sensitive filter to damp small motions and preserve large ones with + // no latency. + float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f); + position = palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter); + rotation = safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter); + palm->setRawPosition(position); + palm->setRawRotation(rotation); + } else { + palm->setRawPosition(position); + palm->setRawRotation(rotation); + } + // use the velocity to determine whether there's any movement (if the hand isn't new) const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f; _amountMoved += rawVelocity * deltaTime; @@ -358,9 +369,7 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) { MyAvatar* avatar = application->getAvatar(); QGLWidget* widget = application->getGLWidget(); QPoint pos; - // Get directon relative to avatar orientation - glm::vec3 direction = glm::inverse(avatar->getOrientation()) * palm->getFingerDirection(); - + Qt::MouseButton bumperButton; Qt::MouseButton triggerButton; @@ -372,19 +381,27 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) { triggerButton = Qt::LeftButton; } - // Get the angles, scaled between (-0.5,0.5) - float xAngle = (atan2(direction.z, direction.x) + M_PI_2); - float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); + if (OculusManager::isConnected()) { + pos = application->getApplicationOverlay().getOculusPalmClickLocation(palm); + } else { + // Get directon relative to avatar orientation + glm::vec3 direction = glm::inverse(avatar->getOrientation()) * palm->getFingerDirection(); - // Get the pixel range over which the xAngle and yAngle are scaled - float cursorRange = widget->width() * getCursorPixelRangeMult(); + // Get the angles, scaled between (-0.5,0.5) + float xAngle = (atan2(direction.z, direction.x) + M_PI_2); + float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); - pos.setX(widget->width() / 2.0f + cursorRange * xAngle); - pos.setY(widget->height() / 2.0f + cursorRange * yAngle); + // Get the pixel range over which the xAngle and yAngle are scaled + float cursorRange = widget->width() * getCursorPixelRangeMult(); + + pos.setX(widget->width() / 2.0f + cursorRange * xAngle); + pos.setY(widget->height() / 2.0f + cursorRange * yAngle); + + } //If we are off screen then we should stop processing, and if a trigger or bumper is pressed, //we should unpress them. - if (pos.x() < 0 || pos.x() > widget->width() || pos.y() < 0 || pos.y() > widget->height()) { + if (pos.x() == INT_MAX) { if (_bumperPressed[index]) { QMouseEvent mouseEvent(QEvent::MouseButtonRelease, pos, bumperButton, bumperButton, 0); diff --git a/interface/src/devices/SixenseManager.h b/interface/src/devices/SixenseManager.h index 8ca27ef77c..bae9e1c6d6 100644 --- a/interface/src/devices/SixenseManager.h +++ b/interface/src/devices/SixenseManager.h @@ -47,6 +47,7 @@ public: public slots: void setFilter(bool filter); + void setLowVelocityFilter(bool lowVelocityFilter) { _lowVelocityFilter = lowVelocityFilter; }; private: #ifdef HAVE_SIXENSE @@ -80,6 +81,8 @@ private: bool _bumperPressed[2]; int _oldX[2]; int _oldY[2]; + + bool _lowVelocityFilter; }; #endif // hifi_SixenseManager_h diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index 25d3ff892a..3b42c03f2d 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -100,6 +100,7 @@ void TV3DManager::display(Camera& whichCamera) { // We only need to render the overlays to a texture once, then we just render the texture as a quad // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() applicationOverlay.renderOverlay(true); + const bool displayOverlays = Menu::getInstance()->isOptionChecked(MenuOption::UserInterface); if (glowEnabled) { Application::getInstance()->getGlowEffect()->prepare(); @@ -128,7 +129,9 @@ void TV3DManager::display(Camera& whichCamera) { glLoadIdentity(); Application::getInstance()->displaySide(whichCamera); - applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov); + if (displayOverlays) { + applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov); + } } glPopMatrix(); glDisable(GL_SCISSOR_TEST); @@ -154,7 +157,9 @@ void TV3DManager::display(Camera& whichCamera) { glLoadIdentity(); Application::getInstance()->displaySide(whichCamera); - applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov); + if (displayOverlays) { + applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov); + } } glPopMatrix(); glDisable(GL_SCISSOR_TEST); diff --git a/interface/src/devices/Visage.cpp b/interface/src/devices/Visage.cpp index 119d89654a..5de2746b07 100644 --- a/interface/src/devices/Visage.cpp +++ b/interface/src/devices/Visage.cpp @@ -11,6 +11,7 @@ #include +#include #include #include @@ -128,6 +129,7 @@ void Visage::update() { if (!_active) { return; } + PerformanceTimer perfTimer("visage"); _headRotation = glm::quat(glm::vec3(-_data->faceRotation[0], -_data->faceRotation[1], _data->faceRotation[2])); _headTranslation = (glm::vec3(_data->faceTranslation[0], _data->faceTranslation[1], _data->faceTranslation[2]) - _headOrigin) * TRANSLATION_SCALE; diff --git a/interface/src/renderer/GlowEffect.cpp b/interface/src/renderer/GlowEffect.cpp index c163136956..1fdebb66d7 100644 --- a/interface/src/renderer/GlowEffect.cpp +++ b/interface/src/renderer/GlowEffect.cpp @@ -121,7 +121,7 @@ static void maybeRelease(QOpenGLFramebufferObject* fbo) { } QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) { - PerformanceTimer perfTimer("paintGL/glowEffect"); + PerformanceTimer perfTimer("glowEffect"); QOpenGLFramebufferObject* primaryFBO = Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject(); primaryFBO->release(); diff --git a/interface/src/renderer/JointState.cpp b/interface/src/renderer/JointState.cpp index 429084480d..5a2766d39f 100644 --- a/interface/src/renderer/JointState.cpp +++ b/interface/src/renderer/JointState.cpp @@ -26,7 +26,7 @@ JointState::JointState() : JointState::JointState(const JointState& other) : _constraint(NULL) { _transform = other._transform; _rotation = other._rotation; - _rotationInParentFrame = other._rotationInParentFrame; + _rotationInConstrainedFrame = other._rotationInConstrainedFrame; _animationPriority = other._animationPriority; _fbxJoint = other._fbxJoint; // DO NOT copy _constraint @@ -43,7 +43,7 @@ JointState::~JointState() { void JointState::setFBXJoint(const FBXJoint* joint) { assert(joint != NULL); - _rotationInParentFrame = joint->rotation; + _rotationInConstrainedFrame = joint->rotation; // NOTE: JointState does not own the FBXJoint to which it points. _fbxJoint = joint; if (_constraint) { @@ -68,24 +68,24 @@ void JointState::copyState(const JointState& state) { _animationPriority = state._animationPriority; _transform = state._transform; _rotation = extractRotation(_transform); - _rotationInParentFrame = state._rotationInParentFrame; + _rotationInConstrainedFrame = state._rotationInConstrainedFrame; _visibleTransform = state._visibleTransform; _visibleRotation = extractRotation(_visibleTransform); - _visibleRotationInParentFrame = state._visibleRotationInParentFrame; + _visibleRotationInConstrainedFrame = state._visibleRotationInConstrainedFrame; // DO NOT copy _fbxJoint or _constraint } void JointState::computeTransform(const glm::mat4& parentTransform) { - glm::quat modifiedRotation = _fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation; - glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(modifiedRotation) * _fbxJoint->postTransform; + glm::quat rotationInConstrainedFrame = _fbxJoint->preRotation * _rotationInConstrainedFrame * _fbxJoint->postRotation; + glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(rotationInConstrainedFrame) * _fbxJoint->postTransform; _transform = parentTransform * glm::translate(_fbxJoint->translation) * modifiedTransform; _rotation = extractRotation(_transform); } void JointState::computeVisibleTransform(const glm::mat4& parentTransform) { - glm::quat modifiedRotation = _fbxJoint->preRotation * _visibleRotationInParentFrame * _fbxJoint->postRotation; - glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(modifiedRotation) * _fbxJoint->postTransform; + glm::quat rotationInConstrainedFrame = _fbxJoint->preRotation * _visibleRotationInConstrainedFrame * _fbxJoint->postRotation; + glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(rotationInConstrainedFrame) * _fbxJoint->postTransform; _visibleTransform = parentTransform * glm::translate(_fbxJoint->translation) * modifiedTransform; _visibleRotation = extractRotation(_visibleTransform); } @@ -97,7 +97,7 @@ glm::quat JointState::getRotationFromBindToModelFrame() const { void JointState::restoreRotation(float fraction, float priority) { assert(_fbxJoint != NULL); if (priority == _animationPriority || _animationPriority == 0.0f) { - setRotationInParentFrame(safeMix(_rotationInParentFrame, _fbxJoint->rotation, fraction)); + setRotationInConstrainedFrame(safeMix(_rotationInConstrainedFrame, _fbxJoint->rotation, fraction)); _animationPriority = 0.0f; } } @@ -106,11 +106,11 @@ void JointState::setRotationFromBindFrame(const glm::quat& rotation, float prior // rotation is from bind- to model-frame assert(_fbxJoint != NULL); if (priority >= _animationPriority) { - glm::quat targetRotation = _rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation); + glm::quat targetRotation = _rotationInConstrainedFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation); if (constrain && _constraint) { - _constraint->softClamp(targetRotation, _rotationInParentFrame, 0.5f); + _constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f); } - setRotationInParentFrame(targetRotation); + setRotationInConstrainedFrame(targetRotation); _animationPriority = priority; } } @@ -137,12 +137,12 @@ void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, floa _animationPriority = priority; if (!constrain || _constraint == NULL) { // no constraints - _rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation; + _rotationInConstrainedFrame = _rotationInConstrainedFrame * glm::inverse(_rotation) * delta * _rotation; _rotation = delta * _rotation; return; } - glm::quat targetRotation = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation; - setRotationInParentFrame(targetRotation); + glm::quat targetRotation = _rotationInConstrainedFrame * glm::inverse(_rotation) * delta * _rotation; + setRotationInConstrainedFrame(targetRotation); } /// Applies delta rotation to joint but mixes a little bit of the default pose as well. @@ -154,30 +154,30 @@ void JointState::mixRotationDelta(const glm::quat& delta, float mixFactor, float return; } _animationPriority = priority; - glm::quat targetRotation = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation; + glm::quat targetRotation = _rotationInConstrainedFrame * glm::inverse(_rotation) * delta * _rotation; if (mixFactor > 0.0f && mixFactor <= 1.0f) { targetRotation = safeMix(targetRotation, _fbxJoint->rotation, mixFactor); } if (_constraint) { - _constraint->softClamp(targetRotation, _rotationInParentFrame, 0.5f); + _constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f); } - setRotationInParentFrame(targetRotation); + setRotationInConstrainedFrame(targetRotation); } glm::quat JointState::computeParentRotation() const { // R = Rp * Rpre * r * Rpost // Rp = R * (Rpre * r * Rpost)^ - return _rotation * glm::inverse(_fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation); + return _rotation * glm::inverse(_fbxJoint->preRotation * _rotationInConstrainedFrame * _fbxJoint->postRotation); } -void JointState::setRotationInParentFrame(const glm::quat& targetRotation) { +void JointState::setRotationInConstrainedFrame(const glm::quat& targetRotation) { glm::quat parentRotation = computeParentRotation(); - _rotationInParentFrame = targetRotation; + _rotationInConstrainedFrame = targetRotation; // R' = Rp * Rpre * r' * Rpost - _rotation = parentRotation * _fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation; + _rotation = parentRotation * _fbxJoint->preRotation * _rotationInConstrainedFrame * _fbxJoint->postRotation; } -const glm::vec3& JointState::getDefaultTranslationInParentFrame() const { +const glm::vec3& JointState::getDefaultTranslationInConstrainedFrame() const { assert(_fbxJoint != NULL); return _fbxJoint->translation; } @@ -185,5 +185,5 @@ const glm::vec3& JointState::getDefaultTranslationInParentFrame() const { void JointState::slaveVisibleTransform() { _visibleTransform = _transform; _visibleRotation = _rotation; - _visibleRotationInParentFrame = _rotationInParentFrame; + _visibleRotationInConstrainedFrame = _rotationInConstrainedFrame; } diff --git a/interface/src/renderer/JointState.h b/interface/src/renderer/JointState.h index 3bd752cdff..049eb6e6b0 100644 --- a/interface/src/renderer/JointState.h +++ b/interface/src/renderer/JointState.h @@ -66,14 +66,14 @@ public: void restoreRotation(float fraction, float priority); /// \param rotation is from bind- to model-frame - /// computes and sets new _rotationInParentFrame + /// computes and sets new _rotationInConstrainedFrame /// NOTE: the JointState's model-frame transform/rotation are NOT updated! void setRotationFromBindFrame(const glm::quat& rotation, float priority, bool constrain = false); - void setRotationInParentFrame(const glm::quat& targetRotation); - const glm::quat& getRotationInParentFrame() const { return _rotationInParentFrame; } + void setRotationInConstrainedFrame(const glm::quat& targetRotation); + const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; } - const glm::vec3& getDefaultTranslationInParentFrame() const; + const glm::vec3& getDefaultTranslationInConstrainedFrame() const; void clearTransformTranslation(); @@ -92,11 +92,11 @@ private: glm::mat4 _transform; // joint- to model-frame glm::quat _rotation; // joint- to model-frame - glm::quat _rotationInParentFrame; // joint- to parentJoint-frame + glm::quat _rotationInConstrainedFrame; // rotation in frame where angular constraints would be applied glm::mat4 _visibleTransform; glm::quat _visibleRotation; - glm::quat _visibleRotationInParentFrame; + glm::quat _visibleRotationInConstrainedFrame; const FBXJoint* _fbxJoint; // JointState does NOT own its FBXJoint AngularConstraint* _constraint; // JointState owns its AngularConstraint diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 723297f6b4..cbdbff072b 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -460,7 +460,7 @@ void Model::reset() { } const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointStates.size(); i++) { - _jointStates[i].setRotationInParentFrame(geometry.joints.at(i).rotation); + _jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation); } } @@ -688,7 +688,7 @@ bool Model::getJointState(int index, glm::quat& rotation) const { if (index == -1 || index >= _jointStates.size()) { return false; } - rotation = _jointStates.at(index).getRotationInParentFrame(); + rotation = _jointStates.at(index).getRotationInConstrainedFrame(); const glm::quat& defaultRotation = _geometry->getFBXGeometry().joints.at(index).rotation; return glm::abs(rotation.x - defaultRotation.x) >= EPSILON || glm::abs(rotation.y - defaultRotation.y) >= EPSILON || @@ -701,7 +701,7 @@ void Model::setJointState(int index, bool valid, const glm::quat& rotation, floa JointState& state = _jointStates[index]; if (priority >= state._animationPriority) { if (valid) { - state.setRotationInParentFrame(rotation); + state.setRotationInConstrainedFrame(rotation); state._animationPriority = priority; } else { state.restoreRotation(1.0f, priority); @@ -1488,14 +1488,19 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re if (cascadedShadows) { program->setUniform(skinLocations->shadowDistances, Application::getInstance()->getShadowDistances()); } - } else { + + // local light uniforms + skinProgram->setUniformValue("numLocalLights", _numLocalLights); + skinProgram->setUniformArray("localLightDirections", _localLightDirections, MAX_LOCAL_LIGHTS); + skinProgram->setUniformArray("localLightColors", _localLightColors, MAX_LOCAL_LIGHTS); + } else { glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]); program->bind(); if (cascadedShadows) { program->setUniform(shadowDistancesLocation, Application::getInstance()->getShadowDistances()); } } - + if (mesh.blendshapes.isEmpty()) { if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) { activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3); @@ -1622,6 +1627,20 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re } } +void Model::setLocalLightDirection(const glm::vec3& direction, int lightIndex) { + assert(lightIndex >= 0 && lightIndex < MAX_LOCAL_LIGHTS); + _localLightDirections[lightIndex] = direction; +} + +void Model::setLocalLightColor(const glm::vec3& color, int lightIndex) { + assert(lightIndex >= 0 && lightIndex < MAX_LOCAL_LIGHTS); + _localLightColors[lightIndex] = color; +} + +void Model::setNumLocalLights(int numLocalLights) { + _numLocalLights = numLocalLights; +} + void AnimationHandle::setURL(const QUrl& url) { if (_url != url) { _animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url); @@ -1768,7 +1787,7 @@ void AnimationHandle::applyFrame(float frameIndex) { if (mapping != -1) { JointState& state = _model->_jointStates[mapping]; if (_priority >= state._animationPriority) { - state.setRotationInParentFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction)); + state.setRotationInConstrainedFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction)); state._animationPriority = _priority; } } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 5e29b869e0..9e2e0d8348 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -32,6 +32,8 @@ class Shape; typedef QSharedPointer AnimationHandlePointer; typedef QWeakPointer WeakAnimationHandlePointer; +const int MAX_LOCAL_LIGHTS = 2; + /// A generic 3D model displaying geometry loaded from a URL. class Model : public QObject, public PhysicsEntity { Q_OBJECT @@ -143,6 +145,10 @@ public: /// Sets blended vertices computed in a separate thread. void setBlendedVertices(const QVector& vertices, const QVector& normals); + void setLocalLightDirection(const glm::vec3& direction, int lightIndex); + void setLocalLightColor(const glm::vec3& color, int lightIndex); + void setNumLocalLights(int numLocalLights); + protected: QSharedPointer _geometry; @@ -158,6 +164,10 @@ protected: bool _showTrueJointTransforms; int _rootIndex; + glm::vec3 _localLightDirections[MAX_LOCAL_LIGHTS]; + glm::vec3 _localLightColors[MAX_LOCAL_LIGHTS]; + int _numLocalLights; + QVector _jointStates; class MeshState { diff --git a/interface/src/renderer/ProgramObject.cpp b/interface/src/renderer/ProgramObject.cpp index b88be69f07..16b3461ad0 100644 --- a/interface/src/renderer/ProgramObject.cpp +++ b/interface/src/renderer/ProgramObject.cpp @@ -10,6 +10,7 @@ // #include "ProgramObject.h" +#include ProgramObject::ProgramObject(QObject* parent) : QGLShaderProgram(parent) { } @@ -22,3 +23,17 @@ void ProgramObject::setUniform(const char* name, const glm::vec3& value) { setUniformValue(name, value.x, value.y, value.z); } +void ProgramObject::setUniformArray(const char* name, const glm::vec3* values, int count) { + GLfloat* floatVal = new GLfloat[count*3]; + int index = 0; + for (int i = 0; i < count; i++) { + assert(index < count*3); + const float* valPtr = glm::value_ptr(values[i]); + floatVal[index++] = valPtr[0]; + floatVal[index++] = valPtr[1]; + floatVal[index++] = valPtr[2]; + } + + setUniformValueArray(name, floatVal, count, 3); + delete[] floatVal; +} diff --git a/interface/src/renderer/ProgramObject.h b/interface/src/renderer/ProgramObject.h index 21e01ac8b3..8e66ce9bc9 100644 --- a/interface/src/renderer/ProgramObject.h +++ b/interface/src/renderer/ProgramObject.h @@ -23,6 +23,7 @@ public: void setUniform(int location, const glm::vec3& value); void setUniform(const char* name, const glm::vec3& value); + void setUniformArray(const char* name, const glm::vec3* values, int count); }; #endif // hifi_ProgramObject_h diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 4c445958ec..7b3db26d8d 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -40,7 +40,7 @@ ApplicationOverlay::ApplicationOverlay() : _framebufferObject(NULL), _textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE), _alpha(1.0f), - _active(true), + _oculusuiRadius(1.0f), _crosshairTexture(0) { memset(_reticleActive, 0, sizeof(_reticleActive)); @@ -70,8 +70,8 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { QGLWidget* glWidget = application->getGLWidget(); MyAvatar* myAvatar = application->getAvatar(); - //Handle fadeing and deactivation/activation of UI - if (_active) { + //Handle fading and deactivation/activation of UI + if (Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) { _alpha += FADE_SPEED; if (_alpha > 1.0f) { _alpha = 1.0f; @@ -165,7 +165,7 @@ void ApplicationOverlay::displayOverlayTexture() { } void ApplicationOverlay::computeOculusPickRay(float x, float y, glm::vec3& direction) const { - glm::quat rot = Application::getInstance()->getAvatar()->getOrientation(); + MyAvatar* myAvatar = Application::getInstance()->getAvatar(); //invert y direction y = 1.0 - y; @@ -177,8 +177,11 @@ void ApplicationOverlay::computeOculusPickRay(float x, float y, glm::vec3& direc float dist = sqrt(x * x + y * y); float z = -sqrt(1.0f - dist * dist); + glm::vec3 relativePosition = myAvatar->getHead()->calculateAverageEyePosition() + + glm::normalize(myAvatar->getOrientation() * glm::vec3(x, y, z)); + //Rotate the UI pick ray by the avatar orientation - direction = glm::normalize(rot * glm::vec3(x, y, z)); + direction = glm::normalize(relativePosition - Application::getInstance()->getCamera()->getPosition()); } // Calculates the click location on the screen by taking into account any @@ -187,7 +190,7 @@ void ApplicationOverlay::getClickLocation(int &x, int &y) const { int dx; int dy; const float xRange = MAGNIFY_WIDTH * MAGNIFY_MULT / 2.0f; - const float yRange = MAGNIFY_WIDTH * MAGNIFY_MULT / 2.0f; + const float yRange = MAGNIFY_HEIGHT * MAGNIFY_MULT / 2.0f; //Loop through all magnification windows for (int i = 0; i < NUMBER_OF_MAGNIFIERS; i++) { @@ -205,6 +208,126 @@ void ApplicationOverlay::getClickLocation(int &x, int &y) const { } } +//Checks if the given ray intersects the sphere at the origin. result will store a multiplier that should +//be multiplied by dir and added to origin to get the location of the collision +bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, float* result) +{ + //Source: http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection + + //Compute A, B and C coefficients + float a = glm::dot(dir, dir); + float b = 2 * glm::dot(dir, origin); + float c = glm::dot(origin, origin) - (r * r); + + //Find discriminant + float disc = b * b - 4 * a * c; + + // if discriminant is negative there are no real roots, so return + // false as ray misses sphere + if (disc < 0) { + return false; + } + + // compute q as described above + float distSqrt = sqrtf(disc); + float q; + if (b < 0) { + q = (-b - distSqrt) / 2.0; + } else { + q = (-b + distSqrt) / 2.0; + } + + // compute t0 and t1 + float t0 = q / a; + float t1 = c / q; + + // make sure t0 is smaller than t1 + if (t0 > t1) { + // if t0 is bigger than t1 swap them around + float temp = t0; + t0 = t1; + t1 = temp; + } + + // if t1 is less than zero, the object is in the ray's negative direction + // and consequently the ray misses the sphere + if (t1 < 0) { + return false; + } + + // if t0 is less than zero, the intersection point is at t1 + if (t0 < 0) { + *result = t1; + return true; + } else { // else the intersection point is at t0 + *result = t0; + return true; + } +} + +//Caculate the click location using one of the sixense controllers. Scale is not applied +QPoint ApplicationOverlay::getOculusPalmClickLocation(const PalmData *palm) const { + + Application* application = Application::getInstance(); + QGLWidget* glWidget = application->getGLWidget(); + MyAvatar* myAvatar = application->getAvatar(); + + glm::vec3 tip = OculusManager::getLaserPointerTipPosition(palm); + glm::vec3 eyePos = myAvatar->getHead()->calculateAverageEyePosition(); + glm::quat orientation = glm::inverse(myAvatar->getOrientation()); + glm::vec3 dir = orientation * glm::normalize(application->getCamera()->getPosition() - tip); //direction of ray goes towards camera + glm::vec3 tipPos = orientation * (tip - eyePos); + + QPoint rv; + + float t; + + //We back the ray up by dir to ensure that it will not start inside the UI. + glm::vec3 adjustedPos = tipPos - dir; + //Find intersection of crosshair ray. + if (raySphereIntersect(dir, adjustedPos, _oculusuiRadius * myAvatar->getScale(), &t)){ + glm::vec3 collisionPos = adjustedPos + dir * t; + //Normalize it in case its not a radius of 1 + collisionPos = glm::normalize(collisionPos); + //If we hit the back hemisphere, mark it as not a collision + if (collisionPos.z > 0) { + rv.setX(INT_MAX); + rv.setY(INT_MAX); + } else { + + float u = asin(collisionPos.x) / (_textureFov)+0.5f; + float v = 1.0 - (asin(collisionPos.y) / (_textureFov)+0.5f); + + rv.setX(u * glWidget->width()); + rv.setY(v * glWidget->height()); + } + } else { + //if they did not click on the overlay, just set the coords to INT_MAX + rv.setX(INT_MAX); + rv.setY(INT_MAX); + } + return rv; +} + +//Finds the collision point of a world space ray +bool ApplicationOverlay::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const { + Application* application = Application::getInstance(); + MyAvatar* myAvatar = application->getAvatar(); + + glm::quat orientation = myAvatar->getOrientation(); + + glm::vec3 relativePosition = orientation * (position - myAvatar->getHead()->calculateAverageEyePosition()); + glm::vec3 relativeDirection = orientation * direction; + + float t; + if (raySphereIntersect(relativeDirection, relativePosition, _oculusuiRadius * myAvatar->getScale(), &t)){ + result = position + direction * t; + return true; + } + + return false; +} + // Draws the FBO texture for Oculus rift. void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { @@ -215,41 +338,37 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { Application* application = Application::getInstance(); MyAvatar* myAvatar = application->getAvatar(); - const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation(); + + //Render the sixense lasers + OculusManager::renderLaserPointers(); glActiveTexture(GL_TEXTURE0); glEnable(GL_BLEND); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture()); glEnable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture()); + glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glLoadIdentity(); - // Transform to world space - glm::quat rotation = whichCamera.getRotation(); - glm::vec3 axis2 = glm::axis(rotation); - glRotatef(-glm::degrees(glm::angle(rotation)), axis2.x, axis2.y, axis2.z); - glTranslatef(viewMatrixTranslation.x, viewMatrixTranslation.y, viewMatrixTranslation.z); - - // Translate to the front of the camera - glm::vec3 pos = whichCamera.getPosition(); - glm::quat rot = myAvatar->getOrientation(); - glm::vec3 axis = glm::axis(rot); - - glTranslatef(pos.x, pos.y, pos.z); - glRotatef(glm::degrees(glm::angle(rot)), axis.x, axis.y, axis.z); - glDepthMask(GL_TRUE); glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.01f); //Update and draw the magnifiers + + glPushMatrix(); + const glm::quat& orientation = myAvatar->getOrientation(); + const glm::vec3& position = myAvatar->getHead()->calculateAverageEyePosition(); + + glm::mat4 rotation = glm::toMat4(orientation); + + glTranslatef(position.x, position.y, position.z); + glMultMatrixf(&rotation[0][0]); for (int i = 0; i < NUMBER_OF_MAGNIFIERS; i++) { if (_magActive[i]) { @@ -269,6 +388,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { renderMagnifier(_magX[i], _magY[i], _magSizeMult[i], i != MOUSE); } } + glPopMatrix(); glDepthMask(GL_FALSE); glDisable(GL_ALPHA_TEST); @@ -276,10 +396,8 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { glColor4f(1.0f, 1.0f, 1.0f, _alpha); renderTexturedHemisphere(); - - renderControllerPointersOculus(); - glPopMatrix(); + renderPointersOculus(whichCamera.getPosition()); glDepthMask(GL_TRUE); glBindTexture(GL_TEXTURE_2D, 0); @@ -334,7 +452,6 @@ void ApplicationOverlay::displayOverlayTexture3DTV(Camera& whichCamera, float as glColor4f(1.0f, 1.0f, 1.0f, _alpha); //Render - // fov -= RADIANS_PER_DEGREE * 2.5f; //reduce by 5 degrees so it fits in the view const GLfloat distance = 1.0f; const GLfloat halfQuadHeight = distance * tan(fov); @@ -406,7 +523,6 @@ void ApplicationOverlay::renderPointers() { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, _crosshairTexture); - if (OculusManager::isConnected() && application->getLastMouseMoveType() == QEvent::MouseMove) { //If we are in oculus, render reticle later _reticleActive[MOUSE] = true; @@ -415,7 +531,6 @@ void ApplicationOverlay::renderPointers() { _mouseY[MOUSE] = application->getMouseY(); _magX[MOUSE] = _mouseX[MOUSE]; _magY[MOUSE] = _mouseY[MOUSE]; - _reticleActive[LEFT_CONTROLLER] = false; _reticleActive[RIGHT_CONTROLLER] = false; @@ -485,7 +600,8 @@ void ApplicationOverlay::renderControllerPointers() { if (palmData->getTrigger() == 1.0f) { if (!triggerPressed[index]) { if (bumperPressed[index]) { - _active = !_active; + Menu::getInstance()->setIsOptionChecked(MenuOption::UserInterface, + !Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)); } triggerPressed[index] = true; } @@ -495,20 +611,40 @@ void ApplicationOverlay::renderControllerPointers() { if ((controllerButtons & BUTTON_FWD)) { if (!bumperPressed[index]) { if (triggerPressed[index]) { - _active = !_active; + Menu::getInstance()->setIsOptionChecked(MenuOption::UserInterface, + !Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)); } bumperPressed[index] = true; } } else { bumperPressed[index] = false; } + + //if we have the oculus, we should make the cursor smaller since it will be + //magnified + if (OculusManager::isConnected()) { + QPoint point = getOculusPalmClickLocation(palmData); + + _mouseX[index] = point.x(); + _mouseY[index] = point.y(); + + //When button 2 is pressed we drag the mag window + if (isPressed[index]) { + _magActive[index] = true; + _magX[index] = point.x(); + _magY[index] = point.y(); + } + + // If oculus is enabled, we draw the crosshairs later + continue; + } // Get directon relative to avatar orientation glm::vec3 direction = glm::inverse(myAvatar->getOrientation()) * palmData->getFingerDirection(); // Get the angles, scaled between (-0.5,0.5) - float xAngle = (atan2(direction.z, direction.x) + M_PI_2) ; + float xAngle = (atan2(direction.z, direction.x) + M_PI_2); float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); // Get the pixel range over which the xAngle and yAngle are scaled @@ -523,24 +659,7 @@ void ApplicationOverlay::renderControllerPointers() { continue; } _reticleActive[index] = true; - - //if we have the oculus, we should make the cursor smaller since it will be - //magnified - if (OculusManager::isConnected()) { - - _mouseX[index] = mouseX; - _mouseY[index] = mouseY; - //When button 2 is pressed we drag the mag window - if (isPressed[index]) { - _magActive[index] = true; - _magX[index] = mouseX; - _magY[index] = mouseY; - } - - // If oculus is enabled, we draw the crosshairs later - continue; - } const float reticleSize = 40.0f; @@ -560,9 +679,11 @@ void ApplicationOverlay::renderControllerPointers() { } } -void ApplicationOverlay::renderControllerPointersOculus() { +void ApplicationOverlay::renderPointersOculus(const glm::vec3& eyePos) { + Application* application = Application::getInstance(); QGLWidget* glWidget = application->getGLWidget(); + glm::vec3 cursorVerts[4]; const int widgetWidth = glWidget->width(); const int widgetHeight = glWidget->height(); @@ -571,19 +692,86 @@ void ApplicationOverlay::renderControllerPointersOculus() { glBindTexture(GL_TEXTURE_2D, _crosshairTexture); glDisable(GL_DEPTH_TEST); - - for (int i = 0; i < NUMBER_OF_MAGNIFIERS; i++) { + glMatrixMode(GL_MODELVIEW); + MyAvatar* myAvatar = application->getAvatar(); - //Dont render the reticle if its inactive - if (!_reticleActive[i]) { - continue; - } + //Controller Pointers + for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { - float mouseX = (float)_mouseX[i]; - float mouseY = (float)_mouseY[i]; + PalmData& palm = myAvatar->getHand()->getPalms()[i]; + if (palm.isActive()) { + glm::vec3 tip = OculusManager::getLaserPointerTipPosition(&palm); + glm::vec3 tipPos = (tip - eyePos); + + float length = glm::length(eyePos - tip); + float size = 0.03f * length; + + glm::vec3 up = glm::vec3(0.0, 1.0, 0.0) * size; + glm::vec3 right = glm::vec3(1.0, 0.0, 0.0) * size; + + cursorVerts[0] = -right + up; + cursorVerts[1] = right + up; + cursorVerts[2] = right - up; + cursorVerts[3] = -right - up; + + glPushMatrix(); + + // objToCamProj is the vector in world coordinates from the + // local origin to the camera projected in the XZ plane + glm::vec3 cursorToCameraXZ(-tipPos.x, 0, -tipPos.z); + cursorToCameraXZ = glm::normalize(cursorToCameraXZ); + + //Translate the cursor to the tip of the oculus ray + glTranslatef(tip.x, tip.y, tip.z); + + glm::vec3 direction(0, 0, 1); + // easy fix to determine wether the angle is negative or positive + // for positive angles upAux will be a vector pointing in the + // positive y direction, otherwise upAux will point downwards + // effectively reversing the rotation. + glm::vec3 upAux = glm::cross(direction, cursorToCameraXZ); + + // compute the angle + float angleCosine = glm::dot(direction, cursorToCameraXZ); + + //Rotate in XZ direction + glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, upAux[0], upAux[1], upAux[2]); + + glm::vec3 cursorToCamera = glm::normalize(-tipPos); + + // Compute the angle between cursorToCameraXZ and cursorToCamera, + angleCosine = glm::dot(cursorToCameraXZ, cursorToCamera); + + //Rotate in Y direction + if (cursorToCamera.y < 0) { + glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, 1, 0, 0); + } else { + glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, -1, 0, 0); + } + + glBegin(GL_QUADS); + + glColor4f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], _alpha); + + glTexCoord2f(0.0f, 0.0f); glVertex3f(cursorVerts[0].x, cursorVerts[0].y, cursorVerts[0].z); + glTexCoord2f(1.0f, 0.0f); glVertex3f(cursorVerts[1].x, cursorVerts[1].y, cursorVerts[1].z); + glTexCoord2f(1.0f, 1.0f); glVertex3f(cursorVerts[2].x, cursorVerts[2].y, cursorVerts[2].z); + glTexCoord2f(0.0f, 1.0f); glVertex3f(cursorVerts[3].x, cursorVerts[3].y, cursorVerts[3].z); + + glEnd(); + + glPopMatrix(); + } + } + + //Mouse Pointer + if (_reticleActive[MOUSE]) { + + float mouseX = (float)_mouseX[MOUSE]; + float mouseY = (float)_mouseY[MOUSE]; mouseX -= reticleSize / 2; mouseY += reticleSize / 2; - + //Get new UV coordinates from our magnification window float newULeft = mouseX / widgetWidth; float newURight = (mouseX + reticleSize) / widgetWidth; @@ -613,15 +801,22 @@ void ApplicationOverlay::renderControllerPointersOculus() { glBegin(GL_QUADS); glColor4f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], _alpha); - - glTexCoord2f(0.0f, 0.0f); glVertex3f(lX, tY, -tlZ); - glTexCoord2f(1.0f, 0.0f); glVertex3f(rX, tY, -trZ); - glTexCoord2f(1.0f, 1.0f); glVertex3f(rX, bY, -brZ); - glTexCoord2f(0.0f, 1.0f); glVertex3f(lX, bY, -blZ); + + const glm::quat& orientation = myAvatar->getOrientation(); + cursorVerts[0] = orientation * glm::vec3(lX, tY, -tlZ) + eyePos; + cursorVerts[1] = orientation * glm::vec3(rX, tY, -trZ) + eyePos; + cursorVerts[2] = orientation * glm::vec3(rX, bY, -brZ) + eyePos; + cursorVerts[3] = orientation * glm::vec3(lX, bY, -blZ) + eyePos; + + glTexCoord2f(0.0f, 0.0f); glVertex3f(cursorVerts[0].x, cursorVerts[0].y, cursorVerts[0].z); + glTexCoord2f(1.0f, 0.0f); glVertex3f(cursorVerts[1].x, cursorVerts[1].y, cursorVerts[1].z); + glTexCoord2f(1.0f, 1.0f); glVertex3f(cursorVerts[2].x, cursorVerts[2].y, cursorVerts[2].z); + glTexCoord2f(0.0f, 1.0f); glVertex3f(cursorVerts[3].x, cursorVerts[3].y, cursorVerts[3].z); glEnd(); - } + + glEnable(GL_DEPTH_TEST); } @@ -662,18 +857,22 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY, float sizeMult, float newVTop = 1.0 - (newMouseY - newHeight) / widgetHeight; // Project our position onto the hemisphere using the UV coordinates - float lX = sin((newULeft - 0.5f) * _textureFov); - float rX = sin((newURight - 0.5f) * _textureFov); - float bY = sin((newVBottom - 0.5f) * _textureFov); - float tY = sin((newVTop - 0.5f) * _textureFov); + float radius = _oculusuiRadius * application->getAvatar()->getScale(); + float radius2 = radius * radius; + + float lX = radius * sin((newULeft - 0.5f) * _textureFov); + float rX = radius * sin((newURight - 0.5f) * _textureFov); + float bY = radius * sin((newVBottom - 0.5f) * _textureFov); + float tY = radius * sin((newVTop - 0.5f) * _textureFov); float blZ, tlZ, brZ, trZ; float dist; float discriminant; + //Bottom Left dist = sqrt(lX * lX + bY * bY); - discriminant = 1.0f - dist * dist; + discriminant = radius2 - dist * dist; if (discriminant > 0) { blZ = sqrt(discriminant); } else { @@ -681,7 +880,7 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY, float sizeMult, } //Top Left dist = sqrt(lX * lX + tY * tY); - discriminant = 1.0f - dist * dist; + discriminant = radius2 - dist * dist; if (discriminant > 0) { tlZ = sqrt(discriminant); } else { @@ -689,7 +888,7 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY, float sizeMult, } //Bottom Right dist = sqrt(rX * rX + bY * bY); - discriminant = 1.0f - dist * dist; + discriminant = radius2 - dist * dist; if (discriminant > 0) { brZ = sqrt(discriminant); } else { @@ -697,7 +896,7 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY, float sizeMult, } //Top Right dist = sqrt(rX * rX + tY * tY); - discriminant = 1.0f - dist * dist; + discriminant = radius2 - dist * dist; if (discriminant > 0) { trZ = sqrt(discriminant); } else { @@ -987,9 +1186,25 @@ void ApplicationOverlay::renderTexturedHemisphere() { glVertexPointer(3, GL_FLOAT, sizeof(TextureVertex), (void*)0); glTexCoordPointer(2, GL_FLOAT, sizeof(TextureVertex), (void*)12); + + glPushMatrix(); + Application* application = Application::getInstance(); + MyAvatar* myAvatar = application->getAvatar(); + const glm::quat& orientation = myAvatar->getOrientation(); + const glm::vec3& position = myAvatar->getHead()->calculateAverageEyePosition(); + + glm::mat4 rotation = glm::toMat4(orientation); + + glTranslatef(position.x, position.y, position.z); + glMultMatrixf(&rotation[0][0]); + const float scale = _oculusuiRadius * myAvatar->getScale(); + glScalef(scale, scale, scale); + glDrawRangeElements(GL_TRIANGLES, 0, vertices - 1, indices, GL_UNSIGNED_SHORT, 0); + glPopMatrix(); + glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); @@ -999,8 +1214,12 @@ void ApplicationOverlay::renderTexturedHemisphere() { } QOpenGLFramebufferObject* ApplicationOverlay::getFramebufferObject() { - if (!_framebufferObject) { - _framebufferObject = new QOpenGLFramebufferObject(Application::getInstance()->getGLWidget()->size()); + QSize size = Application::getInstance()->getGLWidget()->size(); + if (!_framebufferObject || _framebufferObject->size() != size) { + + delete _framebufferObject; + + _framebufferObject = new QOpenGLFramebufferObject(size); glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 7c1f87d575..33ba818f40 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -32,6 +32,9 @@ public: void displayOverlayTexture3DTV(Camera& whichCamera, float aspectRatio, float fov); void computeOculusPickRay(float x, float y, glm::vec3& direction) const; void getClickLocation(int &x, int &y) const; + QPoint getOculusPalmClickLocation(const PalmData *palm) const; + bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const; + // Getters QOpenGLFramebufferObject* getFramebufferObject(); @@ -48,7 +51,7 @@ private: void renderPointers(); void renderControllerPointers(); - void renderControllerPointersOculus(); + void renderPointersOculus(const glm::vec3& eyePos); void renderMagnifier(int mouseX, int mouseY, float sizeMult, bool showBorder) const; void renderAudioMeter(); void renderStatsAndLogs(); @@ -68,7 +71,7 @@ private: float _magSizeMult[NUMBER_OF_MAGNIFIERS]; float _alpha; - bool _active; + float _oculusuiRadius; GLuint _crosshairTexture; }; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 379dd35df7..b40db71132 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -24,6 +24,7 @@ #include "InterfaceConfig.h" #include "Menu.h" #include "Util.h" +#include "SequenceNumberStats.h" using namespace std; @@ -161,36 +162,25 @@ void Stats::drawBackground(unsigned int rgba, int x, int y, int width, int heigh } bool Stats::includeTimingRecord(const QString& name) { - bool included = false; if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) { - - if (name == "idle/update") { - included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandUpdateTiming) || - Menu::getInstance()->isOptionChecked(MenuOption::ExpandIdleTiming); - } else if (name == "idle/updateGL") { - included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandIdleTiming); - } else if (name.startsWith("idle/update")) { - included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandUpdateTiming); - } else if (name.startsWith("idle/")) { - included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandIdleTiming); - } else if (name.startsWith("MyAvatar::simulate")) { - included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandAvatarSimulateTiming); - } else if (name.startsWith("MyAvatar::update/") || name.startsWith("updateMyAvatar")) { - included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandAvatarUpdateTiming); - } else if (name.startsWith("MyAvatar::")) { - included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandMiscAvatarTiming); - } else if (name == "paintGL/displaySide") { - included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandDisplaySideTiming) || - Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming); - } else if (name.startsWith("paintGL/displaySide/")) { - included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandDisplaySideTiming); - } else if (name.startsWith("paintGL/")) { - included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming); - } else { - included = true; // include everything else + if (name.startsWith("/idle/update/")) { + if (name.startsWith("/idle/update/myAvatar/")) { + if (name.startsWith("/idle/update/myAvatar/simulate/")) { + return Menu::getInstance()->isOptionChecked(MenuOption::ExpandMyAvatarSimulateTiming); + } + return Menu::getInstance()->isOptionChecked(MenuOption::ExpandMyAvatarTiming); + } else if (name.startsWith("/idle/update/otherAvatars/")) { + return Menu::getInstance()->isOptionChecked(MenuOption::ExpandOtherAvatarTiming); + } + return Menu::getInstance()->isOptionChecked(MenuOption::ExpandUpdateTiming); + } else if (name.startsWith("/idle/updateGL/paintGL/")) { + return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming); + } else if (name.startsWith("/paintGL/")) { + return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming); } + return true; } - return included; + return false; } // display expanded or contracted stats @@ -288,15 +278,12 @@ void Stats::display( Audio* audio = Application::getInstance()->getAudio(); - const AudioStreamStats& audioMixerAvatarStreamStats = audio->getAudioMixerAvatarStreamStats(); - const QHash& audioMixerInjectedStreamStatsMap = audio->getAudioMixerInjectedStreamStatsMap(); + const QHash& audioMixerInjectedStreamAudioStatsMap = audio->getAudioMixerInjectedStreamAudioStatsMap(); - lines = _expanded ? 10 + audioMixerInjectedStreamStatsMap.size(): 3; + lines = _expanded ? 11 + (audioMixerInjectedStreamAudioStatsMap.size() + 2) * 3 : 3; drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10); horizontalOffset += 5; - - char audioJitter[30]; sprintf(audioJitter, "Buffer msecs %.1f", @@ -328,43 +315,103 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color); char audioMixerStatsLabelString[] = "AudioMixer stats:"; - char streamStatsFormatLabelString[] = "early/late/lost, jframes"; + char streamStatsFormatLabelString[] = "lost%/30s_lost%"; + char streamStatsFormatLabelString2[] = "avail/currJ/desiredJ"; + char streamStatsFormatLabelString3[] = "gaps: min/max/avg, starv/ovfl"; + char streamStatsFormatLabelString4[] = "30s gaps: (same), notmix/sdrop"; verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioMixerStatsLabelString, color); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString, color); - + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString2, color); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString3, color); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString4, color); char downstreamLabelString[] = " Downstream:"; verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color); - const SequenceNumberStats& downstreamAudioSequenceNumberStats = audio->getIncomingMixedAudioSequenceNumberStats(); char downstreamAudioStatsString[30]; - sprintf(downstreamAudioStatsString, " mix: %d/%d/%d, %d", downstreamAudioSequenceNumberStats.getNumEarly(), - downstreamAudioSequenceNumberStats.getNumLate(), downstreamAudioSequenceNumberStats.getNumLost(), - audio->getJitterBufferSamples() / NETWORK_BUFFER_LENGTH_SAMPLES_STEREO); + + AudioStreamStats downstreamAudioStreamStats = audio->getDownstreamAudioStreamStats(); + + sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %u/?/%u", downstreamAudioStreamStats._packetStreamStats.getLostRate()*100.0f, + downstreamAudioStreamStats._packetStreamWindowStats.getLostRate() * 100.0f, + downstreamAudioStreamStats._ringBufferFramesAvailable, downstreamAudioStreamStats._ringBufferDesiredJitterBufferFrames); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); + + sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", downstreamAudioStreamStats._timeGapMin, + downstreamAudioStreamStats._timeGapMax, downstreamAudioStreamStats._timeGapAverage, + downstreamAudioStreamStats._ringBufferStarveCount, downstreamAudioStreamStats._ringBufferOverflowCount); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); + + sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %u/?", downstreamAudioStreamStats._timeGapWindowMin, + downstreamAudioStreamStats._timeGapWindowMax, downstreamAudioStreamStats._timeGapWindowAverage, + downstreamAudioStreamStats._ringBufferConsecutiveNotMixedCount); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); + char upstreamLabelString[] = " Upstream:"; verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamLabelString, color); char upstreamAudioStatsString[30]; - sprintf(upstreamAudioStatsString, " mic: %d/%d/%d, %d", audioMixerAvatarStreamStats._packetsEarly, - audioMixerAvatarStreamStats._packetsLate, audioMixerAvatarStreamStats._packetsLost, - audioMixerAvatarStreamStats._jitterBufferFrames); + + const AudioStreamStats& audioMixerAvatarAudioStreamStats = audio->getAudioMixerAvatarStreamAudioStats(); + + sprintf(upstreamAudioStatsString, " mic: %.1f%%/%.1f%%, %u/%u/%u", audioMixerAvatarAudioStreamStats._packetStreamStats.getLostRate()*100.0f, + audioMixerAvatarAudioStreamStats._packetStreamWindowStats.getLostRate() * 100.0f, + audioMixerAvatarAudioStreamStats._ringBufferFramesAvailable, audioMixerAvatarAudioStreamStats._ringBufferCurrentJitterBufferFrames, + audioMixerAvatarAudioStreamStats._ringBufferDesiredJitterBufferFrames); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - foreach(AudioStreamStats injectedStreamStats, audioMixerInjectedStreamStatsMap) { - sprintf(upstreamAudioStatsString, " inj: %d/%d/%d, %d", injectedStreamStats._packetsEarly, - injectedStreamStats._packetsLate, injectedStreamStats._packetsLost, injectedStreamStats._jitterBufferFrames); - + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapMin, + audioMixerAvatarAudioStreamStats._timeGapMax, audioMixerAvatarAudioStreamStats._timeGapAverage, + audioMixerAvatarAudioStreamStats._ringBufferStarveCount, audioMixerAvatarAudioStreamStats._ringBufferOverflowCount); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); + + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapWindowMin, + audioMixerAvatarAudioStreamStats._timeGapWindowMax, audioMixerAvatarAudioStreamStats._timeGapWindowAverage, + audioMixerAvatarAudioStreamStats._ringBufferConsecutiveNotMixedCount, audioMixerAvatarAudioStreamStats._ringBufferSilentFramesDropped); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); + + foreach(const AudioStreamStats& injectedStreamAudioStats, audioMixerInjectedStreamAudioStatsMap) { + + sprintf(upstreamAudioStatsString, " inj: %.1f%%/%.1f%%, %u/%u/%u", injectedStreamAudioStats._packetStreamStats.getLostRate()*100.0f, + injectedStreamAudioStats._packetStreamWindowStats.getLostRate() * 100.0f, + injectedStreamAudioStats._ringBufferFramesAvailable, injectedStreamAudioStats._ringBufferCurrentJitterBufferFrames, + injectedStreamAudioStats._ringBufferDesiredJitterBufferFrames); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); + + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapMin, + injectedStreamAudioStats._timeGapMax, injectedStreamAudioStats._timeGapAverage, + injectedStreamAudioStats._ringBufferStarveCount, injectedStreamAudioStats._ringBufferOverflowCount); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); + + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapWindowMin, + injectedStreamAudioStats._timeGapWindowMax, injectedStreamAudioStats._timeGapWindowAverage, + injectedStreamAudioStats._ringBufferConsecutiveNotMixedCount, injectedStreamAudioStats._ringBufferSilentFramesDropped); + verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); } @@ -377,7 +424,7 @@ void Stats::display( MyAvatar* myAvatar = Application::getInstance()->getAvatar(); glm::vec3 avatarPos = myAvatar->getPosition(); - lines = _expanded ? 5 : 3; + lines = _expanded ? 8 : 3; drawBackground(backgroundColor, horizontalOffset, 0, _geoStatsWidth, lines * STATS_PELS_PER_LINE + 10); horizontalOffset += 5; @@ -419,6 +466,41 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downloads.str().c_str(), color); + + int internal = 0, leaves = 0; + int sendProgress = 0, sendTotal = 0; + int receiveProgress = 0, receiveTotal = 0; + foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { + if (node->getType() == NodeType::MetavoxelServer) { + QMutexLocker locker(&node->getMutex()); + MetavoxelClient* client = static_cast(node->getLinkedData()); + if (client) { + client->getData().countNodes(internal, leaves, Application::getInstance()->getMetavoxels()->getLOD()); + client->getSequencer().addReliableChannelStats(sendProgress, sendTotal, receiveProgress, receiveTotal); + } + } + } + stringstream nodes; + nodes << "Metavoxels: " << (internal + leaves); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, nodes.str().c_str(), color); + + stringstream nodeTypes; + nodeTypes << "Internal: " << internal << " Leaves: " << leaves; + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, nodeTypes.str().c_str(), color); + + if (sendTotal > 0 || receiveTotal > 0) { + stringstream reliableStats; + if (sendTotal > 0) { + reliableStats << "Upload: " << (sendProgress * 100 / sendTotal) << "% "; + } + if (receiveTotal > 0) { + reliableStats << "Download: " << (receiveProgress * 100 / receiveTotal) << "%"; + } + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, reliableStats.str().c_str(), color); + } } verticalOffset = 0; @@ -601,6 +683,8 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); } + PerformanceTimer::tallyAllTimerRecords(); + // TODO: the display of these timing details should all be moved to JavaScript if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) { // Timing details... diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 8742f19c3d..e7d5cef3be 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -14,28 +14,35 @@ #include "BillboardOverlay.h" BillboardOverlay::BillboardOverlay() -: _scale(1.0f), +: _fromImage(-1,-1,-1,-1), + _scale(1.0f), _isFacingAvatar(true) { } void BillboardOverlay::render() { - if (_billboard.isEmpty()) { + if (!_visible) { return; } - if (!_billboardTexture) { - QImage image = QImage::fromData(_billboard); - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); + + if (!_billboard.isEmpty()) { + if (!_billboardTexture) { + QImage image = QImage::fromData(_billboard); + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } + _size = image.size(); + if (_fromImage.x() == -1) { + _fromImage.setRect(0, 0, _size.width(), _size.height()); + } + _billboardTexture.reset(new Texture()); + glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _size.width(), _size.height(), 0, + GL_BGRA, GL_UNSIGNED_BYTE, image.constBits()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + } else { + glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); } - _size = image.size(); - _billboardTexture.reset(new Texture()); - glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _size.width(), _size.height(), 0, - GL_BGRA, GL_UNSIGNED_BYTE, image.constBits()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - - } else { - glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); } glEnable(GL_ALPHA_TEST); @@ -58,21 +65,35 @@ void BillboardOverlay::render() { } glScalef(_scale, _scale, _scale); - float maxSize = glm::max(_size.width(), _size.height()); - float x = _size.width() / (2.0f * maxSize); - float y = -_size.height() / (2.0f * maxSize); - - glColor3f(1.0f, 1.0f, 1.0f); - glBegin(GL_QUADS); { - glTexCoord2f(0.0f, 0.0f); - glVertex2f(-x, -y); - glTexCoord2f(1.0f, 0.0f); - glVertex2f(x, -y); - glTexCoord2f(1.0f, 1.0f); - glVertex2f(x, y); - glTexCoord2f(0.0f, 1.0f); - glVertex2f(-x, y); - } glEnd(); + if (_billboardTexture) { + float maxSize = glm::max(_fromImage.width(), _fromImage.height()); + float x = _fromImage.width() / (2.0f * maxSize); + float y = -_fromImage.height() / (2.0f * maxSize); + + glColor3f(1.0f, 1.0f, 1.0f); + glBegin(GL_QUADS); { + glTexCoord2f((float)_fromImage.x() / (float)_size.width(), + (float)_fromImage.y() / (float)_size.height()); + glVertex2f(-x, -y); + glTexCoord2f(((float)_fromImage.x() + (float)_fromImage.width()) / (float)_size.width(), + (float)_fromImage.y() / (float)_size.height()); + glVertex2f(x, -y); + glTexCoord2f(((float)_fromImage.x() + (float)_fromImage.width()) / (float)_size.width(), + ((float)_fromImage.y() + (float)_fromImage.height()) / _size.height()); + glVertex2f(x, y); + glTexCoord2f((float)_fromImage.x() / (float)_size.width(), + ((float)_fromImage.y() + (float)_fromImage.height()) / (float)_size.height()); + glVertex2f(-x, y); + } glEnd(); + } else { + glColor4f(0.5f, 0.5f, 0.5f, 1.0f); + glBegin(GL_QUADS); { + glVertex2f(-1.0f, -1.0f); + glVertex2f(1.0f, -1.0f); + glVertex2f(1.0f, 1.0f); + glVertex2f(-1.0f, 1.0f); + } glEnd(); + } } glPopMatrix(); @@ -93,6 +114,33 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) { setBillboardURL(_url); } + QScriptValue subImageBounds = properties.property("subImage"); + if (subImageBounds.isValid()) { + QRect oldSubImageRect = _fromImage; + QRect subImageRect = _fromImage; + if (subImageBounds.property("x").isValid()) { + subImageRect.setX(subImageBounds.property("x").toVariant().toInt()); + } else { + subImageRect.setX(oldSubImageRect.x()); + } + if (subImageBounds.property("y").isValid()) { + subImageRect.setY(subImageBounds.property("y").toVariant().toInt()); + } else { + subImageRect.setY(oldSubImageRect.y()); + } + if (subImageBounds.property("width").isValid()) { + subImageRect.setWidth(subImageBounds.property("width").toVariant().toInt()); + } else { + subImageRect.setWidth(oldSubImageRect.width()); + } + if (subImageBounds.property("height").isValid()) { + subImageRect.setHeight(subImageBounds.property("height").toVariant().toInt()); + } else { + subImageRect.setHeight(oldSubImageRect.height()); + } + setClipFromSource(subImageRect); + } + QScriptValue scaleValue = properties.property("scale"); if (scaleValue.isValid()) { _scale = scaleValue.toVariant().toFloat(); diff --git a/interface/src/ui/overlays/BillboardOverlay.h b/interface/src/ui/overlays/BillboardOverlay.h index 0037d1a4f7..5efb5767c6 100644 --- a/interface/src/ui/overlays/BillboardOverlay.h +++ b/interface/src/ui/overlays/BillboardOverlay.h @@ -25,7 +25,8 @@ public: virtual void render(); virtual void setProperties(const QScriptValue& properties); - + void setClipFromSource(const QRect& bounds) { _fromImage = bounds; } + private slots: void replyFinished(); @@ -37,6 +38,8 @@ private: QSize _size; QScopedPointer _billboardTexture; + QRect _fromImage; // where from in the image to sample + glm::quat _rotation; float _scale; bool _isFacingAvatar; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index bc0cc720c2..57f098aee3 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -35,6 +35,10 @@ void ModelOverlay::update(float deltatime) { } void ModelOverlay::render() { + if (!_visible) { + return; + } + if (_model.isActive()) { if (_model.isRenderable()) { diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 8ec7cbace1..bc7096c471 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -22,7 +22,7 @@ Overlay::Overlay() : _parent(NULL), _alpha(DEFAULT_ALPHA), - _color(DEFAULT_BACKGROUND_COLOR), + _color(DEFAULT_OVERLAY_COLOR), _visible(true), _anchor(NO_ANCHOR) { diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 7667b3d3fd..f8d6400bf6 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -21,7 +21,7 @@ #include // for xColor -const xColor DEFAULT_BACKGROUND_COLOR = { 255, 255, 255 }; +const xColor DEFAULT_OVERLAY_COLOR = { 255, 255, 255 }; const float DEFAULT_ALPHA = 0.7f; class Overlay : public QObject { diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index 797d0be1a2..691179ec54 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -18,6 +18,7 @@ #include "ui/TextRenderer.h" TextOverlay::TextOverlay() : + _backgroundColor(DEFAULT_BACKGROUND_COLOR), _leftMargin(DEFAULT_MARGIN), _topMargin(DEFAULT_MARGIN), _fontSize(DEFAULT_FONTSIZE) @@ -33,7 +34,7 @@ void TextOverlay::render() { } const float MAX_COLOR = 255; - glColor4f(0 / MAX_COLOR, 0 / MAX_COLOR, 0 / MAX_COLOR, _alpha); + glColor4f(_backgroundColor.red / MAX_COLOR, _backgroundColor.green / MAX_COLOR, _backgroundColor.blue / MAX_COLOR, _alpha); glBegin(GL_QUADS); glVertex2f(_bounds.left(), _bounds.top()); @@ -82,6 +83,18 @@ void TextOverlay::setProperties(const QScriptValue& properties) { setText(text.toVariant().toString()); } + QScriptValue backgroundColor = properties.property("backgroundColor"); + if (backgroundColor.isValid()) { + QScriptValue red = backgroundColor.property("red"); + QScriptValue green = backgroundColor.property("green"); + QScriptValue blue = backgroundColor.property("blue"); + if (red.isValid() && green.isValid() && blue.isValid()) { + _backgroundColor.red = red.toVariant().toInt(); + _backgroundColor.green = green.toVariant().toInt(); + _backgroundColor.blue = blue.toVariant().toInt(); + } + } + if (properties.property("leftMargin").isValid()) { setLeftMargin(properties.property("leftMargin").toVariant().toInt()); } diff --git a/interface/src/ui/overlays/TextOverlay.h b/interface/src/ui/overlays/TextOverlay.h index c2aafb24e8..78a51179a0 100644 --- a/interface/src/ui/overlays/TextOverlay.h +++ b/interface/src/ui/overlays/TextOverlay.h @@ -27,6 +27,7 @@ #include "Overlay.h" #include "Overlay2D.h" +const xColor DEFAULT_BACKGROUND_COLOR = { 0, 0, 0 }; const int DEFAULT_MARGIN = 10; const int DEFAULT_FONTSIZE = 11; @@ -54,6 +55,7 @@ public: private: QString _text; + xColor _backgroundColor; int _leftMargin; int _topMargin; int _fontSize; diff --git a/interface/src/voxels/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp index 2b86a58d9b..6c19cfd4c2 100644 --- a/interface/src/voxels/VoxelSystem.cpp +++ b/interface/src/voxels/VoxelSystem.cpp @@ -21,6 +21,8 @@ #include #include +#include + #include "Application.h" #include "InterfaceConfig.h" #include "Menu.h" @@ -57,6 +59,8 @@ GLubyte identityIndicesRight[] = { 1, 2, 6, 1, 6, 5 }; GLubyte identityIndicesFront[] = { 0, 2, 1, 0, 3, 2 }; GLubyte identityIndicesBack[] = { 4, 5, 6, 4, 6, 7 }; +static glm::vec3 grayColor = glm::vec3(0.3f, 0.3f, 0.3f); + VoxelSystem::VoxelSystem(float treeScale, int maxVoxels, VoxelTree* tree) : NodeData(), _treeScale(treeScale), @@ -67,7 +71,10 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels, VoxelTree* tree) _inOcclusions(false), _showCulledSharedFaces(false), _usePrimitiveRenderer(false), - _renderer(0) + _renderer(0), + _drawHaze(false), + _farHazeDistance(300.0f), + _hazeColor(grayColor) { _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0; @@ -373,6 +380,7 @@ void VoxelSystem::cleanupVoxelMemory() { delete[] _readVoxelDirtyArray; _writeVoxelDirtyArray = _readVoxelDirtyArray = NULL; _readArraysLock.unlock(); + } } @@ -454,6 +462,7 @@ void VoxelSystem::initVoxelMemory() { _readVoxelShaderData = new VoxelShaderVBOData[_maxVoxels]; _memoryUsageRAM += (sizeof(VoxelShaderVBOData) * _maxVoxels); + } else { // Global Normals mode uses a technique of not including normals on any voxel vertices, and instead @@ -499,17 +508,7 @@ void VoxelSystem::initVoxelMemory() { _memoryUsageRAM += (sizeof(GLubyte) * vertexPointsPerVoxel * _maxVoxels); // create our simple fragment shader if we're the first system to init - if (!_perlinModulateProgram.isLinked()) { - _perlinModulateProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() - + "shaders/perlin_modulate.vert"); - _perlinModulateProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() - + "shaders/perlin_modulate.frag"); - _perlinModulateProgram.link(); - - _perlinModulateProgram.bind(); - _perlinModulateProgram.setUniformValue("permutationNormalTexture", 0); - _perlinModulateProgram.release(); - + if (!_shadowMapProgram.isLinked()) { _shadowMapProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/shadow_map.vert"); _shadowMapProgram.addShaderFromSourceFile(QGLShader::Fragment, @@ -531,13 +530,23 @@ void VoxelSystem::initVoxelMemory() { _shadowDistancesLocation = _cascadedShadowMapProgram.uniformLocation("shadowDistances"); _cascadedShadowMapProgram.release(); } + } _renderer = new PrimitiveRenderer(_maxVoxels); _initialized = true; - + _writeArraysLock.unlock(); _readArraysLock.unlock(); + + // fog for haze + if (_drawHaze) { + GLfloat fogColor[] = {_hazeColor.x, _hazeColor.y, _hazeColor.z, 1.0f}; + glFogi(GL_FOG_MODE, GL_LINEAR); + glFogfv(GL_FOG_COLOR, fogColor); + glFogf(GL_FOG_START, 0.0f); + glFogf(GL_FOG_END, _farHazeDistance); + } } int VoxelSystem::parseData(const QByteArray& packet) { @@ -1124,6 +1133,7 @@ int VoxelSystem::updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, boo node->setBufferIndex(nodeIndex); node->setVoxelSystem(this); } + // populate the array with points for the 8 vertices and RGB color for each added vertex updateArraysDetails(nodeIndex, startVertex, voxelScale, node->getColor()); } @@ -1141,11 +1151,13 @@ int VoxelSystem::updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, boo void VoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex, float voxelScale, const nodeColor& color) { - + if (_initialized && nodeIndex <= _maxVoxels) { _writeVoxelDirtyArray[nodeIndex] = true; - + if (_useVoxelShader) { + // write in position, scale, and color for the voxel + if (_writeVoxelShaderData) { VoxelShaderVBOData* writeVerticesAt = &_writeVoxelShaderData[nodeIndex]; writeVerticesAt->x = startVertex.x * TREE_SCALE; @@ -1167,6 +1179,7 @@ void VoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& } } } + } } @@ -1417,6 +1430,10 @@ void VoxelSystem::render() { } } else if (!_usePrimitiveRenderer) { + if (_drawHaze) { + glEnable(GL_FOG); + } + PerformanceWarning warn(showWarnings, "render().. TRIANGLES..."); { @@ -1488,6 +1505,10 @@ void VoxelSystem::render() { glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } + + if (_drawHaze) { + glDisable(GL_FOG); + } } else { applyScaleAndBindProgram(texture); @@ -1509,7 +1530,7 @@ void VoxelSystem::applyScaleAndBindProgram(bool texture) { glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getShadowDepthTextureID()); } else if (texture) { - _perlinModulateProgram.bind(); + bindPerlinModulateProgram(); glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPermutationNormalTextureID()); } @@ -2159,6 +2180,22 @@ unsigned long VoxelSystem::getVoxelMemoryUsageGPU() { return (_initialMemoryUsageGPU - currentFreeMemory); } +void VoxelSystem::bindPerlinModulateProgram() { + if (!_perlinModulateProgram.isLinked()) { + _perlinModulateProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/perlin_modulate.vert"); + _perlinModulateProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/perlin_modulate.frag"); + _perlinModulateProgram.link(); + + _perlinModulateProgram.bind(); + _perlinModulateProgram.setUniformValue("permutationNormalTexture", 0); + + } else { + _perlinModulateProgram.bind(); + } +} + // Swizzle value of bit pairs of the value of index unsigned short VoxelSystem::_sSwizzledOcclusionBits[64] = { 0x0000, // 00000000 diff --git a/interface/src/voxels/VoxelSystem.h b/interface/src/voxels/VoxelSystem.h index 71abc9ca93..9f61e0579c 100644 --- a/interface/src/voxels/VoxelSystem.h +++ b/interface/src/voxels/VoxelSystem.h @@ -236,6 +236,8 @@ private: static ProgramObject _cascadedShadowMapProgram; static int _shadowDistancesLocation; + static void bindPerlinModulateProgram(); + int _hookID; std::vector _freeIndexes; QMutex _freeIndexLock; @@ -271,7 +273,11 @@ private: static unsigned short _sSwizzledOcclusionBits[64]; ///< Swizzle value of bit pairs of the value of index static unsigned char _sOctantIndexToBitMask[8]; ///< Map octant index to partition mask static unsigned char _sOctantIndexToSharedBitMask[8][8]; ///< Map octant indices to shared partition mask - + + // haze + bool _drawHaze; + float _farHazeDistance; + glm::vec3 _hazeColor; }; #endif // hifi_VoxelSystem_h diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 047db70693..9f049fc5e8 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -20,6 +20,7 @@ #include #include "NodeData.h" +#include "SharedUtil.h" const int SAMPLE_RATE = 24000; @@ -29,7 +30,7 @@ const int NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL = 512; const int NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL = NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL / sizeof(int16_t); const unsigned int BUFFER_SEND_INTERVAL_USECS = floorf((NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL - / (float) SAMPLE_RATE) * 1000 * 1000); + / (float) SAMPLE_RATE) * USECS_PER_SECOND); const int MAX_SAMPLE_VALUE = std::numeric_limits::max(); const int MIN_SAMPLE_VALUE = std::numeric_limits::min(); @@ -65,6 +66,9 @@ public: void shiftReadPosition(unsigned int numSamples); int samplesAvailable() const; + int framesAvailable() const { return samplesAvailable() / _numFrameSamples; } + + int getNumFrameSamples() const { return _numFrameSamples; } bool isNotStarvedOrHasMinimumSamples(int numRequiredSamples) const; diff --git a/libraries/audio/src/AudioStreamStats.h b/libraries/audio/src/AudioStreamStats.h index 004d697fcf..f26a3847c7 100644 --- a/libraries/audio/src/AudioStreamStats.h +++ b/libraries/audio/src/AudioStreamStats.h @@ -13,34 +13,50 @@ #define hifi_AudioStreamStats_h #include "PositionalAudioRingBuffer.h" +#include "SequenceNumberStats.h" class AudioStreamStats { public: AudioStreamStats() : _streamType(PositionalAudioRingBuffer::Microphone), _streamIdentifier(), - _jitterBufferFrames(0), - _packetsReceived(0), - _packetsUnreasonable(0), - _packetsEarly(0), - _packetsLate(0), - _packetsLost(0), - _packetsRecovered(0), - _packetsDuplicate(0) + _timeGapMin(0), + _timeGapMax(0), + _timeGapAverage(0.0f), + _timeGapWindowMin(0), + _timeGapWindowMax(0), + _timeGapWindowAverage(0.0f), + _ringBufferFramesAvailable(0), + _ringBufferCurrentJitterBufferFrames(0), + _ringBufferDesiredJitterBufferFrames(0), + _ringBufferStarveCount(0), + _ringBufferConsecutiveNotMixedCount(0), + _ringBufferOverflowCount(0), + _ringBufferSilentFramesDropped(0), + _packetStreamStats(), + _packetStreamWindowStats() {} PositionalAudioRingBuffer::Type _streamType; QUuid _streamIdentifier; - quint16 _jitterBufferFrames; + quint64 _timeGapMin; + quint64 _timeGapMax; + float _timeGapAverage; + quint64 _timeGapWindowMin; + quint64 _timeGapWindowMax; + float _timeGapWindowAverage; - quint32 _packetsReceived; - quint32 _packetsUnreasonable; - quint32 _packetsEarly; - quint32 _packetsLate; - quint32 _packetsLost; - quint32 _packetsRecovered; - quint32 _packetsDuplicate; + quint32 _ringBufferFramesAvailable; + quint16 _ringBufferCurrentJitterBufferFrames; + quint16 _ringBufferDesiredJitterBufferFrames; + quint32 _ringBufferStarveCount; + quint32 _ringBufferConsecutiveNotMixedCount; + quint32 _ringBufferOverflowCount; + quint32 _ringBufferSilentFramesDropped; + + PacketStreamStats _packetStreamStats; + PacketStreamStats _packetStreamWindowStats; }; #endif // hifi_AudioStreamStats_h diff --git a/libraries/audio/src/InjectedAudioRingBuffer.cpp b/libraries/audio/src/InjectedAudioRingBuffer.cpp index c84fe173c9..bdad7b2a7a 100644 --- a/libraries/audio/src/InjectedAudioRingBuffer.cpp +++ b/libraries/audio/src/InjectedAudioRingBuffer.cpp @@ -31,7 +31,7 @@ InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier, const uchar MAX_INJECTOR_VOLUME = 255; int InjectedAudioRingBuffer::parseData(const QByteArray& packet) { - _interframeTimeGapStats.frameReceived(); + timeGapStatsFrameReceived(); updateDesiredJitterBufferFrames(); // setup a data stream to read from this packet diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 0fe75f1239..411b02400d 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -21,70 +21,6 @@ #include "PositionalAudioRingBuffer.h" #include "SharedUtil.h" -InterframeTimeGapStats::InterframeTimeGapStats() - : _lastFrameReceivedTime(0), - _numSamplesInCurrentInterval(0), - _currentIntervalMaxGap(0), - _newestIntervalMaxGapAt(0), - _windowMaxGap(0), - _newWindowMaxGapAvailable(false) -{ - memset(_intervalMaxGaps, 0, TIME_GAP_NUM_INTERVALS_IN_WINDOW * sizeof(quint64)); -} - -void InterframeTimeGapStats::frameReceived() { - quint64 now = usecTimestampNow(); - - // make sure this isn't the first time frameReceived() is called so can actually calculate a gap. - if (_lastFrameReceivedTime != 0) { - quint64 gap = now - _lastFrameReceivedTime; - - // update the current interval max - if (gap > _currentIntervalMaxGap) { - _currentIntervalMaxGap = gap; - - // keep the window max gap at least as large as the current interval max - // this allows the window max gap to respond immediately to a sudden spike in gap times - // also, this prevents the window max gap from staying at 0 until the first interval of samples filled up - if (_currentIntervalMaxGap > _windowMaxGap) { - _windowMaxGap = _currentIntervalMaxGap; - _newWindowMaxGapAvailable = true; - } - } - _numSamplesInCurrentInterval++; - - // if the current interval of samples is now full, record it in our interval maxes - if (_numSamplesInCurrentInterval == TIME_GAP_NUM_SAMPLES_IN_INTERVAL) { - - // find location to insert this interval's max (increment index cyclically) - _newestIntervalMaxGapAt = _newestIntervalMaxGapAt == TIME_GAP_NUM_INTERVALS_IN_WINDOW - 1 ? 0 : _newestIntervalMaxGapAt + 1; - - // record the current interval's max gap as the newest - _intervalMaxGaps[_newestIntervalMaxGapAt] = _currentIntervalMaxGap; - - // update the window max gap, which is the max out of all the past intervals' max gaps - _windowMaxGap = 0; - for (int i = 0; i < TIME_GAP_NUM_INTERVALS_IN_WINDOW; i++) { - if (_intervalMaxGaps[i] > _windowMaxGap) { - _windowMaxGap = _intervalMaxGaps[i]; - } - } - _newWindowMaxGapAvailable = true; - - // reset the current interval - _numSamplesInCurrentInterval = 0; - _currentIntervalMaxGap = 0; - } - } - _lastFrameReceivedTime = now; -} - -quint64 InterframeTimeGapStats::getWindowMaxGap() { - _newWindowMaxGapAvailable = false; - return _windowMaxGap; -} - - PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo, bool dynamicJitterBuffers) : AudioRingBuffer(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL, @@ -97,9 +33,15 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer:: _shouldOutputStarveDebug(true), _isStereo(isStereo), _listenerUnattenuatedZone(NULL), + _lastFrameReceivedTime(0), + _interframeTimeGapStatsForJitterCalc(TIME_GAPS_FOR_JITTER_CALC_INTERVAL_SAMPLES, TIME_GAPS_FOR_JITTER_CALC_WINDOW_INTERVALS), + _interframeTimeGapStatsForStatsPacket(TIME_GAPS_FOR_STATS_PACKET_INTERVAL_SAMPLES, TIME_GAPS_FOR_STATS_PACKET_WINDOW_INTERVALS), _desiredJitterBufferFrames(1), _currentJitterBufferFrames(-1), - _dynamicJitterBuffers(dynamicJitterBuffers) + _dynamicJitterBuffers(dynamicJitterBuffers), + _consecutiveNotMixedCount(0), + _starveCount(0), + _silentFramesDropped(0) { } @@ -129,7 +71,7 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) { numSilentSamples = getSamplesPerFrame(); if (numSilentSamples > 0) { - if (_currentJitterBufferFrames > _desiredJitterBufferFrames) { + if (_dynamicJitterBuffers && _currentJitterBufferFrames > _desiredJitterBufferFrames) { // our current jitter buffer size exceeds its desired value, so ignore some silent // frames to get that size as close to desired as possible int samplesPerFrame = getSamplesPerFrame(); @@ -142,9 +84,12 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) { addSilentFrame(numSilentFramesToAdd * samplesPerFrame); _currentJitterBufferFrames = _desiredJitterBufferFrames; + _silentFramesDropped += numFramesToDropDesired; } else { // we need to drop all frames to get the jitter buffer close as possible to its desired length _currentJitterBufferFrames -= numSilentFrames; + + _silentFramesDropped += numSilentFrames; } } else { addSilentFrame(numSilentSamples); @@ -206,15 +151,17 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() { if (!isNotStarvedOrHasMinimumSamples(samplesPerFrame + desiredJitterBufferSamples)) { // if the buffer was starved, allow it to accrue at least the desired number of // jitter buffer frames before we start taking frames from it for mixing - + if (_shouldOutputStarveDebug) { _shouldOutputStarveDebug = false; } + _consecutiveNotMixedCount++; return false; } else if (samplesAvailable() < samplesPerFrame) { // if the buffer doesn't have a full frame of samples to take for mixing, it is starved _isStarved = true; + _starveCount++; // set to -1 to indicate the jitter buffer is starved _currentJitterBufferFrames = -1; @@ -222,6 +169,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() { // reset our _shouldOutputStarveDebug to true so the next is printed _shouldOutputStarveDebug = true; + _consecutiveNotMixedCount = 1; return false; } @@ -243,21 +191,31 @@ int PositionalAudioRingBuffer::getCalculatedDesiredJitterBufferFrames() const { int calculatedDesiredJitterBufferFrames = 1; const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE; - calculatedDesiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.peekWindowMaxGap() / USECS_PER_FRAME); + calculatedDesiredJitterBufferFrames = ceilf((float)_interframeTimeGapStatsForJitterCalc.getWindowMax() / USECS_PER_FRAME); if (calculatedDesiredJitterBufferFrames < 1) { calculatedDesiredJitterBufferFrames = 1; } return calculatedDesiredJitterBufferFrames; } +void PositionalAudioRingBuffer::timeGapStatsFrameReceived() { + quint64 now = usecTimestampNow(); + if (_lastFrameReceivedTime != 0) { + quint64 gap = now - _lastFrameReceivedTime; + _interframeTimeGapStatsForJitterCalc.update(gap); + _interframeTimeGapStatsForStatsPacket.update(gap); + } + _lastFrameReceivedTime = now; +} + void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() { - if (_interframeTimeGapStats.hasNewWindowMaxGapAvailable()) { + if (_interframeTimeGapStatsForJitterCalc.getNewStatsAvailableFlag()) { if (!_dynamicJitterBuffers) { _desiredJitterBufferFrames = 1; // HACK to see if this fixes the audio silence } else { const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE; - _desiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.getWindowMaxGap() / USECS_PER_FRAME); + _desiredJitterBufferFrames = ceilf((float)_interframeTimeGapStatsForJitterCalc.getWindowMax() / USECS_PER_FRAME); if (_desiredJitterBufferFrames < 1) { _desiredJitterBufferFrames = 1; } @@ -266,5 +224,6 @@ void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() { _desiredJitterBufferFrames = maxDesired; } } + _interframeTimeGapStatsForJitterCalc.clearNewStatsAvailableFlag(); } } diff --git a/libraries/audio/src/PositionalAudioRingBuffer.h b/libraries/audio/src/PositionalAudioRingBuffer.h index 0322afb47b..3a9e7ed124 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.h +++ b/libraries/audio/src/PositionalAudioRingBuffer.h @@ -17,31 +17,17 @@ #include #include "AudioRingBuffer.h" +#include "MovingMinMaxAvg.h" -// this means that every 500 samples, the max for the past 10*500 samples will be calculated -const int TIME_GAP_NUM_SAMPLES_IN_INTERVAL = 500; -const int TIME_GAP_NUM_INTERVALS_IN_WINDOW = 10; +// the time gaps stats for _desiredJitterBufferFrames calculation +// will recalculate the max for the past 5000 samples every 500 samples +const int TIME_GAPS_FOR_JITTER_CALC_INTERVAL_SAMPLES = 500; +const int TIME_GAPS_FOR_JITTER_CALC_WINDOW_INTERVALS = 10; -// class used to track time between incoming frames for the purpose of varying the jitter buffer length -class InterframeTimeGapStats { -public: - InterframeTimeGapStats(); - - void frameReceived(); - bool hasNewWindowMaxGapAvailable() const { return _newWindowMaxGapAvailable; } - quint64 peekWindowMaxGap() const { return _windowMaxGap; } - quint64 getWindowMaxGap(); - -private: - quint64 _lastFrameReceivedTime; - - int _numSamplesInCurrentInterval; - quint64 _currentIntervalMaxGap; - quint64 _intervalMaxGaps[TIME_GAP_NUM_INTERVALS_IN_WINDOW]; - int _newestIntervalMaxGapAt; - quint64 _windowMaxGap; - bool _newWindowMaxGapAvailable; -}; +// the time gap stats for constructing AudioStreamStats will +// recalculate min/max/avg every ~1 second for the past ~30 seconds of time gap data +const int TIME_GAPS_FOR_STATS_PACKET_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS; +const int TIME_GAPS_FOR_STATS_PACKET_WINDOW_INTERVALS = 30; const int AUDIOMIXER_INBOUND_RING_BUFFER_FRAME_CAPACITY = 100; @@ -79,15 +65,22 @@ public: int getSamplesPerFrame() const { return _isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; } + const MovingMinMaxAvg& getInterframeTimeGapStatsForStatsPacket() const { return _interframeTimeGapStatsForStatsPacket; } + int getCalculatedDesiredJitterBufferFrames() const; /// returns what we would calculate our desired as if asked int getDesiredJitterBufferFrames() const { return _desiredJitterBufferFrames; } int getCurrentJitterBufferFrames() const { return _currentJitterBufferFrames; } + int getConsecutiveNotMixedCount() const { return _consecutiveNotMixedCount; } + int getStarveCount() const { return _starveCount; } + int getSilentFramesDropped() const { return _silentFramesDropped; } + protected: // disallow copying of PositionalAudioRingBuffer objects PositionalAudioRingBuffer(const PositionalAudioRingBuffer&); PositionalAudioRingBuffer& operator= (const PositionalAudioRingBuffer&); + void timeGapStatsFrameReceived(); void updateDesiredJitterBufferFrames(); PositionalAudioRingBuffer::Type _type; @@ -101,15 +94,18 @@ protected: float _nextOutputTrailingLoudness; AABox* _listenerUnattenuatedZone; - InterframeTimeGapStats _interframeTimeGapStats; + quint64 _lastFrameReceivedTime; + MovingMinMaxAvg _interframeTimeGapStatsForJitterCalc; + MovingMinMaxAvg _interframeTimeGapStatsForStatsPacket; + int _desiredJitterBufferFrames; int _currentJitterBufferFrames; bool _dynamicJitterBuffers; // extra stats + int _consecutiveNotMixedCount; int _starveCount; int _silentFramesDropped; - }; #endif // hifi_PositionalAudioRingBuffer_h diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 3f3e71c5e8..6cfa4ba488 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -490,11 +490,11 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { for (int i = 0; i < numJoints; i++) { JointData& data = _jointData[i]; if (data.valid) { + _hasNewJointRotations = true; sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation); } } } // numJoints * 8 bytes - _hasNewJointRotations = true; return sourceBuffer - startPosition; } diff --git a/libraries/avatars/src/HandData.cpp b/libraries/avatars/src/HandData.cpp index e6ef10ed3e..69f9669973 100644 --- a/libraries/avatars/src/HandData.cpp +++ b/libraries/avatars/src/HandData.cpp @@ -108,12 +108,6 @@ glm::quat HandData::getBaseOrientation() const { glm::vec3 HandData::getBasePosition() const { return _owningAvatarData->getPosition(); } - -glm::vec3 PalmData::getFingerTipPosition() const { - glm::vec3 fingerOffset(0.0f, 0.0f, 0.3f); - glm::vec3 palmOffset(0.0f, -0.08f, 0.0f); - return getPosition() + _owningHandData->localToWorldDirection(_rawRotation * (fingerOffset + palmOffset)); -} glm::vec3 PalmData::getFingerDirection() const { const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 0.0f, 1.0f); diff --git a/libraries/avatars/src/HandData.h b/libraries/avatars/src/HandData.h index 404d00b0f3..770a44197d 100755 --- a/libraries/avatars/src/HandData.h +++ b/libraries/avatars/src/HandData.h @@ -140,7 +140,6 @@ public: void getBallHoldPosition(glm::vec3& position) const; // return world-frame: - glm::vec3 getFingerTipPosition() const; glm::vec3 getFingerDirection() const; glm::vec3 getNormal() const; diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 33ce298859..64b2646261 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -9,7 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include +#include #include "AttributeRegistry.h" #include "MetavoxelData.h" @@ -69,6 +71,7 @@ AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute if (!attribute) { return attribute; } + QWriteLocker locker(&_attributesLock); AttributePointer& pointer = _attributes[attribute->getName()]; if (!pointer) { pointer = attribute; @@ -77,9 +80,15 @@ AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute } void AttributeRegistry::deregisterAttribute(const QString& name) { + QWriteLocker locker(&_attributesLock); _attributes.remove(name); } +AttributePointer AttributeRegistry::getAttribute(const QString& name) { + QReadLocker locker(&_attributesLock); + return _attributes.value(name); +} + QScriptValue AttributeRegistry::getAttribute(QScriptContext* context, QScriptEngine* engine) { return engine->newQObject(getInstance()->getAttribute(context->argument(0).toString()).data(), QScriptEngine::QtOwnership, QScriptEngine::PreferExistingWrapperObject); @@ -559,6 +568,10 @@ void SpannerSetAttribute::readMetavoxelRoot(MetavoxelData& data, MetavoxelStream } data.insert(state.attribute, object); } + // even if the root is empty, it should still exist + if (!data.getRoot(state.attribute)) { + data.createRoot(state.attribute); + } } void SpannerSetAttribute::writeMetavoxelRoot(const MetavoxelNode& root, MetavoxelStreamState& state) { @@ -577,6 +590,10 @@ void SpannerSetAttribute::readMetavoxelDelta(MetavoxelData& data, } data.toggle(state.attribute, object); } + // even if the root is empty, it should still exist + if (!data.getRoot(state.attribute)) { + data.createRoot(state.attribute); + } } void SpannerSetAttribute::writeMetavoxelDelta(const MetavoxelNode& root, diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index 7dc2e110b8..23e3c1fa97 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -61,11 +62,14 @@ public: void deregisterAttribute(const QString& name); /// Retrieves an attribute by name. - AttributePointer getAttribute(const QString& name) const { return _attributes.value(name); } + AttributePointer getAttribute(const QString& name); /// Returns a reference to the attribute hash. const QHash& getAttributes() const { return _attributes; } + /// Returns a reference to the attributes lock. + QReadWriteLock& getAttributesLock() { return _attributesLock; } + /// Returns a reference to the standard SharedObjectPointer "guide" attribute. const AttributePointer& getGuideAttribute() const { return _guideAttribute; } @@ -92,6 +96,8 @@ private: static QScriptValue getAttribute(QScriptContext* context, QScriptEngine* engine); QHash _attributes; + QReadWriteLock _attributesLock; + AttributePointer _guideAttribute; AttributePointer _spannersAttribute; AttributePointer _colorAttribute; diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index d18903f923..0d4f8a52b4 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -87,6 +87,12 @@ IDStreamer& IDStreamer::operator>>(int& value) { return *this; } +void Bitstream::preThreadingInit() { + getObjectStreamers(); + getEnumStreamers(); + getEnumStreamersByName(); +} + int Bitstream::registerMetaObject(const char* className, const QMetaObject* metaObject) { getMetaObjects().insert(className, metaObject); @@ -274,6 +280,26 @@ void Bitstream::persistAndResetReadMappings() { persistReadMappings(getAndResetReadMappings()); } +void Bitstream::copyPersistentMappings(const Bitstream& other) { + _objectStreamerStreamer.copyPersistentMappings(other._objectStreamerStreamer); + _typeStreamerStreamer.copyPersistentMappings(other._typeStreamerStreamer); + _attributeStreamer.copyPersistentMappings(other._attributeStreamer); + _scriptStringStreamer.copyPersistentMappings(other._scriptStringStreamer); + _sharedObjectStreamer.copyPersistentMappings(other._sharedObjectStreamer); + _sharedObjectReferences = other._sharedObjectReferences; + _weakSharedObjectHash = other._weakSharedObjectHash; +} + +void Bitstream::clearPersistentMappings() { + _objectStreamerStreamer.clearPersistentMappings(); + _typeStreamerStreamer.clearPersistentMappings(); + _attributeStreamer.clearPersistentMappings(); + _scriptStringStreamer.clearPersistentMappings(); + _sharedObjectStreamer.clearPersistentMappings(); + _sharedObjectReferences.clear(); + _weakSharedObjectHash.clear(); +} + void Bitstream::clearSharedObject(int id) { SharedObjectPointer object = _sharedObjectStreamer.takePersistentValue(id); if (object) { @@ -1122,7 +1148,7 @@ Bitstream& Bitstream::operator>(ObjectStreamerPointer& streamer) { } if (_metadataType == NO_METADATA) { if (!metaObject) { - qWarning() << "Unknown class name:" << className; + throw BitstreamException(QString("Unknown class name: ") + className); } return *this; } @@ -1232,7 +1258,7 @@ Bitstream& Bitstream::operator>(TypeStreamerPointer& streamer) { } if (_metadataType == NO_METADATA) { if (!baseStreamer) { - qWarning() << "Unknown type name:" << typeName; + throw BitstreamException(QString("Unknown type name: ") + typeName); } return *this; } @@ -1240,7 +1266,7 @@ Bitstream& Bitstream::operator>(TypeStreamerPointer& streamer) { *this >> category; if (category == TypeStreamer::SIMPLE_CATEGORY) { if (!streamer) { - qWarning() << "Unknown type name:" << typeName; + throw BitstreamException(QString("Unknown type name: ") + typeName); } return *this; } @@ -1441,7 +1467,7 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) { _objectStreamerStreamer >> objectStreamer; if (delta) { if (!reference) { - qWarning() << "Delta without reference" << id << originID; + throw BitstreamException(QString("Delta without reference [id=%1, originID=%2]").arg(id).arg(originID)); } objectStreamer->readRawDelta(*this, reference.data(), pointer.data()); } else { @@ -1451,7 +1477,7 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) { QObject* rawObject; if (delta) { if (!reference) { - qWarning() << "Delta without reference" << id << originID; + throw BitstreamException(QString("Delta without reference [id=%1, originID=%2]").arg(id).arg(originID)); } readRawDelta(rawObject, (const QObject*)reference.data()); } else { @@ -1682,6 +1708,10 @@ const TypeStreamer* Bitstream::createInvalidTypeStreamer() { return streamer; } +BitstreamException::BitstreamException(const QString& description) : + _description(description) { +} + QJsonValue JSONWriter::getData(bool value) { return value; } diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index e32f93dbe2..dacfeb2ee9 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -102,6 +102,9 @@ public: V takePersistentValue(int id) { V value = _persistentValues.take(id); _valueIDs.remove(value); return value; } + void copyPersistentMappings(const RepeatedValueStreamer& other); + void clearPersistentMappings(); + RepeatedValueStreamer& operator<<(K value); RepeatedValueStreamer& operator>>(V& value); @@ -199,6 +202,29 @@ template inline RepeatedValueStreamer& return *this; } +template inline void RepeatedValueStreamer::copyPersistentMappings( + const RepeatedValueStreamer& other) { + _lastPersistentID = other._lastPersistentID; + _idStreamer.setBitsFromValue(_lastPersistentID); + _persistentIDs = other._persistentIDs; + _transientOffsets.clear(); + _lastTransientOffset = 0; + _persistentValues = other._persistentValues; + _transientValues.clear(); + _valueIDs = other._valueIDs; +} + +template inline void RepeatedValueStreamer::clearPersistentMappings() { + _lastPersistentID = 0; + _idStreamer.setBitsFromValue(_lastPersistentID); + _persistentIDs.clear(); + _transientOffsets.clear(); + _lastTransientOffset = 0; + _persistentValues.clear(); + _transientValues.clear(); + _valueIDs.clear(); +} + /// A stream for bit-aligned data. Through a combination of code generation, reflection, macros, and templates, provides a /// serialization mechanism that may be used for both networking and persistent storage. For unreliable networking, the /// class provides a mapping system that resends mappings for ids until they are acknowledged (and thus persisted). For @@ -264,6 +290,11 @@ public: QHash sharedObjectValues; }; + /// Performs all of the various lazily initializations (of object streamers, etc.) If multiple threads need to use + /// Bitstream instances, call this beforehand to prevent errors from occurring when multiple threads attempt lazy + /// initialization simultaneously. + static void preThreadingInit(); + /// Registers a metaobject under its name so that instances of it can be streamed. Consider using the REGISTER_META_OBJECT /// at the top level of the source file associated with the class rather than calling this function directly. /// \return zero; the function only returns a value so that it can be used in static initialization @@ -303,6 +334,9 @@ public: Bitstream(QDataStream& underlying, MetadataType metadataType = NO_METADATA, GenericsMode = NO_GENERICS, QObject* parent = NULL); + /// Returns a reference to the underlying data stream. + QDataStream& getUnderlying() { return _underlying; } + /// Substitutes the supplied metaobject for the given class name's default mapping. This is mostly useful for testing the /// process of mapping between different types, but may in the future be used for permanently renaming classes. void addMetaObjectSubstitution(const QByteArray& className, const QMetaObject* metaObject); @@ -347,6 +381,12 @@ public: /// Immediately persists and resets the read mappings. void persistAndResetReadMappings(); + /// Copies the persistent mappings from the specified other stream. + void copyPersistentMappings(const Bitstream& other); + + /// Clears the persistent mappings for this stream. + void clearPersistentMappings(); + /// Returns a reference to the weak hash storing shared objects for this stream. const WeakSharedObjectHash& getWeakSharedObjectHash() const { return _weakSharedObjectHash; } @@ -823,6 +863,19 @@ template inline Bitstream& Bitstream::operator>>(QHash& return *this; } +/// Thrown for unrecoverable errors. +class BitstreamException { +public: + + BitstreamException(const QString& description); + + const QString& getDescription() const { return _description; } + +private: + + QString _description; +}; + /// Provides a means of writing Bitstream-able data to JSON rather than the usual binary format in a manner that allows it to /// be manipulated and re-read, converted to binary, etc. To use, create a JSONWriter, stream values in using the << operator, /// and call getDocument to obtain the JSON data. diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index eb02497321..c757e131bb 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -79,7 +79,25 @@ ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) { return channel; } -int DatagramSequencer::startPacketGroup(int desiredPackets) { +void DatagramSequencer::addReliableChannelStats(int& sendProgress, int& sendTotal, + int& receiveProgress, int& receiveTotal) const { + foreach (ReliableChannel* channel, _reliableOutputChannels) { + int sent, total; + if (channel->getMessageSendProgress(sent, total)) { + sendProgress += sent; + sendTotal += total; + } + } + foreach (ReliableChannel* channel, _reliableInputChannels) { + int received, total; + if (channel->getMessageReceiveProgress(received, total)) { + receiveProgress += received; + receiveTotal += total; + } + } +} + +int DatagramSequencer::notePacketGroup(int desiredPackets) { // figure out how much data we have enqueued and increase the number of packets desired int totalAvailable = 0; foreach (ReliableChannel* channel, _reliableOutputChannels) { @@ -113,17 +131,16 @@ Bitstream& DatagramSequencer::startPacket() { _outgoingPacketStream << (quint32)record.packetNumber; } - // write the high-priority messages - _outgoingPacketStream << (quint32)_highPriorityMessages.size(); - foreach (const HighPriorityMessage& message, _highPriorityMessages) { - _outputStream << message.data; - } - // return the stream, allowing the caller to write the rest return _outputStream; } void DatagramSequencer::endPacket() { + // write the high-priority messages + _outputStream << _highPriorityMessages.size(); + foreach (const HighPriorityMessage& message, _highPriorityMessages) { + _outputStream << message.data; + } _outputStream.flush(); // if we have space remaining, send some data from our reliable channels @@ -222,22 +239,22 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) { _sendRecords.erase(_sendRecords.begin(), it + 1); } + // alert external parties so that they can read the middle + emit readyToRead(_inputStream); + // read and dispatch the high-priority messages - quint32 highPriorityMessageCount; - _incomingPacketStream >> highPriorityMessageCount; + int highPriorityMessageCount; + _inputStream >> highPriorityMessageCount; int newHighPriorityMessages = highPriorityMessageCount - _receivedHighPriorityMessages; - for (quint32 i = 0; i < highPriorityMessageCount; i++) { + for (int i = 0; i < highPriorityMessageCount; i++) { QVariant data; _inputStream >> data; - if ((int)i >= _receivedHighPriorityMessages) { + if (i >= _receivedHighPriorityMessages) { emit receivedHighPriorityMessage(data); } } _receivedHighPriorityMessages = highPriorityMessageCount; - // alert external parties so that they can read the middle - emit readyToRead(_inputStream); - // read the reliable data, if any quint32 reliableChannels; _incomingPacketStream >> reliableChannels; @@ -253,6 +270,8 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) { // record the receipt ReceiveRecord record = { _incomingPacketNumber, _inputStream.getAndResetReadMappings(), newHighPriorityMessages }; _receiveRecords.append(record); + + emit receiveRecorded(); } void DatagramSequencer::sendClearSharedObjectMessage(int id) { @@ -274,6 +293,11 @@ void DatagramSequencer::handleHighPriorityMessage(const QVariant& data) { } } +void DatagramSequencer::clearReliableChannel(QObject* object) { + ReliableChannel* channel = static_cast(object); + (channel->isOutput() ? _reliableOutputChannels : _reliableInputChannels).remove(channel->getIndex()); +} + void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) { // stop acknowledging the recorded packets while (!_receiveRecords.isEmpty() && _receiveRecords.first().packetNumber <= record.lastReceivedPacketNumber) { @@ -295,7 +319,10 @@ void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) { // acknowledge the received spans foreach (const ChannelSpan& span, record.spans) { - getReliableOutputChannel(span.channel)->spanAcknowledged(span); + ReliableChannel* channel = _reliableOutputChannels.value(span.channel); + if (channel) { + channel->spanAcknowledged(span); + } } // increase the packet rate with every ack until we pass the slow start threshold; then, every round trip @@ -310,7 +337,10 @@ void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) { void DatagramSequencer::sendRecordLost(const SendRecord& record) { // notify the channels of their lost spans foreach (const ChannelSpan& span, record.spans) { - getReliableOutputChannel(span.channel)->spanLost(record.packetNumber, _outgoingPacketNumber + 1); + ReliableChannel* channel = _reliableOutputChannels.value(span.channel); + if (channel) { + channel->spanLost(record.packetNumber, _outgoingPacketNumber + 1); + } } // halve the rate and remember as threshold @@ -364,6 +394,8 @@ void DatagramSequencer::sendPacket(const QByteArray& packet, const QVector= _messageReceivedOffset) { + return false; + } + sent = qMax(0, _messageSize - (_messageReceivedOffset - _offset)); + total = _messageSize; + return true; +} + +bool ReliableChannel::getMessageReceiveProgress(int& received, int& total) const { + if (!_messagesEnabled || _buffer.bytesAvailable() < (int)sizeof(quint32)) { + return false; + } + quint32 length; + _buffer.readBytes(_buffer.pos(), sizeof(quint32), (char*)&length); + total = length; + received = _buffer.bytesAvailable(); + return true; } void ReliableChannel::sendClearSharedObjectMessage(int id) { @@ -675,7 +737,7 @@ void ReliableChannel::sendClearSharedObjectMessage(int id) { sendMessage(QVariant::fromValue(message)); } -void ReliableChannel::handleMessage(const QVariant& message) { +void ReliableChannel::handleMessage(const QVariant& message, Bitstream& in) { if (message.userType() == ClearSharedObjectMessage::Type) { _bitstream.clearSharedObject(message.value().id); @@ -688,19 +750,23 @@ void ReliableChannel::handleMessage(const QVariant& message) { ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool output) : QObject(sequencer), _index(index), + _output(output), _dataStream(&_buffer), _bitstream(_dataStream), _priority(1.0f), _offset(0), _writePosition(0), _writePositionResetPacketNumber(0), - _messagesEnabled(true) { + _messagesEnabled(true), + _messageReceivedOffset(0) { _buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly); _dataStream.setByteOrder(QDataStream::LittleEndian); connect(&_bitstream, SIGNAL(sharedObjectCleared(int)), SLOT(sendClearSharedObjectMessage(int))); - connect(this, SIGNAL(receivedMessage(const QVariant&)), SLOT(handleMessage(const QVariant&))); + connect(this, SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&))); + + sequencer->connect(this, SIGNAL(destroyed(QObject*)), SLOT(clearReliableChannel(QObject*))); } void ReliableChannel::writeData(QDataStream& out, int bytes, QVector& spans) { @@ -843,9 +909,9 @@ void ReliableChannel::readData(QDataStream& in) { _dataStream.skipRawData(sizeof(quint32)); QVariant message; _bitstream >> message; + emit receivedMessage(message, _bitstream); _bitstream.reset(); _bitstream.persistAndResetReadMappings(); - emit receivedMessage(message); continue; } } diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index aa8b6907ff..4a01679c68 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -78,6 +78,15 @@ public: /// Returns the packet number of the last packet received (or the packet currently being assembled). int getIncomingPacketNumber() const { return _incomingPacketNumber; } + /// Returns a reference to the stream used to read packets. + Bitstream& getInputStream() { return _inputStream; } + + /// Returns a reference to the stream used to write packets. + Bitstream& getOutputStream() { return _outputStream; } + + /// Returns a reference to the outgoing packet data. + const QByteArray& getOutgoingPacketData() const { return _outgoingPacketData; } + /// Returns the packet number of the sent packet at the specified index. int getSentPacketNumber(int index) const { return _sendRecords.at(index).packetNumber; } @@ -99,10 +108,13 @@ public: /// Returns the intput channel at the specified index, creating it if necessary. ReliableChannel* getReliableInputChannel(int index = 0); - /// Starts a packet group. + /// Adds stats for all reliable channels to the referenced variables. + void addReliableChannelStats(int& sendProgress, int& sendTotal, int& receiveProgress, int& receiveTotal) const; + + /// Notes that we're sending a group of packets. /// \param desiredPackets the number of packets we'd like to write in the group /// \return the number of packets to write in the group - int startPacketGroup(int desiredPackets = 1); + int notePacketGroup(int desiredPackets = 1); /// Starts a new packet for transmission. /// \return a reference to the Bitstream to use for writing to the packet @@ -126,9 +138,15 @@ signals: /// Emitted when a packet is available to read. void readyToRead(Bitstream& input); - /// Emitted when we've received a high-priority message + /// Emitted when we've received a high-priority message. void receivedHighPriorityMessage(const QVariant& data); + /// Emitted when we've recorded the transmission of a packet. + void sendRecorded(); + + /// Emitted when we've recorded the receipt of a packet (that is, at the end of packet processing). + void receiveRecorded(); + /// Emitted when a sent packet has been acknowledged by the remote side. /// \param index the index of the packet in our list of send records void sendAcknowledged(int index); @@ -141,6 +159,7 @@ private slots: void sendClearSharedObjectMessage(int id); void handleHighPriorityMessage(const QVariant& data); + void clearReliableChannel(QObject* object); private: @@ -319,6 +338,9 @@ public: /// Returns the channel's index in the sequencer's channel map. int getIndex() const { return _index; } + /// Checks whether this is an output channel. + bool isOutput() const { return _output; } + /// Returns a reference to the buffer used to write/read data to/from this channel. CircularBuffer& getBuffer() { return _buffer; } @@ -336,22 +358,44 @@ public: /// Returns the number of bytes available to read from this channel. int getBytesAvailable() const; + /// Returns the offset, which represents the total number of bytes acknowledged + /// (on the write end) or received completely (on the read end). + int getOffset() const { return _offset; } + + /// Returns the total number of bytes written to this channel. + int getBytesWritten() const { return _offset + _buffer.pos(); } + /// Sets whether we expect to write/read framed messages. void setMessagesEnabled(bool enabled) { _messagesEnabled = enabled; } bool getMessagesEnabled() const { return _messagesEnabled; } - /// Sends a framed message on this channel. + /// Starts a framed message on this channel. + void startMessage(); + + /// Ends a framed message on this channel. + void endMessage(); + + /// Sends a framed message on this channel (convenience function that calls startMessage, + /// writes the message to the bitstream, then calls endMessage). void sendMessage(const QVariant& message); + /// Determines the number of bytes uploaded towards the currently pending message. + /// \return true if there is a message pending, in which case the sent and total arguments will be set + bool getMessageSendProgress(int& sent, int& total) const; + + /// Determines the number of bytes downloaded towards the currently pending message. + /// \return true if there is a message pending, in which case the received and total arguments will be set + bool getMessageReceiveProgress(int& received, int& total) const; + signals: /// Fired when a framed message has been received on this channel. - void receivedMessage(const QVariant& message); + void receivedMessage(const QVariant& message, Bitstream& in); private slots: void sendClearSharedObjectMessage(int id); - void handleMessage(const QVariant& message); + void handleMessage(const QVariant& message, Bitstream& in); private: @@ -370,6 +414,7 @@ private: void readData(QDataStream& in); int _index; + bool _output; CircularBuffer _buffer; CircularBuffer _assemblyBuffer; QDataStream _dataStream; @@ -381,6 +426,9 @@ private: int _writePositionResetPacketNumber; SpanList _acknowledged; bool _messagesEnabled; + int _messageLengthPlaceholder; + int _messageReceivedOffset; + int _messageSize; }; #endif // hifi_DatagramSequencer_h diff --git a/libraries/metavoxels/src/Endpoint.cpp b/libraries/metavoxels/src/Endpoint.cpp index c656054504..420a52ef95 100644 --- a/libraries/metavoxels/src/Endpoint.cpp +++ b/libraries/metavoxels/src/Endpoint.cpp @@ -19,6 +19,8 @@ Endpoint::Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendReco connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&))); connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&))); + connect(&_sequencer, SIGNAL(sendRecorded()), SLOT(recordSend())); + connect(&_sequencer, SIGNAL(receiveRecorded()), SLOT(recordReceive())); connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int))); connect(&_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int))); @@ -37,12 +39,12 @@ Endpoint::~Endpoint() { } void Endpoint::update() { - Bitstream& out = _sequencer.startPacket(); - writeUpdateMessage(out); - _sequencer.endPacket(); - - // record the send - _sendRecords.append(maybeCreateSendRecord()); + int packetsToSend = _sequencer.notePacketGroup(); + for (int i = 0; i < packetsToSend; i++) { + Bitstream& out = _sequencer.startPacket(); + writeUpdateMessage(out); + _sequencer.endPacket(); + } } int Endpoint::parseData(const QByteArray& packet) { @@ -59,8 +61,21 @@ void Endpoint::readMessage(Bitstream& in) { QVariant message; in >> message; handleMessage(message, in); - - // record the receipt +} + +void Endpoint::handleMessage(const QVariant& message, Bitstream& in) { + if (message.userType() == QMetaType::QVariantList) { + foreach (const QVariant& element, message.toList()) { + handleMessage(element, in); + } + } +} + +void Endpoint::recordSend() { + _sendRecords.append(maybeCreateSendRecord()); +} + +void Endpoint::recordReceive() { _receiveRecords.append(maybeCreateReceiveRecord()); } @@ -84,14 +99,6 @@ void Endpoint::writeUpdateMessage(Bitstream& out) { out << QVariant(); } -void Endpoint::handleMessage(const QVariant& message, Bitstream& in) { - if (message.userType() == QMetaType::QVariantList) { - foreach (const QVariant& element, message.toList()) { - handleMessage(element, in); - } - } -} - PacketRecord* Endpoint::maybeCreateSendRecord() const { return NULL; } diff --git a/libraries/metavoxels/src/Endpoint.h b/libraries/metavoxels/src/Endpoint.h index d253a69ded..d6999196d8 100644 --- a/libraries/metavoxels/src/Endpoint.h +++ b/libraries/metavoxels/src/Endpoint.h @@ -24,11 +24,16 @@ class Endpoint : public NodeData { Q_OBJECT public: + + /// The index of the input/output channel used to transmit reliable deltas. + static const int RELIABLE_DELTA_CHANNEL_INDEX = 1; Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendRecord = NULL, PacketRecord* baselineReceiveRecord = NULL); virtual ~Endpoint(); + const DatagramSequencer& getSequencer() const { return _sequencer; } + virtual void update(); virtual int parseData(const QByteArray& packet); @@ -37,6 +42,10 @@ protected slots: virtual void sendDatagram(const QByteArray& data); virtual void readMessage(Bitstream& in); + virtual void handleMessage(const QVariant& message, Bitstream& in); + + void recordSend(); + void recordReceive(); void clearSendRecordsBefore(int index); void clearReceiveRecordsBefore(int index); @@ -44,7 +53,6 @@ protected slots: protected: virtual void writeUpdateMessage(Bitstream& out); - virtual void handleMessage(const QVariant& message, Bitstream& in); virtual PacketRecord* maybeCreateSendRecord() const; virtual PacketRecord* maybeCreateReceiveRecord() const; diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp index 008a477187..f3ea1ae8c5 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.cpp +++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp @@ -86,7 +86,12 @@ void MetavoxelClientManager::updateClient(MetavoxelClient* client) { MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelClientManager* manager) : Endpoint(node, new PacketRecord(), new PacketRecord()), - _manager(manager) { + _manager(manager), + _reliableDeltaChannel(NULL), + _reliableDeltaID(0) { + + connect(_sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX), + SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&))); } void MetavoxelClient::guide(MetavoxelVisitor& visitor) { @@ -112,31 +117,50 @@ void MetavoxelClient::writeUpdateMessage(Bitstream& out) { out << QVariant::fromValue(state); } -void MetavoxelClient::readMessage(Bitstream& in) { - Endpoint::readMessage(in); - - // reapply local edits - foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) { - if (message.data.userType() == MetavoxelEditMessage::Type) { - message.data.value().apply(_data, _sequencer.getWeakSharedObjectHash()); - } - } -} - void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) { - if (message.userType() == MetavoxelDeltaMessage::Type) { + int userType = message.userType(); + if (userType == MetavoxelDeltaMessage::Type) { PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord(); - _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, getLastAcknowledgedSendRecord()->getLOD()); - + if (_reliableDeltaChannel) { + _remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _remoteDataLOD = _reliableDeltaLOD); + _sequencer.getInputStream().persistReadMappings(in.getAndResetReadMappings()); + in.clearPersistentMappings(); + _reliableDeltaChannel = NULL; + + } else { + _remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, + _remoteDataLOD = getLastAcknowledgedSendRecord()->getLOD()); + in.reset(); + } + // copy to local and reapply local edits + _data = _remoteData; + foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) { + if (message.data.userType() == MetavoxelEditMessage::Type) { + message.data.value().apply(_data, _sequencer.getWeakSharedObjectHash()); + } + } + } else if (userType == MetavoxelDeltaPendingMessage::Type) { + // check the id to make sure this is not a delta we've already processed + int id = message.value().id; + if (id > _reliableDeltaID) { + _reliableDeltaID = id; + _reliableDeltaChannel = _sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX); + _reliableDeltaChannel->getBitstream().copyPersistentMappings(_sequencer.getInputStream()); + _reliableDeltaLOD = getLastAcknowledgedSendRecord()->getLOD(); + PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord(); + _remoteDataLOD = receiveRecord->getLOD(); + _remoteData = receiveRecord->getData(); + } } else { Endpoint::handleMessage(message, in); } } PacketRecord* MetavoxelClient::maybeCreateSendRecord() const { - return new PacketRecord(_manager->getLOD()); + return new PacketRecord(_reliableDeltaChannel ? _reliableDeltaLOD : _manager->getLOD()); } PacketRecord* MetavoxelClient::maybeCreateReceiveRecord() const { - return new PacketRecord(getLastAcknowledgedSendRecord()->getLOD(), _data); + return new PacketRecord(_remoteDataLOD, _remoteData); } + diff --git a/libraries/metavoxels/src/MetavoxelClientManager.h b/libraries/metavoxels/src/MetavoxelClientManager.h index dd11e871ec..ad6c86c8fc 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.h +++ b/libraries/metavoxels/src/MetavoxelClientManager.h @@ -60,7 +60,6 @@ public: protected: virtual void writeUpdateMessage(Bitstream& out); - virtual void readMessage(Bitstream& in); virtual void handleMessage(const QVariant& message, Bitstream& in); virtual PacketRecord* maybeCreateSendRecord() const; @@ -70,6 +69,12 @@ private: MetavoxelClientManager* _manager; MetavoxelData _data; + MetavoxelData _remoteData; + MetavoxelLOD _remoteDataLOD; + + ReliableChannel* _reliableDeltaChannel; + MetavoxelLOD _reliableDeltaLOD; + int _reliableDeltaID; }; #endif // hifi_MetavoxelClientManager_h diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 2d61ede796..1362731a8a 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include @@ -627,6 +628,33 @@ bool MetavoxelData::deepEquals(const MetavoxelData& other, const MetavoxelLOD& l return true; } +void MetavoxelData::countNodes(int& internal, int& leaves, const MetavoxelLOD& lod) const { + glm::vec3 minimum = getMinimum(); + for (QHash::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) { + it.value()->countNodes(it.key(), minimum, _size, lod, internal, leaves); + } +} + +void MetavoxelData::dumpStats(QDebug debug) const { + QDebugStateSaver saver(debug); + debug.nospace() << "[size=" << _size << ", roots=["; + int totalInternal = 0, totalLeaves = 0; + glm::vec3 minimum = getMinimum(); + for (QHash::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) { + if (it != _roots.constBegin()) { + debug << ", "; + } + debug << it.key()->getName() << " (" << it.key()->metaObject()->className() << "): "; + int internal = 0, leaves = 0; + it.value()->countNodes(it.key(), minimum, _size, MetavoxelLOD(), internal, leaves); + debug << internal << " internal, " << leaves << " leaves, " << (internal + leaves) << " total"; + totalInternal += internal; + totalLeaves += leaves; + } + debug << "], totalInternal=" << totalInternal << ", totalLeaves=" << totalLeaves << + ", grandTotal=" << (totalInternal + totalLeaves) << "]"; +} + bool MetavoxelData::operator==(const MetavoxelData& other) const { return _size == other._size && _roots == other._roots; } @@ -1056,8 +1084,20 @@ void MetavoxelNode::getSpanners(const AttributePointer& attribute, const glm::ve } float nextSize = size * 0.5f; for (int i = 0; i < CHILD_COUNT; i++) { - glm::vec3 nextMinimum = getNextMinimum(minimum, nextSize, i); - _children[i]->getSpanners(attribute, nextMinimum, nextSize, lod, results); + _children[i]->getSpanners(attribute, getNextMinimum(minimum, nextSize, i), nextSize, lod, results); + } +} + +void MetavoxelNode::countNodes(const AttributePointer& attribute, const glm::vec3& minimum, + float size, const MetavoxelLOD& lod, int& internal, int& leaves) const { + if (isLeaf() || !lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier())) { + leaves++; + return; + } + internal++; + float nextSize = size * 0.5f; + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i]->countNodes(attribute, getNextMinimum(minimum, nextSize, i), nextSize, lod, internal, leaves); } } diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 6a7ba33eb5..f558bf8e80 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -125,6 +125,11 @@ public: /// shallow comparison). bool deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod = MetavoxelLOD()) const; + /// Counts the nodes in the data. + void countNodes(int& internalNodes, int& leaves, const MetavoxelLOD& lod = MetavoxelLOD()) const; + + void dumpStats(QDebug debug = QDebug(QtDebugMsg)) const; + bool operator==(const MetavoxelData& other) const; bool operator!=(const MetavoxelData& other) const; @@ -221,6 +226,9 @@ public: void getSpanners(const AttributePointer& attribute, const glm::vec3& minimum, float size, const MetavoxelLOD& lod, SharedObjectSet& results) const; + void countNodes(const AttributePointer& attribute, const glm::vec3& minimum, + float size, const MetavoxelLOD& lod, int& internalNodes, int& leaves) const; + private: Q_DISABLE_COPY(MetavoxelNode) diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index 8f819fe3d8..91d73c08a9 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -61,6 +61,17 @@ class MetavoxelDeltaMessage { DECLARE_STREAMABLE_METATYPE(MetavoxelDeltaMessage) +/// A message indicating that metavoxel delta information is being sent on a reliable channel. +class MetavoxelDeltaPendingMessage { + STREAMABLE + +public: + + STREAM int id; +}; + +DECLARE_STREAMABLE_METATYPE(MetavoxelDeltaPendingMessage) + /// A simple streamable edit. class MetavoxelEditMessage { STREAMABLE diff --git a/libraries/models/src/ModelItem.cpp b/libraries/models/src/ModelItem.cpp index 8c061102a0..750af8f1b6 100644 --- a/libraries/models/src/ModelItem.cpp +++ b/libraries/models/src/ModelItem.cpp @@ -331,6 +331,7 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& newModelItem.setCreatorTokenID(creatorTokenID); newModelItem._newlyCreated = true; + valid = true; } else { // look up the existing modelItem @@ -339,20 +340,19 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& // copy existing properties before over-writing with new properties if (existingModelItem) { newModelItem = *existingModelItem; + valid = true; } else { // the user attempted to edit a modelItem that doesn't exist - qDebug() << "user attempted to edit a modelItem that doesn't exist..."; + qDebug() << "user attempted to edit a modelItem that doesn't exist... editID=" << editID; + + // NOTE: even though this is a bad editID, we have to consume the edit details, so that + // the buffer doesn't get corrupted for further processing... valid = false; - return newModelItem; } newModelItem._id = editID; newModelItem._newlyCreated = false; } - // if we got this far, then our result will be valid - valid = true; - - // lastEdited memcpy(&newModelItem._lastEdited, dataAt, sizeof(newModelItem._lastEdited)); dataAt += sizeof(newModelItem._lastEdited); diff --git a/libraries/models/src/ModelsScriptingInterface.cpp b/libraries/models/src/ModelsScriptingInterface.cpp index bac1213071..634039f949 100644 --- a/libraries/models/src/ModelsScriptingInterface.cpp +++ b/libraries/models/src/ModelsScriptingInterface.cpp @@ -69,8 +69,9 @@ ModelItemProperties ModelsScriptingInterface::getModelProperties(ModelItemID mod } if (_modelTree) { _modelTree->lockForRead(); - const ModelItem* model = _modelTree->findModelByID(identity.id, true); + ModelItem* model = const_cast(_modelTree->findModelByID(identity.id, true)); if (model) { + model->setSittingPoints(_modelTree->getGeometryForModel(*model)->sittingPoints); results.copyFromModelItem(*model); } else { results.setIsUnknownID(); diff --git a/libraries/networking/src/NetworkAccessManager.cpp b/libraries/networking/src/NetworkAccessManager.cpp index 7e5ba3f66e..e92760d303 100644 --- a/libraries/networking/src/NetworkAccessManager.cpp +++ b/libraries/networking/src/NetworkAccessManager.cpp @@ -9,153 +9,19 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include -#include +#include #include "NetworkAccessManager.h" +QThreadStorage networkAccessManagers; + NetworkAccessManager& NetworkAccessManager::getInstance() { - static NetworkAccessManager sharedInstance; - return sharedInstance; + if (!networkAccessManagers.hasLocalData()) { + networkAccessManagers.setLocalData(new NetworkAccessManager()); + } + + return *networkAccessManagers.localData(); } NetworkAccessManager::NetworkAccessManager() { - qRegisterMetaType(); } - -QNetworkReply* NetworkAccessManager::get(const QNetworkRequest& request) { - if (QThread::currentThread() != thread()) { - QNetworkReply* result; - QMetaObject::invokeMethod(this, - "get", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QNetworkReply*, result), - Q_ARG(const QNetworkRequest, request)); - return result; - } - return QNetworkAccessManager::get(request); -} - -QNetworkReply* NetworkAccessManager::head(const QNetworkRequest& request) { - if (QThread::currentThread() != thread()) { - QNetworkReply* result; - QMetaObject::invokeMethod(this, - "head", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QNetworkReply*, result), - Q_ARG(const QNetworkRequest, request)); - return result; - } - return QNetworkAccessManager::head(request); -} - -QNetworkReply* NetworkAccessManager::post(const QNetworkRequest& request, QIODevice* data) { - if (QThread::currentThread() != thread()) { - QNetworkReply* result; - QMetaObject::invokeMethod(this, - "post", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QNetworkReply*, result), - Q_ARG(const QNetworkRequest, request), - Q_ARG(QIODevice*, data)); - return result; - } - return QNetworkAccessManager::post(request, data); -} - -QNetworkReply* NetworkAccessManager::post(const QNetworkRequest& request, const QByteArray& data) { - if (QThread::currentThread() != thread()) { - QNetworkReply* result; - QMetaObject::invokeMethod(this, - "post", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QNetworkReply*, result), - Q_ARG(const QNetworkRequest, request), - Q_ARG(const QByteArray, data)); - return result; - } - return QNetworkAccessManager::post(request, data); -} - -QNetworkReply* NetworkAccessManager::post(const QNetworkRequest& request, QHttpMultiPart* multiPart) { - if (QThread::currentThread() != thread()) { - QNetworkReply* result; - QMetaObject::invokeMethod(this, - "post", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QNetworkReply*, result), - Q_ARG(const QNetworkRequest, request), - Q_ARG(QHttpMultiPart*, multiPart)); - return result; - } - return QNetworkAccessManager::post(request, multiPart); -} - -QNetworkReply* NetworkAccessManager::put(const QNetworkRequest& request, QIODevice* data) { - if (QThread::currentThread() != thread()) { - QNetworkReply* result; - QMetaObject::invokeMethod(this, - "put", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QNetworkReply*, result), - Q_ARG(const QNetworkRequest, request), - Q_ARG(QIODevice*, data)); - return result; - } - return QNetworkAccessManager::put(request, data); -} - -QNetworkReply* NetworkAccessManager::put(const QNetworkRequest& request, QHttpMultiPart* multiPart) { - if (QThread::currentThread() != thread()) { - QNetworkReply* result; - QMetaObject::invokeMethod(this, - "put", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QNetworkReply*, result), - Q_ARG(const QNetworkRequest, request), - Q_ARG(QHttpMultiPart*, multiPart)); - return result; - } - return QNetworkAccessManager::put(request, multiPart); -} - -QNetworkReply* NetworkAccessManager::put(const QNetworkRequest & request, const QByteArray & data) { - if (QThread::currentThread() != thread()) { - QNetworkReply* result; - QMetaObject::invokeMethod(this, - "put", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QNetworkReply*, result), - Q_ARG(const QNetworkRequest, request), - Q_ARG(const QByteArray, data)); - return result; - } - return QNetworkAccessManager::put(request, data); -} - - -QNetworkReply* NetworkAccessManager::sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb, QIODevice* data) { - if (QThread::currentThread() != thread()) { - QNetworkReply* result; - QMetaObject::invokeMethod(this, - "sendCustomRequest", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QNetworkReply*, result), - Q_ARG(const QNetworkRequest, request), - Q_ARG(const QByteArray, verb), - Q_ARG(QIODevice*, data)); - return result; - } - return QNetworkAccessManager::sendCustomRequest(request, verb, data); -} - -void NetworkAccessManager::setCache(QAbstractNetworkCache* cache) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, - "setCache", - Qt::BlockingQueuedConnection, - Q_ARG(QAbstractNetworkCache*, cache)); - } - QNetworkAccessManager::setCache(cache); -} \ No newline at end of file diff --git a/libraries/networking/src/NetworkAccessManager.h b/libraries/networking/src/NetworkAccessManager.h index 1b49cc9dee..9594170518 100644 --- a/libraries/networking/src/NetworkAccessManager.h +++ b/libraries/networking/src/NetworkAccessManager.h @@ -13,30 +13,13 @@ #define hifi_NetworkAccessManager_h #include -#include -#include -/// Wrapper around QNetworkAccessManager wo that we only use one instance -/// For any other method you should need, make sure to be on the right thread -/// or if it is not but is a slot, use QMetaObject::invokeMethod() -/// In the case what you want to call isn't a slot and you aren't on the same thread, -/// then add then method to the method to the wrapper with the Q_INVKABLE flag +/// Wrapper around QNetworkAccessManager to restrict at one instance by thread class NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: static NetworkAccessManager& getInstance(); - Q_INVOKABLE QNetworkReply* get(const QNetworkRequest& request); - Q_INVOKABLE QNetworkReply* head(const QNetworkRequest& request); - Q_INVOKABLE QNetworkReply* post(const QNetworkRequest& request, QIODevice* data); - Q_INVOKABLE QNetworkReply* post(const QNetworkRequest& request, const QByteArray& data); - Q_INVOKABLE QNetworkReply* post(const QNetworkRequest& request, QHttpMultiPart* multiPart); - Q_INVOKABLE QNetworkReply* put(const QNetworkRequest& request, QIODevice* data); - Q_INVOKABLE QNetworkReply* put(const QNetworkRequest& request, QHttpMultiPart* multiPart); - Q_INVOKABLE QNetworkReply* put(const QNetworkRequest& request, const QByteArray& data); - Q_INVOKABLE QNetworkReply* sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb, QIODevice* data = 0); - Q_INVOKABLE void setCache(QAbstractNetworkCache* cache); - private: NetworkAccessManager(); }; diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index a5c05a6ae9..f17715ddfe 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -78,6 +78,8 @@ PacketVersion versionForPacketType(PacketType type) { return 2; case PacketTypeModelErase: return 1; + case PacketTypeAudioStreamStats: + return 1; default: return 0; } diff --git a/libraries/networking/src/SentPacketHistory.cpp b/libraries/networking/src/SentPacketHistory.cpp index 841b5e909c..3cdb0af8c0 100644 --- a/libraries/networking/src/SentPacketHistory.cpp +++ b/libraries/networking/src/SentPacketHistory.cpp @@ -14,8 +14,6 @@ SentPacketHistory::SentPacketHistory(int size) : _sentPackets(size), - _newestPacketAt(0), - _numExistingPackets(0), _newestSequenceNumber(std::numeric_limits::max()) { } @@ -29,16 +27,8 @@ void SentPacketHistory::packetSent(uint16_t sequenceNumber, const QByteArray& pa qDebug() << "Unexpected sequence number passed to SentPacketHistory::packetSent()!" << "Expected:" << expectedSequenceNumber << "Actual:" << sequenceNumber; } - _newestSequenceNumber = sequenceNumber; - - // increment _newestPacketAt cyclically, insert new packet there. - // this will overwrite the oldest packet in the buffer - _newestPacketAt = (_newestPacketAt == _sentPackets.size() - 1) ? 0 : _newestPacketAt + 1; - _sentPackets[_newestPacketAt] = packet; - if (_numExistingPackets < _sentPackets.size()) { - _numExistingPackets++; - } + _sentPackets.insert(packet); } const QByteArray* SentPacketHistory::getPacket(uint16_t sequenceNumber) const { @@ -51,13 +41,6 @@ const QByteArray* SentPacketHistory::getPacket(uint16_t sequenceNumber) const { if (seqDiff < 0) { seqDiff += UINT16_RANGE; } - // if desired sequence number is too old to be found in the history, return null - if (seqDiff >= _numExistingPackets) { - return NULL; - } - int packetAt = _newestPacketAt - seqDiff; - if (packetAt < 0) { - packetAt += _sentPackets.size(); - } - return &_sentPackets.at(packetAt); + + return _sentPackets.get(seqDiff); } diff --git a/libraries/networking/src/SentPacketHistory.h b/libraries/networking/src/SentPacketHistory.h index 325f973f7e..96d10a63cf 100644 --- a/libraries/networking/src/SentPacketHistory.h +++ b/libraries/networking/src/SentPacketHistory.h @@ -13,7 +13,7 @@ #include #include -#include +#include "RingBufferHistory.h" #include "SequenceNumberStats.h" @@ -26,9 +26,7 @@ public: const QByteArray* getPacket(uint16_t sequenceNumber) const; private: - QVector _sentPackets; // circular buffer - int _newestPacketAt; - int _numExistingPackets; + RingBufferHistory _sentPackets; // circular buffer uint16_t _newestSequenceNumber; }; diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index 3f696a522b..28dea7de63 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -13,29 +13,19 @@ #include -SequenceNumberStats::SequenceNumberStats() +SequenceNumberStats::SequenceNumberStats(int statsHistoryLength) : _lastReceived(std::numeric_limits::max()), _missingSet(), - _numReceived(0), - _numUnreasonable(0), - _numEarly(0), - _numLate(0), - _numLost(0), - _numRecovered(0), - _numDuplicate(0), - _lastSenderUUID() + _stats(), + _lastSenderUUID(), + _statsHistory(statsHistoryLength) { } void SequenceNumberStats::reset() { _missingSet.clear(); - _numReceived = 0; - _numUnreasonable = 0; - _numEarly = 0; - _numLate = 0; - _numLost = 0; - _numRecovered = 0; - _numDuplicate = 0; + _stats = PacketStreamStats(); + _statsHistory.clear(); } static const int UINT16_RANGE = std::numeric_limits::max() + 1; @@ -51,9 +41,9 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU } // determine our expected sequence number... handle rollover appropriately - quint16 expected = _numReceived > 0 ? _lastReceived + (quint16)1 : incoming; + quint16 expected = _stats._numReceived > 0 ? _lastReceived + (quint16)1 : incoming; - _numReceived++; + _stats._numReceived++; if (incoming == expected) { // on time _lastReceived = incoming; @@ -80,7 +70,7 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU // ignore packet if gap is unreasonable qDebug() << "ignoring unreasonable sequence number:" << incoming << "previous:" << _lastReceived; - _numUnreasonable++; + _stats._numUnreasonable++; return; } @@ -92,8 +82,8 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU qDebug() << ">>>>>>>> missing gap=" << (incomingInt - expectedInt); } - _numEarly++; - _numLost += (incomingInt - expectedInt); + _stats._numEarly++; + _stats._numLost += (incomingInt - expectedInt); _lastReceived = incoming; // add all sequence numbers that were skipped to the missing sequence numbers list @@ -110,7 +100,7 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU if (wantExtraDebugging) { qDebug() << "this packet is later than expected..."; } - _numLate++; + _stats._numLate++; // do not update _lastReceived; it shouldn't become smaller @@ -119,13 +109,13 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU if (wantExtraDebugging) { qDebug() << "found it in _missingSet"; } - _numLost--; - _numRecovered++; + _stats._numLost--; + _stats._numRecovered++; } else { if (wantExtraDebugging) { qDebug() << "sequence:" << incoming << "was NOT found in _missingSet and is probably a duplicate"; } - _numDuplicate++; + _stats._numDuplicate++; } } } @@ -180,3 +170,26 @@ void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) { } } } + +PacketStreamStats SequenceNumberStats::getStatsForHistoryWindow() const { + + const PacketStreamStats* newestStats = _statsHistory.getNewestEntry(); + const PacketStreamStats* oldestStats = _statsHistory.get(_statsHistory.getNumEntries() - 1); + + // this catches cases where history is length 1 or 0 (both are NULL in case of 0) + if (newestStats == oldestStats) { + return PacketStreamStats(); + } + + // calculate difference between newest stats and oldest stats to get window stats + PacketStreamStats windowStats; + windowStats._numReceived = newestStats->_numReceived - oldestStats->_numReceived; + windowStats._numUnreasonable = newestStats->_numUnreasonable - oldestStats->_numUnreasonable; + windowStats._numEarly = newestStats->_numEarly - oldestStats->_numEarly; + windowStats._numLate = newestStats->_numLate - oldestStats->_numLate; + windowStats._numLost = newestStats->_numLost - oldestStats->_numLost; + windowStats._numRecovered = newestStats->_numRecovered - oldestStats->_numRecovered; + windowStats._numDuplicate = newestStats->_numDuplicate - oldestStats->_numDuplicate; + + return windowStats; +} diff --git a/libraries/networking/src/SequenceNumberStats.h b/libraries/networking/src/SequenceNumberStats.h index 88c8748b03..8c16345aaf 100644 --- a/libraries/networking/src/SequenceNumberStats.h +++ b/libraries/networking/src/SequenceNumberStats.h @@ -13,31 +13,29 @@ #define hifi_SequenceNumberStats_h #include "SharedUtil.h" +#include "RingBufferHistory.h" #include const int MAX_REASONABLE_SEQUENCE_GAP = 1000; -class SequenceNumberStats { +class PacketStreamStats { public: - SequenceNumberStats(); + PacketStreamStats() + : _numReceived(0), + _numUnreasonable(0), + _numEarly(0), + _numLate(0), + _numLost(0), + _numRecovered(0), + _numDuplicate(0) + {} - void reset(); - void sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false); - void pruneMissingSet(const bool wantExtraDebugging = false); - - quint32 getNumReceived() const { return _numReceived; } - quint32 getNumUnreasonable() const { return _numUnreasonable; } - quint32 getNumOutOfOrder() const { return _numEarly + _numLate; } - quint32 getNumEarly() const { return _numEarly; } - quint32 getNumLate() const { return _numLate; } - quint32 getNumLost() const { return _numLost; } - quint32 getNumRecovered() const { return _numRecovered; } - quint32 getNumDuplicate() const { return _numDuplicate; } - const QSet& getMissingSet() const { return _missingSet; } - -private: - quint16 _lastReceived; - QSet _missingSet; + float getUnreasonableRate() const { return (float)_numUnreasonable / _numReceived; } + float getNumEaryRate() const { return (float)_numEarly / _numReceived; } + float getLateRate() const { return (float)_numLate / _numReceived; } + float getLostRate() const { return (float)_numLost / _numReceived; } + float getRecoveredRate() const { return (float)_numRecovered / _numReceived; } + float getDuplicateRate() const { return (float)_numDuplicate / _numReceived; } quint32 _numReceived; quint32 _numUnreasonable; @@ -46,8 +44,38 @@ private: quint32 _numLost; quint32 _numRecovered; quint32 _numDuplicate; +}; + +class SequenceNumberStats { +public: + SequenceNumberStats(int statsHistoryLength = 0); + + void reset(); + void sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false); + void pruneMissingSet(const bool wantExtraDebugging = false); + void pushStatsToHistory() { _statsHistory.insert(_stats); } + + quint32 getNumReceived() const { return _stats._numReceived; } + quint32 getNumUnreasonable() const { return _stats._numUnreasonable; } + quint32 getNumOutOfOrder() const { return _stats._numEarly + _stats._numLate; } + quint32 getNumEarly() const { return _stats._numEarly; } + quint32 getNumLate() const { return _stats._numLate; } + quint32 getNumLost() const { return _stats._numLost; } + quint32 getNumRecovered() const { return _stats._numRecovered; } + quint32 getNumDuplicate() const { return _stats._numDuplicate; } + const PacketStreamStats& getStats() const { return _stats; } + PacketStreamStats getStatsForHistoryWindow() const; + const QSet& getMissingSet() const { return _missingSet; } + +private: + quint16 _lastReceived; + QSet _missingSet; + + PacketStreamStats _stats; QUuid _lastSenderUUID; + + RingBufferHistory _statsHistory; }; #endif // hifi_SequenceNumberStats_h diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index d67306e8c7..2ed8f6c2a0 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -34,7 +34,6 @@ OctreeEditPacketSender::OctreeEditPacketSender() : _maxPendingMessages(DEFAULT_MAX_PENDING_MESSAGES), _releaseQueuedMessagesPending(false), _serverJurisdictions(NULL), - _sequenceNumber(0), _maxPacketSize(MAX_PACKET_SIZE) { } @@ -88,7 +87,7 @@ bool OctreeEditPacketSender::serversExist() const { // This method is called when the edit packet layer has determined that it has a fully formed packet destined for // a known nodeID. -void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, const unsigned char* buffer, ssize_t length) { +void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned char* buffer, ssize_t length) { NodeList* nodeList = NodeList::getInstance(); foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { @@ -96,13 +95,18 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, const unsi if (node->getType() == getMyNodeType() && ((node->getUUID() == nodeUUID) || (nodeUUID.isNull()))) { if (node->getActiveSocket()) { + + // pack sequence number + int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast(buffer)); + unsigned char* sequenceAt = buffer + numBytesPacketHeader; + quint16 sequence = _outgoingSequenceNumbers[nodeUUID]++; + memcpy(sequenceAt, &sequence, sizeof(quint16)); + + // send packet QByteArray packet(reinterpret_cast(buffer), length); queuePacketForSending(node, packet); - // extract sequence number and add packet to history - int numBytesPacketHeader = numBytesForPacketHeader(packet); - const char* dataAt = reinterpret_cast(packet.data()) + numBytesPacketHeader; - unsigned short int sequence = *((unsigned short int*)dataAt); + // add packet to history _sentPacketHistories[nodeUUID].packetSent(sequence, packet); // debugging output... @@ -312,11 +316,8 @@ void OctreeEditPacketSender::releaseQueuedPacket(EditPacketBuffer& packetBuffer) void OctreeEditPacketSender::initializePacket(EditPacketBuffer& packetBuffer, PacketType type) { packetBuffer._currentSize = populatePacketHeader(reinterpret_cast(&packetBuffer._currentBuffer[0]), type); - // pack in sequence numbers - unsigned short int* sequenceAt = (unsigned short int*)&packetBuffer._currentBuffer[packetBuffer._currentSize]; - *sequenceAt = _sequenceNumber; - packetBuffer._currentSize += sizeof(unsigned short int); // nudge past sequence - _sequenceNumber++; + // skip over sequence number for now; will be packed when packet is ready to be sent out + packetBuffer._currentSize += sizeof(quint16); // pack in timestamp quint64 now = usecTimestampNow(); @@ -373,5 +374,6 @@ void OctreeEditPacketSender::nodeKilled(SharedNodePointer node) { // TODO: add locks QUuid nodeUUID = node->getUUID(); _pendingEditPackets.remove(nodeUUID); + _outgoingSequenceNumbers.remove(nodeUUID); _sentPacketHistories.remove(nodeUUID); } diff --git a/libraries/octree/src/OctreeEditPacketSender.h b/libraries/octree/src/OctreeEditPacketSender.h index c16c0a2d4b..cdcfc21d4a 100644 --- a/libraries/octree/src/OctreeEditPacketSender.h +++ b/libraries/octree/src/OctreeEditPacketSender.h @@ -21,7 +21,7 @@ /// Used for construction of edit packets class EditPacketBuffer { public: - EditPacketBuffer() : _nodeUUID(), _currentType(PacketTypeUnknown), _currentSize(0) { } + EditPacketBuffer() : _nodeUUID(), _currentType(PacketTypeUnknown), _currentSize(0) { } EditPacketBuffer(PacketType type, unsigned char* codeColorBuffer, ssize_t length, const QUuid nodeUUID = QUuid()); QUuid _nodeUUID; PacketType _currentType; @@ -100,7 +100,7 @@ public: protected: bool _shouldSend; - void queuePacketToNode(const QUuid& nodeID, const unsigned char* buffer, ssize_t length); + void queuePacketToNode(const QUuid& nodeID, unsigned char* buffer, ssize_t length); void queuePendingPacketToNodes(PacketType type, unsigned char* buffer, ssize_t length); void queuePacketToNodes(unsigned char* buffer, ssize_t length); void initializePacket(EditPacketBuffer& packetBuffer, PacketType type); @@ -120,12 +120,12 @@ protected: NodeToJurisdictionMap* _serverJurisdictions; - unsigned short int _sequenceNumber; int _maxPacketSize; QMutex _releaseQueuedPacketMutex; // TODO: add locks for this and _pendingEditPackets QHash _sentPacketHistories; + QHash _outgoingSequenceNumbers; }; #endif // hifi_OctreeEditPacketSender_h diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index 59265c00dc..76890afafe 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -357,15 +357,23 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr processedBytes = 0; // the first part of the data is our octcode... - int octets = numberOfThreeBitSectionsInCode(data); + int octets = numberOfThreeBitSectionsInCode(data, length); int lengthOfOctcode = bytesRequiredForCodeLength(octets); // we don't actually do anything with this octcode... dataAt += lengthOfOctcode; processedBytes += lengthOfOctcode; - + // id uint32_t editID; + + // check to make sure we have enough content to keep reading... + if (processedBytes + sizeof(editID) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } + memcpy(&editID, dataAt, sizeof(editID)); dataAt += sizeof(editID); processedBytes += sizeof(editID); @@ -377,6 +385,14 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // If this is a NEW_PARTICLE, then we assume that there's an additional uint32_t creatorToken, that // we want to send back to the creator as an map to the actual id uint32_t creatorTokenID; + + // check to make sure we have enough content to keep reading... + if (processedBytes + sizeof(creatorTokenID) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } + memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID)); dataAt += sizeof(creatorTokenID); processedBytes += sizeof(creatorTokenID); @@ -385,6 +401,8 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr newParticle._newlyCreated = true; newParticle.setAge(0); // this guy is new! + valid = true; + } else { // look up the existing particle const Particle* existingParticle = tree->findParticleByID(editID, true); @@ -392,21 +410,27 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // copy existing properties before over-writing with new properties if (existingParticle) { newParticle = *existingParticle; + valid = true; + } else { // the user attempted to edit a particle that doesn't exist - qDebug() << "user attempted to edit a particle that doesn't exist..."; + qDebug() << "user attempted to edit a particle that doesn't exist... editID=" << editID; + + // NOTE: even though this is a bad particle ID, we have to consume the edit details, so that + // the buffer doesn't get corrupted for further processing... valid = false; - return newParticle; } newParticle._id = editID; newParticle._newlyCreated = false; } - // if we got this far, then our result will be valid - valid = true; - - // lastEdited + // check to make sure we have enough content to keep reading... + if (processedBytes + sizeof(newParticle._lastEdited) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&newParticle._lastEdited, dataAt, sizeof(newParticle._lastEdited)); dataAt += sizeof(newParticle._lastEdited); processedBytes += sizeof(newParticle._lastEdited); @@ -415,6 +439,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // properties included bits uint16_t packetContainsBits = 0; if (!isNewParticle) { + if (processedBytes + sizeof(packetContainsBits) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&packetContainsBits, dataAt, sizeof(packetContainsBits)); dataAt += sizeof(packetContainsBits); processedBytes += sizeof(packetContainsBits); @@ -423,6 +452,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // radius if (isNewParticle || ((packetContainsBits & CONTAINS_RADIUS) == CONTAINS_RADIUS)) { + if (processedBytes + sizeof(newParticle._radius) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&newParticle._radius, dataAt, sizeof(newParticle._radius)); dataAt += sizeof(newParticle._radius); processedBytes += sizeof(newParticle._radius); @@ -430,6 +464,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // position if (isNewParticle || ((packetContainsBits & CONTAINS_POSITION) == CONTAINS_POSITION)) { + if (processedBytes + sizeof(newParticle._position) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&newParticle._position, dataAt, sizeof(newParticle._position)); dataAt += sizeof(newParticle._position); processedBytes += sizeof(newParticle._position); @@ -437,6 +476,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // color if (isNewParticle || ((packetContainsBits & CONTAINS_COLOR) == CONTAINS_COLOR)) { + if (processedBytes + sizeof(newParticle._color) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(newParticle._color, dataAt, sizeof(newParticle._color)); dataAt += sizeof(newParticle._color); processedBytes += sizeof(newParticle._color); @@ -444,6 +488,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // velocity if (isNewParticle || ((packetContainsBits & CONTAINS_VELOCITY) == CONTAINS_VELOCITY)) { + if (processedBytes + sizeof(newParticle._velocity) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&newParticle._velocity, dataAt, sizeof(newParticle._velocity)); dataAt += sizeof(newParticle._velocity); processedBytes += sizeof(newParticle._velocity); @@ -451,6 +500,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // gravity if (isNewParticle || ((packetContainsBits & CONTAINS_GRAVITY) == CONTAINS_GRAVITY)) { + if (processedBytes + sizeof(newParticle._gravity) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&newParticle._gravity, dataAt, sizeof(newParticle._gravity)); dataAt += sizeof(newParticle._gravity); processedBytes += sizeof(newParticle._gravity); @@ -458,6 +512,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // damping if (isNewParticle || ((packetContainsBits & CONTAINS_DAMPING) == CONTAINS_DAMPING)) { + if (processedBytes + sizeof(newParticle._damping) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&newParticle._damping, dataAt, sizeof(newParticle._damping)); dataAt += sizeof(newParticle._damping); processedBytes += sizeof(newParticle._damping); @@ -465,6 +524,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // lifetime if (isNewParticle || ((packetContainsBits & CONTAINS_LIFETIME) == CONTAINS_LIFETIME)) { + if (processedBytes + sizeof(newParticle._lifetime) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&newParticle._lifetime, dataAt, sizeof(newParticle._lifetime)); dataAt += sizeof(newParticle._lifetime); processedBytes += sizeof(newParticle._lifetime); @@ -473,6 +537,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // TODO: make inHand and shouldDie into single bits // inHand if (isNewParticle || ((packetContainsBits & CONTAINS_INHAND) == CONTAINS_INHAND)) { + if (processedBytes + sizeof(newParticle._inHand) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&newParticle._inHand, dataAt, sizeof(newParticle._inHand)); dataAt += sizeof(newParticle._inHand); processedBytes += sizeof(newParticle._inHand); @@ -480,6 +549,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // shouldDie if (isNewParticle || ((packetContainsBits & CONTAINS_SHOULDDIE) == CONTAINS_SHOULDDIE)) { + if (processedBytes + sizeof(newParticle._shouldDie) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&newParticle._shouldDie, dataAt, sizeof(newParticle._shouldDie)); dataAt += sizeof(newParticle._shouldDie); processedBytes += sizeof(newParticle._shouldDie); @@ -488,9 +562,20 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // script if (isNewParticle || ((packetContainsBits & CONTAINS_SCRIPT) == CONTAINS_SCRIPT)) { uint16_t scriptLength; + if (processedBytes + sizeof(scriptLength) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&scriptLength, dataAt, sizeof(scriptLength)); dataAt += sizeof(scriptLength); processedBytes += sizeof(scriptLength); + + if (processedBytes + scriptLength > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } QString tempString((const char*)dataAt); newParticle._script = tempString; dataAt += scriptLength; @@ -500,9 +585,20 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // modelURL if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_URL) == CONTAINS_MODEL_URL)) { uint16_t modelURLLength; + if (processedBytes + sizeof(modelURLLength) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&modelURLLength, dataAt, sizeof(modelURLLength)); dataAt += sizeof(modelURLLength); processedBytes += sizeof(modelURLLength); + + if (processedBytes + modelURLLength > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } QString tempString((const char*)dataAt); newParticle._modelURL = tempString; dataAt += modelURLLength; @@ -511,6 +607,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // modelScale if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_SCALE) == CONTAINS_MODEL_SCALE)) { + if (processedBytes + sizeof(newParticle._modelScale) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&newParticle._modelScale, dataAt, sizeof(newParticle._modelScale)); dataAt += sizeof(newParticle._modelScale); processedBytes += sizeof(newParticle._modelScale); @@ -518,6 +619,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // modelTranslation if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_TRANSLATION) == CONTAINS_MODEL_TRANSLATION)) { + if (processedBytes + sizeof(newParticle._modelTranslation) > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } memcpy(&newParticle._modelTranslation, dataAt, sizeof(newParticle._modelTranslation)); dataAt += sizeof(newParticle._modelTranslation); processedBytes += sizeof(newParticle._modelTranslation); @@ -525,6 +631,12 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // modelRotation if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_ROTATION) == CONTAINS_MODEL_ROTATION)) { + const int expectedBytesForPackedQuat = sizeof(uint16_t) * 4; // this is how we pack the quats + if (processedBytes + expectedBytesForPackedQuat > length) { + valid = false; + processedBytes = length; + return newParticle; // fail as if we read the entire buffer + } int bytes = unpackOrientationQuatFromBytes(dataAt, newParticle._modelRotation); dataAt += bytes; processedBytes += bytes; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 21fac94488..e2c7d90f7a 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -524,8 +524,6 @@ void ScriptEngine::run() { qint64 now = usecTimestampNow(); float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; - emit update(deltaTime); - lastUpdate = now; if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); @@ -533,6 +531,9 @@ void ScriptEngine::run() { emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + _engine.uncaughtException().toString()); _engine.clearExceptions(); } + + emit update(deltaTime); + lastUpdate = now; } emit scriptEnding(); diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h new file mode 100644 index 0000000000..7c645dbd93 --- /dev/null +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -0,0 +1,150 @@ +// +// MovingMinMaxAvg.h +// libraries/shared/src +// +// Created by Yixin Wang on 7/8/2014 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_MovingMinMaxAvg_h +#define hifi_MovingMinMaxAvg_h + +#include + +#include "RingBufferHistory.h" + +template +class MovingMinMaxAvg { + +private: + class Stats { + public: + Stats() + : _min(std::numeric_limits::max()), + _max(std::numeric_limits::min()), + _average(0.0) {} + + void updateWithSample(T sample, int& numSamplesInAverage) { + if (sample < _min) { + _min = sample; + } + if (sample > _max) { + _max = sample; + } + _average = _average * ((double)numSamplesInAverage / (numSamplesInAverage + 1)) + + (double)sample / (numSamplesInAverage + 1); + numSamplesInAverage++; + } + + void updateWithOtherStats(const Stats& other, int& numStatsInAverage) { + if (other._min < _min) { + _min = other._min; + } + if (other._max > _max) { + _max = other._max; + } + _average = _average * ((double)numStatsInAverage / (numStatsInAverage + 1)) + + other._average / (numStatsInAverage + 1); + numStatsInAverage++; + } + + T _min; + T _max; + double _average; + }; + +public: + // This class collects 3 stats (min, max, avg) over a moving window of samples. + // The moving window contains _windowIntervals * _intervalLength samples. + // Those stats are updated every _intervalLength samples collected. When that happens, _newStatsAvaialble is set + // to true and it's up to the user to clear that flag. + // For example, if you want a moving avg of the past 5000 samples updated every 100 samples, you would instantiate + // this class with MovingMinMaxAvg(100, 50). If you want a moving min of the past 100 samples updated on every + // new sample, instantiate this class with MovingMinMaxAvg(1, 100). + + MovingMinMaxAvg(int intervalLength, int windowIntervals) + : _intervalLength(intervalLength), + _windowIntervals(windowIntervals), + _overallStats(), + _samplesCollected(0), + _windowStats(), + _existingSamplesInCurrentInterval(0), + _currentIntervalStats(), + _intervalStats(windowIntervals), + _newStatsAvailable(false) + {} + + void reset() { + _overallStats = Stats(); + _samplesCollected = 0; + _windowStats = Stats(); + _existingSamplesInCurrentInterval = 0; + _currentIntervalStats = Stats(); + _intervalStats.clear(); + _newStatsAvailable = false; + } + + void update(T newSample) { + // update overall stats + _overallStats.updateWithSample(newSample, _samplesCollected); + + // update the current interval stats + _currentIntervalStats.updateWithSample(newSample, _existingSamplesInCurrentInterval); + + // if the current interval of samples is now full, record its stats into our past intervals' stats + if (_existingSamplesInCurrentInterval == _intervalLength) { + + // record current interval's stats, then reset them + _intervalStats.insert(_currentIntervalStats); + _currentIntervalStats = Stats(); + _existingSamplesInCurrentInterval = 0; + + // update the window's stats by combining the intervals' stats + typename RingBufferHistory::Iterator i = _intervalStats.begin(); + typename RingBufferHistory::Iterator end = _intervalStats.end(); + _windowStats = Stats(); + int intervalsIncludedInWindowStats = 0; + while (i != end) { + _windowStats.updateWithOtherStats(*i, intervalsIncludedInWindowStats); + i++; + } + + _newStatsAvailable = true; + } + } + + bool getNewStatsAvailableFlag() const { return _newStatsAvailable; } + void clearNewStatsAvailableFlag() { _newStatsAvailable = false; } + + T getMin() const { return _overallStats._min; } + T getMax() const { return _overallStats._max; } + double getAverage() const { return _overallStats._average; } + T getWindowMin() const { return _windowStats._min; } + T getWindowMax() const { return _windowStats._max; } + double getWindowAverage() const { return _windowStats._average; } + +private: + int _intervalLength; + int _windowIntervals; + + // these are min/max/avg stats for all samples collected. + Stats _overallStats; + int _samplesCollected; + + // these are the min/max/avg stats for the samples in the moving window + Stats _windowStats; + int _existingSamplesInCurrentInterval; + + // these are the min/max/avg stats for the current interval + Stats _currentIntervalStats; + + // these are stored stats for the past intervals in the window + RingBufferHistory _intervalStats; + + bool _newStatsAvailable; +}; + +#endif // hifi_MovingMinMaxAvg_h diff --git a/libraries/shared/src/PerfStat.cpp b/libraries/shared/src/PerfStat.cpp index 4dca3f3d49..b811a719bc 100644 --- a/libraries/shared/src/PerfStat.cpp +++ b/libraries/shared/src/PerfStat.cpp @@ -17,6 +17,12 @@ #include "PerfStat.h" +#include "SharedUtil.h" + +// ---------------------------------------------------------------------------- +// PerformanceWarning +// ---------------------------------------------------------------------------- + // Static class members initialization here! bool PerformanceWarning::_suppressShortTimings = false; @@ -52,14 +58,50 @@ PerformanceWarning::~PerformanceWarning() { } }; +// ---------------------------------------------------------------------------- +// PerformanceTimerRecord +// ---------------------------------------------------------------------------- +const quint64 STALE_STAT_PERIOD = 4 * USECS_PER_SECOND; + +void PerformanceTimerRecord::tallyResult(const quint64& now) { + if (_numAccumulations > 0) { + _numTallies++; + _movingAverage.updateAverage(_runningTotal - _lastTotal); + _lastTotal = _runningTotal; + _numAccumulations = 0; + _expiry = now + STALE_STAT_PERIOD; + } +} + +// ---------------------------------------------------------------------------- +// PerformanceTimer +// ---------------------------------------------------------------------------- + +QString PerformanceTimer::_fullName; QMap PerformanceTimer::_records; PerformanceTimer::~PerformanceTimer() { - quint64 end = usecTimestampNow(); - quint64 elapsedusec = (end - _start); - PerformanceTimerRecord& namedRecord = _records[_name]; - namedRecord.recordResult(elapsedusec); + quint64 elapsedusec = (usecTimestampNow() - _start); + PerformanceTimerRecord& namedRecord = _records[_fullName]; + namedRecord.accumulateResult(elapsedusec); + _fullName.resize(_fullName.size() - (_name.size() + 1)); +} + +// static +void PerformanceTimer::tallyAllTimerRecords() { + QMap::iterator recordsItr = _records.begin(); + QMap::const_iterator recordsEnd = _records.end(); + quint64 now = usecTimestampNow(); + while (recordsItr != recordsEnd) { + recordsItr.value().tallyResult(now); + if (recordsItr.value().isStale(now)) { + // purge stale records + recordsItr = _records.erase(recordsItr); + } else { + ++recordsItr; + } + } } void PerformanceTimer::dumpAllTimerRecords() { diff --git a/libraries/shared/src/PerfStat.h b/libraries/shared/src/PerfStat.h index f849fb844c..4f94be73b1 100644 --- a/libraries/shared/src/PerfStat.h +++ b/libraries/shared/src/PerfStat.h @@ -25,13 +25,13 @@ class PerformanceWarning { private: - quint64 _start; - const char* _message; - bool _renderWarningsOn; - bool _alwaysDisplay; - quint64* _runningTotal; - quint64* _totalCalls; - static bool _suppressShortTimings; + quint64 _start; + const char* _message; + bool _renderWarningsOn; + bool _alwaysDisplay; + quint64* _runningTotal; + quint64* _totalCalls; + static bool _suppressShortTimings; public: PerformanceWarning(bool renderWarnings, const char* message, bool alwaysDisplay = false, @@ -52,38 +52,47 @@ public: class PerformanceTimerRecord { public: - PerformanceTimerRecord() : _runningTotal(0), _totalCalls(0) {} + PerformanceTimerRecord() : _runningTotal(0), _lastTotal(0), _numAccumulations(0), _numTallies(0), _expiry(0) {} - void recordResult(quint64 elapsed) { _runningTotal += elapsed; _totalCalls++; _movingAverage.updateAverage(elapsed); } - quint64 getAverage() const { return (_totalCalls == 0) ? 0 : _runningTotal / _totalCalls; } - quint64 getMovingAverage() const { return (_totalCalls == 0) ? 0 : _movingAverage.getAverage(); } - quint64 getCount() const { return _totalCalls; } + void accumulateResult(const quint64& elapsed) { _runningTotal += elapsed; ++_numAccumulations; } + void tallyResult(const quint64& now); + bool isStale(const quint64& now) const { return now > _expiry; } + quint64 getAverage() const { return (_numTallies == 0) ? 0 : _runningTotal / _numTallies; } + quint64 getMovingAverage() const { return (_numTallies == 0) ? 0 : _movingAverage.getAverage(); } + quint64 getCount() const { return _numTallies; } private: - quint64 _runningTotal; - quint64 _totalCalls; - SimpleMovingAverage _movingAverage; + quint64 _runningTotal; + quint64 _lastTotal; + quint64 _numAccumulations; + quint64 _numTallies; + quint64 _expiry; + SimpleMovingAverage _movingAverage; }; class PerformanceTimer { public: PerformanceTimer(const QString& name) : - _start(usecTimestampNow()), - _name(name) { } + _start(0), + _name(name) { + _fullName.append("/"); + _fullName.append(_name); + _start = usecTimestampNow(); + } - quint64 elapsed() const { return (usecTimestampNow() - _start); }; - ~PerformanceTimer(); static const PerformanceTimerRecord& getTimerRecord(const QString& name) { return _records[name]; }; static const QMap& getAllTimerRecords() { return _records; }; + static void tallyAllTimerRecords(); static void dumpAllTimerRecords(); private: - quint64 _start; - QString _name; - static QMap _records; + quint64 _start; + QString _name; + static QString _fullName; + static QMap _records; }; diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h new file mode 100644 index 0000000000..e9875ec38a --- /dev/null +++ b/libraries/shared/src/RingBufferHistory.h @@ -0,0 +1,122 @@ +// +// RingBufferHistory.h +// libraries/shared/src +// +// Created by Yixin Wang on 7/9/2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_RingBufferHistory_h +#define hifi_RingBufferHistory_h + +#include +#include + +#include + +template +class RingBufferHistory { + +public: + + RingBufferHistory(int capacity = 10) + : _size(capacity + 1), + _capacity(capacity), + _newestEntryAtIndex(0), + _numEntries(0), + _buffer(capacity + 1) + { + } + + void clear() { + _numEntries = 0; + } + + void insert(const T& entry) { + // increment newest entry index cyclically + _newestEntryAtIndex = (_newestEntryAtIndex == _size - 1) ? 0 : _newestEntryAtIndex + 1; + + // insert new entry + _buffer[_newestEntryAtIndex] = entry; + if (_numEntries < _capacity) { + _numEntries++; + } + } + + // 0 retrieves the most recent entry, _numEntries - 1 retrieves the oldest. + // returns NULL if entryAge not within [0, _numEntries-1] + const T* get(int entryAge) const { + if (!(entryAge >= 0 && entryAge < _numEntries)) { + return NULL; + } + int entryAt = _newestEntryAtIndex - entryAge; + if (entryAt < 0) { + entryAt += _size; + } + return &_buffer[entryAt]; + } + + T* get(int entryAge) { + return const_cast((static_cast(this))->get(entryAge)); + } + + const T* getNewestEntry() const { + return _numEntries == 0 ? NULL : &_buffer[_newestEntryAtIndex]; + } + + T* getNewestEntry() { + return _numEntries == 0 ? NULL : &_buffer[_newestEntryAtIndex]; + } + + int getCapacity() const { return _capacity; } + int getNumEntries() const { return _numEntries; } + +private: + int _size; + int _capacity; + int _newestEntryAtIndex; + int _numEntries; + QVector _buffer; + +public: + class Iterator : public std::iterator < std::forward_iterator_tag, T > { + public: + Iterator(T* bufferFirst, T* bufferLast, T* at) : _bufferFirst(bufferFirst), _bufferLast(bufferLast), _at(at) {} + + bool operator==(const Iterator& rhs) { return _at == rhs._at; } + bool operator!=(const Iterator& rhs) { return _at != rhs._at; } + T& operator*() { return *_at; } + T* operator->() { return _at; } + + Iterator& operator++() { + _at = (_at == _bufferFirst) ? _bufferLast : _at - 1; + return *this; + } + + Iterator operator++(int) { + Iterator tmp(*this); + ++(*this); + return tmp; + } + + private: + T* const _bufferFirst; + T* const _bufferLast; + T* _at; + }; + + Iterator begin() { return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[_newestEntryAtIndex]); } + + Iterator end() { + int endAtIndex = _newestEntryAtIndex - _numEntries; + if (endAtIndex < 0) { + endAtIndex += _size; + } + return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[endAtIndex]); + } +}; + +#endif // hifi_RingBufferHistory_h diff --git a/libraries/shared/src/SimpleMovingAverage.cpp b/libraries/shared/src/SimpleMovingAverage.cpp index 9f7e541c9a..64198d2a06 100644 --- a/libraries/shared/src/SimpleMovingAverage.cpp +++ b/libraries/shared/src/SimpleMovingAverage.cpp @@ -14,8 +14,8 @@ SimpleMovingAverage::SimpleMovingAverage(int numSamplesToAverage) : _numSamples(0), - _average(0), - _eventDeltaAverage(0), + _average(0.0f), + _eventDeltaAverage(0.0f), WEIGHTING(1.0f / numSamplesToAverage), ONE_MINUS_WEIGHTING(1 - WEIGHTING) { @@ -45,8 +45,8 @@ int SimpleMovingAverage::updateAverage(float sample) { void SimpleMovingAverage::reset() { _numSamples = 0; - _average = 0; - _eventDeltaAverage = 0; + _average = 0.0f; + _eventDeltaAverage = 0.0f; } float SimpleMovingAverage::getEventDeltaAverage() const { @@ -55,5 +55,5 @@ float SimpleMovingAverage::getEventDeltaAverage() const { } float SimpleMovingAverage::getAverageSampleValuePerSecond() const { - return _average * (1 / getEventDeltaAverage()); + return _average * (1.0f / getEventDeltaAverage()); } diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 61ab664310..0a6a5de96d 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -646,16 +646,24 @@ TestEndpoint::TestEndpoint(Mode mode) : Endpoint(SharedNodePointer(), new TestSendRecord(), new TestReceiveRecord()), _mode(mode), _highPriorityMessagesToSend(0.0f), - _reliableMessagesToSend(0.0f) { + _reliableMessagesToSend(0.0f), + _reliableDeltaChannel(NULL), + _reliableDeltaID(0) { connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleHighPriorityMessage(const QVariant&))); - + connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&, Bitstream&)), + SLOT(handleReliableMessage(const QVariant&, Bitstream&))); + if (mode == METAVOXEL_CLIENT_MODE) { _lod = MetavoxelLOD(glm::vec3(), 0.01f); + connect(_sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX), + SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleReliableMessage(const QVariant&, Bitstream&))); return; } if (mode == METAVOXEL_SERVER_MODE) { + connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(checkReliableDeltaReceived())); + _data.expand(); _data.expand(); @@ -673,9 +681,6 @@ TestEndpoint::TestEndpoint(Mode mode) : // create the object that represents out delta-encoded state _localState = new TestSharedObjectA(); - connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)), - SLOT(handleReliableMessage(const QVariant&))); - ReliableChannel* secondInput = _sequencer.getReliableInputChannel(1); secondInput->setMessagesEnabled(false); connect(&secondInput->getBuffer(), SIGNAL(readyRead()), SLOT(readReliableChannel())); @@ -854,7 +859,7 @@ bool TestEndpoint::simulate(int iterationNumber) { bytesReceived += datagram.size(); _remainingPipelineCapacity += datagram.size(); } - int packetCount = _sequencer.startPacketGroup(); + int packetCount = _sequencer.notePacketGroup(); groupsSent++; maxPacketsPerGroup = qMax(maxPacketsPerGroup, packetCount); for (int i = 0; i < packetCount; i++) { @@ -867,9 +872,6 @@ bool TestEndpoint::simulate(int iterationNumber) { maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent); maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent); - - // record the send - _sendRecords.append(maybeCreateSendRecord()); } return false; @@ -880,9 +882,6 @@ bool TestEndpoint::simulate(int iterationNumber) { out << QVariant::fromValue(state); _sequencer.endPacket(); - // record the send - _sendRecords.append(maybeCreateSendRecord()); - } else if (_mode == METAVOXEL_SERVER_MODE) { // make a random change MutateVisitor visitor; @@ -907,15 +906,41 @@ bool TestEndpoint::simulate(int iterationNumber) { if (!_lod.isValid()) { return false; } + // if we're sending a reliable delta, wait until it's acknowledged + if (_reliableDeltaChannel) { + Bitstream& out = _sequencer.startPacket(); + MetavoxelDeltaPendingMessage msg = { _reliableDeltaID }; + out << QVariant::fromValue(msg); + _sequencer.endPacket(); + return false; + } Bitstream& out = _sequencer.startPacket(); + int start = _sequencer.getOutputStream().getUnderlying().device()->pos(); out << QVariant::fromValue(MetavoxelDeltaMessage()); PacketRecord* sendRecord = getLastAcknowledgedSendRecord(); _data.writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod); - _sequencer.endPacket(); - - // record the send - _sendRecords.append(maybeCreateSendRecord()); - + out.flush(); + int end = _sequencer.getOutputStream().getUnderlying().device()->pos(); + if (end > _sequencer.getMaxPacketSize()) { + // we need to send the delta on the reliable channel + _reliableDeltaChannel = _sequencer.getReliableOutputChannel(RELIABLE_DELTA_CHANNEL_INDEX); + _reliableDeltaChannel->startMessage(); + _reliableDeltaChannel->getBuffer().write(_sequencer.getOutgoingPacketData().constData() + start, end - start); + _reliableDeltaChannel->endMessage(); + + _reliableDeltaWriteMappings = out.getAndResetWriteMappings(); + _reliableDeltaReceivedOffset = _reliableDeltaChannel->getBytesWritten(); + _reliableDeltaData = _data; + _reliableDeltaLOD = _lod; + + _sequencer.getOutputStream().getUnderlying().device()->seek(start); + MetavoxelDeltaPendingMessage msg = { ++_reliableDeltaID }; + out << QVariant::fromValue(msg); + _sequencer.endPacket(); + + } else { + _sequencer.endPacket(); + } } else { // enqueue some number of high priority messages const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f; @@ -957,9 +982,6 @@ bool TestEndpoint::simulate(int iterationNumber) { qDebug() << message; return true; } - - // record the send - _sendRecords.append(maybeCreateSendRecord()); } maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent); maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent); @@ -995,7 +1017,7 @@ void TestEndpoint::sendDatagram(const QByteArray& datagram) { // some are received out of order const float REORDER_PROBABILITY = 0.1f; if (randFloat() < REORDER_PROBABILITY * probabilityMultiplier) { - const int MIN_DELAY = 1; + const int MIN_DELAY = 2; const int MAX_DELAY = 5; // have to copy the datagram; the one we're passed is a reference to a shared buffer _delayedDatagrams.append(ByteArrayIntPair(QByteArray(datagram.constData(), datagram.size()), @@ -1008,58 +1030,32 @@ void TestEndpoint::sendDatagram(const QByteArray& datagram) { } } - _other->parseData(datagram); + _delayedDatagrams.append(ByteArrayIntPair(QByteArray(datagram.constData(), datagram.size()), 1)); } void TestEndpoint::readMessage(Bitstream& in) { if (_mode == CONGESTION_MODE) { QVariant message; in >> message; - - // record the receipt - _receiveRecords.append(maybeCreateReceiveRecord()); return; } if (_mode == METAVOXEL_CLIENT_MODE) { QVariant message; in >> message; handleMessage(message, in); - - // deep-compare data to sent version - int packetNumber = _sequencer.getIncomingPacketNumber(); - foreach (PacketRecord* record, _other->_sendRecords) { - TestSendRecord* sendRecord = static_cast(record); - if (sendRecord->getPacketNumber() == packetNumber) { - if (!sendRecord->getData().deepEquals(_data, getLastAcknowledgedSendRecord()->getLOD())) { - qDebug() << "Sent/received metavoxel data mismatch."; - exit(true); - } - break; - } - } - - // record the receipt - _receiveRecords.append(maybeCreateReceiveRecord()); return; } if (_mode == METAVOXEL_SERVER_MODE) { QVariant message; in >> message; handleMessage(message, in); - - // record the receipt - _receiveRecords.append(maybeCreateReceiveRecord()); return; } - SequencedTestMessage message; in >> message; _remoteState = message.state; - // record the receipt - _receiveRecords.append(maybeCreateReceiveRecord()); - for (QList::iterator it = _other->_unreliableMessagesSent.begin(); it != _other->_unreliableMessagesSent.end(); it++) { if (it->sequenceNumber == message.sequenceNumber) { @@ -1088,8 +1084,23 @@ void TestEndpoint::handleMessage(const QVariant& message, Bitstream& in) { } else if (userType == MetavoxelDeltaMessage::Type) { PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord(); - _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, getLastAcknowledgedSendRecord()->getLOD()); + _remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, + _remoteDataLOD = getLastAcknowledgedSendRecord()->getLOD()); + in.reset(); + _data = _remoteData; + compareMetavoxelData(); + } else if (userType == MetavoxelDeltaPendingMessage::Type) { + int id = message.value().id; + if (id > _reliableDeltaID) { + _reliableDeltaID = id; + _reliableDeltaChannel = _sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX); + _reliableDeltaChannel->getBitstream().copyPersistentMappings(_sequencer.getInputStream()); + _reliableDeltaLOD = getLastAcknowledgedSendRecord()->getLOD(); + PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord(); + _remoteDataLOD = receiveRecord->getLOD(); + _remoteData = receiveRecord->getData(); + } } else if (userType == QMetaType::QVariantList) { foreach (const QVariant& element, message.toList()) { handleMessage(element, in); @@ -1098,13 +1109,15 @@ void TestEndpoint::handleMessage(const QVariant& message, Bitstream& in) { } PacketRecord* TestEndpoint::maybeCreateSendRecord() const { + if (_reliableDeltaChannel) { + return new TestSendRecord(_reliableDeltaLOD, _reliableDeltaData, _localState, _sequencer.getOutgoingPacketNumber()); + } return new TestSendRecord(_lod, (_mode == METAVOXEL_CLIENT_MODE) ? MetavoxelData() : _data, _localState, _sequencer.getOutgoingPacketNumber()); } PacketRecord* TestEndpoint::maybeCreateReceiveRecord() const { - return new TestReceiveRecord(getLastAcknowledgedSendRecord()->getLOD(), - (_mode == METAVOXEL_SERVER_MODE) ? MetavoxelData() : _data, _remoteState); + return new TestReceiveRecord(_remoteDataLOD, _remoteData, _remoteState); } void TestEndpoint::handleHighPriorityMessage(const QVariant& message) { @@ -1121,7 +1134,17 @@ void TestEndpoint::handleHighPriorityMessage(const QVariant& message) { highPriorityMessagesReceived++; } -void TestEndpoint::handleReliableMessage(const QVariant& message) { +void TestEndpoint::handleReliableMessage(const QVariant& message, Bitstream& in) { + if (message.userType() == MetavoxelDeltaMessage::Type) { + PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord(); + _remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _remoteDataLOD = _reliableDeltaLOD); + _sequencer.getInputStream().persistReadMappings(in.getAndResetReadMappings()); + in.clearPersistentMappings(); + _data = _remoteData; + compareMetavoxelData(); + _reliableDeltaChannel = NULL; + return; + } if (message.userType() == ClearSharedObjectMessage::Type || message.userType() == ClearMainChannelSharedObjectMessage::Type) { return; @@ -1150,6 +1173,33 @@ void TestEndpoint::readReliableChannel() { streamedBytesReceived += bytes.size(); } +void TestEndpoint::checkReliableDeltaReceived() { + if (!_reliableDeltaChannel || _reliableDeltaChannel->getOffset() < _reliableDeltaReceivedOffset) { + return; + } + _sequencer.getOutputStream().persistWriteMappings(_reliableDeltaWriteMappings); + _reliableDeltaWriteMappings = Bitstream::WriteMappings(); + _reliableDeltaData = MetavoxelData(); + _reliableDeltaChannel = NULL; +} + +void TestEndpoint::compareMetavoxelData() { + // deep-compare data to sent version + int packetNumber = _sequencer.getIncomingPacketNumber(); + foreach (PacketRecord* record, _other->_sendRecords) { + TestSendRecord* sendRecord = static_cast(record); + if (sendRecord->getPacketNumber() == packetNumber) { + if (!sendRecord->getData().deepEquals(_data, getLastAcknowledgedSendRecord()->getLOD())) { + qDebug() << "Sent/received metavoxel data mismatch."; + exit(true); + } + return; + } + } + qDebug() << "Received metavoxel data with no corresponding send." << packetNumber; + exit(true); +} + TestSharedObjectA::TestSharedObjectA(float foo, TestEnum baz, TestFlags bong) : _foo(foo), _baz(baz), diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index 476a8c6295..ce357fbb90 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -64,17 +64,23 @@ protected: private slots: void handleHighPriorityMessage(const QVariant& message); - void handleReliableMessage(const QVariant& message); + void handleReliableMessage(const QVariant& message, Bitstream& in); void readReliableChannel(); + void checkReliableDeltaReceived(); private: + void compareMetavoxelData(); + Mode _mode; SharedObjectPointer _localState; SharedObjectPointer _remoteState; MetavoxelData _data; + MetavoxelLOD _dataLOD; + MetavoxelData _remoteData; + MetavoxelLOD _remoteDataLOD; MetavoxelLOD _lod; SharedObjectPointer _sphere; @@ -94,6 +100,13 @@ private: float _reliableMessagesToSend; QVariantList _reliableMessagesSent; CircularBuffer _dataStreamed; + + ReliableChannel* _reliableDeltaChannel; + int _reliableDeltaReceivedOffset; + MetavoxelData _reliableDeltaData; + MetavoxelLOD _reliableDeltaLOD; + Bitstream::WriteMappings _reliableDeltaWriteMappings; + int _reliableDeltaID; }; /// A simple shared object. diff --git a/tests/shared/src/MovingMinMaxAvgTests.cpp b/tests/shared/src/MovingMinMaxAvgTests.cpp new file mode 100644 index 0000000000..108db82e35 --- /dev/null +++ b/tests/shared/src/MovingMinMaxAvgTests.cpp @@ -0,0 +1,218 @@ +// +// MovingMinMaxAvgTests.cpp +// tests/shared/src +// +// Created by Yixin Wang on 7/8/2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MovingMinMaxAvgTests.h" +#include + +quint64 MovingMinMaxAvgTests::randQuint64() { + quint64 ret = 0; + for (int i = 0; i < 32; i++) { + ret = (ret + rand() % 4); + ret *= 4; + } + return ret; +} + +void MovingMinMaxAvgTests::runAllTests() { + { + // quint64 test + + const int INTERVAL_LENGTH = 100; + const int WINDOW_INTERVALS = 50; + + MovingMinMaxAvg stats(INTERVAL_LENGTH, WINDOW_INTERVALS); + + quint64 min = std::numeric_limits::max(); + quint64 max = 0; + double average = 0.0; + int totalSamples = 0; + + quint64 windowMin; + quint64 windowMax; + double windowAverage; + + QQueue windowSamples; + // fill window samples + for (int i = 0; i < 100000; i++) { + + quint64 sample = randQuint64(); + + windowSamples.enqueue(sample); + if (windowSamples.size() > INTERVAL_LENGTH * WINDOW_INTERVALS) { + windowSamples.dequeue(); + } + + stats.update(sample); + + min = std::min(min, sample); + max = std::max(max, sample); + average = (average * totalSamples + sample) / (totalSamples + 1); + totalSamples++; + + assert(stats.getMin() == min); + assert(stats.getMax() == max); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001 || abs(stats.getAverage() - average) < 0.000001); + + if ((i + 1) % INTERVAL_LENGTH == 0) { + + assert(stats.getNewStatsAvailableFlag()); + stats.clearNewStatsAvailableFlag(); + + windowMin = std::numeric_limits::max(); + windowMax = 0; + windowAverage = 0.0; + foreach(quint64 s, windowSamples) { + windowMin = std::min(windowMin, s); + windowMax = std::max(windowMax, s); + windowAverage += (double)s; + } + windowAverage /= (double)windowSamples.size(); + + assert(stats.getWindowMin() == windowMin); + assert(stats.getWindowMax() == windowMax); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001 || abs(stats.getAverage() - average) < 0.000001); + + } else { + assert(!stats.getNewStatsAvailableFlag()); + } + } + } + + { + // int test + + const int INTERVAL_LENGTH = 1; + const int WINDOW_INTERVALS = 75; + + MovingMinMaxAvg stats(INTERVAL_LENGTH, WINDOW_INTERVALS); + + int min = std::numeric_limits::max(); + int max = 0; + double average = 0.0; + int totalSamples = 0; + + int windowMin; + int windowMax; + double windowAverage; + + QQueue windowSamples; + // fill window samples + for (int i = 0; i < 100000; i++) { + + int sample = rand(); + + windowSamples.enqueue(sample); + if (windowSamples.size() > INTERVAL_LENGTH * WINDOW_INTERVALS) { + windowSamples.dequeue(); + } + + stats.update(sample); + + min = std::min(min, sample); + max = std::max(max, sample); + average = (average * totalSamples + sample) / (totalSamples + 1); + totalSamples++; + + assert(stats.getMin() == min); + assert(stats.getMax() == max); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + + if ((i + 1) % INTERVAL_LENGTH == 0) { + + assert(stats.getNewStatsAvailableFlag()); + stats.clearNewStatsAvailableFlag(); + + windowMin = std::numeric_limits::max(); + windowMax = 0; + windowAverage = 0.0; + foreach(int s, windowSamples) { + windowMin = std::min(windowMin, s); + windowMax = std::max(windowMax, s); + windowAverage += (double)s; + } + windowAverage /= (double)windowSamples.size(); + + assert(stats.getWindowMin() == windowMin); + assert(stats.getWindowMax() == windowMax); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + + } else { + assert(!stats.getNewStatsAvailableFlag()); + } + } + } + + { + // float test + + const int INTERVAL_LENGTH = 57; + const int WINDOW_INTERVALS = 1; + + MovingMinMaxAvg stats(INTERVAL_LENGTH, WINDOW_INTERVALS); + + float min = std::numeric_limits::max(); + float max = 0; + double average = 0.0; + int totalSamples = 0; + + float windowMin; + float windowMax; + double windowAverage; + + QQueue windowSamples; + // fill window samples + for (int i = 0; i < 100000; i++) { + + float sample = randFloat(); + + windowSamples.enqueue(sample); + if (windowSamples.size() > INTERVAL_LENGTH * WINDOW_INTERVALS) { + windowSamples.dequeue(); + } + + stats.update(sample); + + min = std::min(min, sample); + max = std::max(max, sample); + average = (average * totalSamples + sample) / (totalSamples + 1); + totalSamples++; + + assert(stats.getMin() == min); + assert(stats.getMax() == max); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + + if ((i + 1) % INTERVAL_LENGTH == 0) { + + assert(stats.getNewStatsAvailableFlag()); + stats.clearNewStatsAvailableFlag(); + + windowMin = std::numeric_limits::max(); + windowMax = 0; + windowAverage = 0.0; + foreach(float s, windowSamples) { + windowMin = std::min(windowMin, s); + windowMax = std::max(windowMax, s); + windowAverage += (double)s; + } + windowAverage /= (double)windowSamples.size(); + + assert(stats.getWindowMin() == windowMin); + assert(stats.getWindowMax() == windowMax); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + + } else { + assert(!stats.getNewStatsAvailableFlag()); + } + } + } + printf("moving min/max/avg test passed!\n"); +} + diff --git a/tests/shared/src/MovingMinMaxAvgTests.h b/tests/shared/src/MovingMinMaxAvgTests.h new file mode 100644 index 0000000000..52a2edf0af --- /dev/null +++ b/tests/shared/src/MovingMinMaxAvgTests.h @@ -0,0 +1,25 @@ +// +// MovingMinMaxAvgTests.h +// tests/shared/src +// +// Created by Yixin Wang on 7/8/2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_MovingMinMaxAvgTests_h +#define hifi_MovingMinMaxAvgTests_h + +#include "MovingMinMaxAvg.h" +#include "SharedUtil.h" + +namespace MovingMinMaxAvgTests { + + quint64 randQuint64(); + + void runAllTests(); +} + +#endif // hifi_MovingMinMaxAvgTests_h diff --git a/tests/shared/src/main.cpp b/tests/shared/src/main.cpp index 6215d394a8..d4251eef7a 100644 --- a/tests/shared/src/main.cpp +++ b/tests/shared/src/main.cpp @@ -10,9 +10,12 @@ #include "AngularConstraintTests.h" #include "MovingPercentileTests.h" +#include "MovingMinMaxAvgTests.h" int main(int argc, char** argv) { + MovingMinMaxAvgTests::runAllTests(); MovingPercentileTests::runAllTests(); AngularConstraintTests::runAllTests(); + getchar(); return 0; }