From 2e2d03c58b502d4731a8bb850b224d1033d05fe6 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Thu, 26 Jun 2014 16:53:19 -0700 Subject: [PATCH 001/135] added ESC to restore to original camera in concertCamera.js --- examples/concertCamera.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/concertCamera.js b/examples/concertCamera.js index 7280958bc6..96218e697f 100644 --- a/examples/concertCamera.js +++ b/examples/concertCamera.js @@ -56,6 +56,11 @@ function keyPressEvent(event) { 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(); From f072c04b4cf6ca211b47d4c8e7906055aecd0471 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Fri, 27 Jun 2014 09:30:51 -0700 Subject: [PATCH 002/135] hair is a separate class --- interface/src/Hair.cpp | 35 +++++++++++++++++++++++++++++++ interface/src/Hair.h | 35 +++++++++++++++++++++++++++++++ interface/src/avatar/Avatar.cpp | 8 +++++++ interface/src/avatar/Avatar.h | 2 ++ interface/src/avatar/MyAvatar.cpp | 6 ++++++ 5 files changed, 86 insertions(+) create mode 100644 interface/src/Hair.cpp create mode 100644 interface/src/Hair.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/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 5a294bb2a5..769d0398a2 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -143,6 +143,11 @@ void Avatar::simulate(float deltaTime) { if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { simulateHair(deltaTime); } + + foreach (Hair* hair, _hairs) { + hair->simulate(deltaTime); + } + } // update position by velocity, and subtract the change added earlier for gravity @@ -380,6 +385,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(); + } } } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index f20db1019d..369cd7e688 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" @@ -159,6 +160,7 @@ signals: void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision); protected: + QVector _hairs; SkeletonModel _skeletonModel; QVector _attachmentModels; float _bodyYawDelta; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7367f64d73..cfdf7e057a 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -199,6 +199,9 @@ void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("MyAvatar::simulate/hair Simulate"); if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { simulateHair(deltaTime); + foreach (Hair* hair, _hairs) { + hair->simulate(deltaTime); + } } } @@ -860,6 +863,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); From 370540cc339423a176c2bc8597910641954b22d7 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Sat, 28 Jun 2014 13:00:51 -0700 Subject: [PATCH 003/135] squeezeHands uses different animation on clench/release --- examples/squeezeHands.js | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) 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); From cda88bf4afcc4dc2b21fc89b2c672a50f7b30e57 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 30 Jun 2014 12:06:32 -0700 Subject: [PATCH 004/135] Add backgroundColor to TextOverlay --- interface/src/ui/overlays/TextOverlay.cpp | 14 +++++++++++++- interface/src/ui/overlays/TextOverlay.h | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index 797d0be1a2..5fc98810be 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -33,7 +33,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 +82,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 78a037762e..6de415dcc7 100644 --- a/interface/src/ui/overlays/TextOverlay.h +++ b/interface/src/ui/overlays/TextOverlay.h @@ -55,6 +55,7 @@ public: private: QString _text; + xColor _backgroundColor; int _leftMargin; int _topMargin; int _fontSize; From 9ae65e443999d7fa9e1bb9848b413fd966c32f37 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 30 Jun 2014 12:06:50 -0700 Subject: [PATCH 005/135] Update locationsMenu.js to use backgroundColor --- examples/locationsMenu.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) 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; From 123773a8e384e56e52c120ab78fa4efced831ab1 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 30 Jun 2014 12:20:05 -0700 Subject: [PATCH 006/135] Add default background color and initialize property --- interface/src/ui/overlays/Overlay.cpp | 2 +- interface/src/ui/overlays/Overlay.h | 2 +- interface/src/ui/overlays/TextOverlay.cpp | 1 + interface/src/ui/overlays/TextOverlay.h | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) 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 5fc98810be..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) diff --git a/interface/src/ui/overlays/TextOverlay.h b/interface/src/ui/overlays/TextOverlay.h index 6de415dcc7..6484b6a38f 100644 --- a/interface/src/ui/overlays/TextOverlay.h +++ b/interface/src/ui/overlays/TextOverlay.h @@ -28,6 +28,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; From 154eb0433635fcf84a5748ef136fb2f5f359bb62 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 2 Jul 2014 17:13:39 -0700 Subject: [PATCH 007/135] Working on sending large deltas as reliable messages. --- .../src/metavoxels/MetavoxelServer.cpp | 4 +- libraries/metavoxels/src/Bitstream.cpp | 20 ++- libraries/metavoxels/src/Bitstream.h | 20 +++ .../metavoxels/src/DatagramSequencer.cpp | 32 +++-- libraries/metavoxels/src/DatagramSequencer.h | 29 +++- libraries/metavoxels/src/Endpoint.cpp | 30 +++-- libraries/metavoxels/src/Endpoint.h | 5 +- libraries/metavoxels/src/MetavoxelData.cpp | 14 ++ libraries/metavoxels/src/MetavoxelData.h | 7 + tests/metavoxels/src/MetavoxelTests.cpp | 124 ++++++++++-------- tests/metavoxels/src/MetavoxelTests.h | 8 +- 11 files changed, 204 insertions(+), 89 deletions(-) diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index d0c0d4c781..e7e06c96d0 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -94,8 +94,8 @@ MetavoxelSession::MetavoxelSession(const SharedNodePointer& node, MetavoxelServe _server(server) { connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleMessage(const QVariant&))); - connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)), - SLOT(handleMessage(const QVariant&))); + connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&, Bitstream&)), + SLOT(handleMessage(const QVariant&, Bitstream&))); } void MetavoxelSession::update() { diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index d18903f923..4a86344c8c 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -127,6 +127,7 @@ Bitstream::Bitstream(QDataStream& underlying, MetadataType metadataType, Generic _underlying(underlying), _byte(0), _position(0), + _bytesRemaining(INT_MAX), _metadataType(metadataType), _genericsMode(genericsMode), _objectStreamerStreamer(*this), @@ -193,13 +194,16 @@ Bitstream& Bitstream::read(void* data, int bits, int offset) { void Bitstream::flush() { if (_position != 0) { _underlying << _byte; - reset(); + _bytesRemaining--; + _byte = 0; + _position = 0; } } void Bitstream::reset() { _byte = 0; _position = 0; + _bytesRemaining = INT_MAX; } Bitstream::WriteMappings Bitstream::getAndResetWriteMappings() { @@ -1122,7 +1126,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 +1236,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 +1244,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 +1445,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 +1455,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 +1686,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..d900b34847 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -329,6 +329,12 @@ public: /// Resets to the initial state. void reset(); + /// Sets the number of "bytes remaining," which will be decremented with each byte written. + void setBytesRemaining(int bytesRemaining) { _bytesRemaining = bytesRemaining; } + + /// Returns the number of bytes remaining. + int getBytesRemaining() const { return _bytesRemaining; } + /// Returns the set of transient mappings gathered during writing and resets them. WriteMappings getAndResetWriteMappings(); @@ -508,6 +514,7 @@ private: QDataStream& _underlying; quint8 _byte; int _position; + int _bytesRemaining; MetadataType _metadataType; GenericsMode _genericsMode; @@ -823,6 +830,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..3b16a829e6 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -253,6 +253,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) { @@ -364,6 +366,8 @@ void DatagramSequencer::sendPacket(const QByteArray& packet, const QVector().id); @@ -700,7 +712,7 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o _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&))); } void ReliableChannel::writeData(QDataStream& out, int bytes, QVector& spans) { @@ -843,9 +855,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..09d2f834ef 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -126,9 +126,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); @@ -336,22 +342,36 @@ 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); 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: @@ -381,6 +401,7 @@ private: int _writePositionResetPacketNumber; SpanList _acknowledged; bool _messagesEnabled; + int _messageLengthPlaceholder; }; #endif // hifi_DatagramSequencer_h diff --git a/libraries/metavoxels/src/Endpoint.cpp b/libraries/metavoxels/src/Endpoint.cpp index c656054504..666ffe52d9 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))); @@ -40,9 +42,6 @@ void Endpoint::update() { Bitstream& out = _sequencer.startPacket(); writeUpdateMessage(out); _sequencer.endPacket(); - - // record the send - _sendRecords.append(maybeCreateSendRecord()); } int Endpoint::parseData(const QByteArray& packet) { @@ -59,8 +58,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 +96,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..b1f468531b 100644 --- a/libraries/metavoxels/src/Endpoint.h +++ b/libraries/metavoxels/src/Endpoint.h @@ -37,6 +37,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 +48,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/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 2d61ede796..43206588cc 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -681,6 +681,12 @@ void MetavoxelStreamState::setMinimum(const glm::vec3& lastMinimum, int index) { minimum = getNextMinimum(lastMinimum, size, index); } +void MetavoxelStreamState::checkByteLimitExceeded() { + if (stream.getBytesRemaining() < 0) { + throw ByteLimitExceededException(); + } +} + MetavoxelNode::MetavoxelNode(const AttributeValue& attributeValue, const MetavoxelNode* copyChildren) : _referenceCount(1) { @@ -772,11 +778,13 @@ void MetavoxelNode::read(MetavoxelStreamState& state) { void MetavoxelNode::write(MetavoxelStreamState& state) const { if (!state.shouldSubdivide()) { state.attribute->write(state.stream, _attributeValue, true); + state.checkByteLimitExceeded(); return; } bool leaf = isLeaf(); state.stream << leaf; state.attribute->write(state.stream, _attributeValue, leaf); + state.checkByteLimitExceeded(); if (!leaf) { MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute, state.stream, state.lod, state.referenceLOD }; @@ -830,11 +838,13 @@ void MetavoxelNode::readDelta(const MetavoxelNode& reference, MetavoxelStreamSta void MetavoxelNode::writeDelta(const MetavoxelNode& reference, MetavoxelStreamState& state) const { if (!state.shouldSubdivide()) { state.attribute->writeDelta(state.stream, _attributeValue, reference._attributeValue, true); + state.checkByteLimitExceeded(); return; } bool leaf = isLeaf(); state.stream << leaf; state.attribute->writeDelta(state.stream, _attributeValue, reference._attributeValue, leaf); + state.checkByteLimitExceeded(); if (!leaf) { MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute, state.stream, state.lod, state.referenceLOD }; @@ -897,6 +907,7 @@ void MetavoxelNode::writeSubdivision(MetavoxelStreamState& state) const { bool subdivideReference = state.shouldSubdivideReference(); if (!subdivideReference) { state.stream << leaf; + state.checkByteLimitExceeded(); } if (!leaf) { MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute, @@ -921,6 +932,7 @@ void MetavoxelNode::writeSpanners(MetavoxelStreamState& state) const { foreach (const SharedObjectPointer& object, decodeInline(_attributeValue)) { if (static_cast(object.data())->testAndSetVisited()) { state.stream << object; + state.checkByteLimitExceeded(); } } if (!state.shouldSubdivide() || isLeaf()) { @@ -940,11 +952,13 @@ void MetavoxelNode::writeSpannerDelta(const MetavoxelNode& reference, MetavoxelS foreach (const SharedObjectPointer& object, oldSet) { if (static_cast(object.data())->testAndSetVisited() && !newSet.contains(object)) { state.stream << object; + state.checkByteLimitExceeded(); } } foreach (const SharedObjectPointer& object, newSet) { if (static_cast(object.data())->testAndSetVisited() && !oldSet.contains(object)) { state.stream << object; + state.checkByteLimitExceeded(); } } if (isLeaf() || !state.shouldSubdivide()) { diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 6a7ba33eb5..a0b0b6ef47 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -164,6 +164,13 @@ public: bool becameSubdivided() const; void setMinimum(const glm::vec3& lastMinimum, int index); + + /// Throws ByteLimitExceededException if the stream has fewer than zero bytes remaining. + void checkByteLimitExceeded(); +}; + +/// Thrown when we have exceeded the byte limit in writing. +class ByteLimitExceededException { }; /// A single node within a metavoxel layer. diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 61ab664310..5096f2b733 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -646,11 +646,14 @@ TestEndpoint::TestEndpoint(Mode mode) : Endpoint(SharedNodePointer(), new TestSendRecord(), new TestReceiveRecord()), _mode(mode), _highPriorityMessagesToSend(0.0f), - _reliableMessagesToSend(0.0f) { + _reliableMessagesToSend(0.0f), + _reliableDeltaReceivedOffset(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); return; @@ -663,19 +666,16 @@ TestEndpoint::TestEndpoint(Mode mode) : _data.guide(visitor); qDebug() << "Created" << visitor.leafCount << "base leaves"; - _data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), new Sphere()); + //_data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), new Sphere()); _sphere = new Sphere(); static_cast(_sphere.data())->setScale(0.01f); - _data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), _sphere); + //_data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), _sphere); return; } // 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())); @@ -867,9 +867,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 +877,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; @@ -899,7 +893,7 @@ bool TestEndpoint::simulate(int iterationNumber) { newSphere->setTranslation(newSphere->getTranslation() + glm::vec3(randFloatInRange(-0.01f, 0.01f), randFloatInRange(-0.01f, 0.01f), randFloatInRange(-0.01f, 0.01f))); } - _data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), oldSphere, _sphere); + //_data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), oldSphere, _sphere); spannerMutationsPerformed++; } @@ -907,15 +901,43 @@ bool TestEndpoint::simulate(int iterationNumber) { if (!_lod.isValid()) { return false; } + // if we're sending a reliable delta, wait until it's acknowledged + if (_reliableDeltaReceivedOffset > 0) { + if (_sequencer.getReliableOutputChannel()->getOffset() < _reliableDeltaReceivedOffset) { + Bitstream& out = _sequencer.startPacket(); + out << QVariant(); + _sequencer.endPacket(); + return false; + } + _reliableDeltaReceivedOffset = 0; + _reliableDeltaData = MetavoxelData(); + } Bitstream& out = _sequencer.startPacket(); out << QVariant::fromValue(MetavoxelDeltaMessage()); PacketRecord* sendRecord = getLastAcknowledgedSendRecord(); - _data.writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod); - _sequencer.endPacket(); - - // record the send - _sendRecords.append(maybeCreateSendRecord()); - + out.setBytesRemaining(_sequencer.getMaxPacketSize()); + try { + _data.writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod); + _sequencer.endPacket(); + + } catch (const ByteLimitExceededException& exception) { + _sequencer.cancelPacket(); + + // we need to send the delta on the reliable channel + ReliableChannel* channel = _sequencer.getReliableOutputChannel(); + channel->startMessage(); + channel->getBitstream() << QVariant::fromValue(MetavoxelDeltaMessage()); + _data.writeDelta(sendRecord->getData(), sendRecord->getLOD(), channel->getBitstream(), _lod); + channel->endMessage(); + + _reliableDeltaReceivedOffset = channel->getBytesWritten(); + _reliableDeltaData = _data; + _reliableDeltaLOD = _lod; + + Bitstream& out = _sequencer.startPacket(); + out << QVariant(); + _sequencer.endPacket(); + } } else { // enqueue some number of high priority messages const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f; @@ -957,9 +979,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 +1014,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 +1027,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) { @@ -1089,6 +1082,7 @@ 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()); + compareMetavoxelData(); } else if (userType == QMetaType::QVariantList) { foreach (const QVariant& element, message.toList()) { @@ -1098,6 +1092,9 @@ void TestEndpoint::handleMessage(const QVariant& message, Bitstream& in) { } PacketRecord* TestEndpoint::maybeCreateSendRecord() const { + if (_reliableDeltaReceivedOffset > 0) { + return new TestSendRecord(_reliableDeltaLOD, _reliableDeltaData, _localState, _sequencer.getOutgoingPacketNumber()); + } return new TestSendRecord(_lod, (_mode == METAVOXEL_CLIENT_MODE) ? MetavoxelData() : _data, _localState, _sequencer.getOutgoingPacketNumber()); } @@ -1121,7 +1118,13 @@ 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(); + _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, getLastAcknowledgedSendRecord()->getLOD()); + compareMetavoxelData(); + return; + } if (message.userType() == ClearSharedObjectMessage::Type || message.userType() == ClearMainChannelSharedObjectMessage::Type) { return; @@ -1150,6 +1153,23 @@ void TestEndpoint::readReliableChannel() { streamedBytesReceived += bytes.size(); } +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..e946dfddaf 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -64,11 +64,13 @@ protected: private slots: void handleHighPriorityMessage(const QVariant& message); - void handleReliableMessage(const QVariant& message); + void handleReliableMessage(const QVariant& message, Bitstream& in); void readReliableChannel(); private: + void compareMetavoxelData(); + Mode _mode; SharedObjectPointer _localState; @@ -94,6 +96,10 @@ private: float _reliableMessagesToSend; QVariantList _reliableMessagesSent; CircularBuffer _dataStreamed; + + int _reliableDeltaReceivedOffset; + MetavoxelData _reliableDeltaData; + MetavoxelLOD _reliableDeltaLOD; }; /// A simple shared object. From b6a626c92893753a628746aa3bce034f27e279cb Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Wed, 2 Jul 2014 17:18:12 -0700 Subject: [PATCH 008/135] Removed comment --- interface/src/ui/ApplicationOverlay.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 44a83e164a..c423a967f2 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -335,7 +335,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); From 282c62a083610238e4203b3a81f270e795c01fd4 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 2 Jul 2014 18:49:40 -0700 Subject: [PATCH 009/135] Handle LODs correctly (?) for reliable deltas. --- libraries/metavoxels/src/MetavoxelMessages.h | 7 +++++++ tests/metavoxels/src/MetavoxelTests.cpp | 21 +++++++++++++------- tests/metavoxels/src/MetavoxelTests.h | 2 ++ 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index 8f819fe3d8..b822f1c561 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -61,6 +61,13 @@ class MetavoxelDeltaMessage { DECLARE_STREAMABLE_METATYPE(MetavoxelDeltaMessage) +/// A message indicating that metavoxel delta information is being sent on a reliable channel. +class MetavoxelDeltaPendingMessage { + STREAMABLE +}; + +DECLARE_STREAMABLE_METATYPE(MetavoxelDeltaPendingMessage) + /// A simple streamable edit. class MetavoxelEditMessage { STREAMABLE diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 5096f2b733..7c278f3c2f 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -647,7 +647,8 @@ TestEndpoint::TestEndpoint(Mode mode) : _mode(mode), _highPriorityMessagesToSend(0.0f), _reliableMessagesToSend(0.0f), - _reliableDeltaReceivedOffset(0) { + _reliableDeltaReceivedOffset(0), + _reliableDeltaPending(false) { connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleHighPriorityMessage(const QVariant&))); @@ -905,7 +906,7 @@ bool TestEndpoint::simulate(int iterationNumber) { if (_reliableDeltaReceivedOffset > 0) { if (_sequencer.getReliableOutputChannel()->getOffset() < _reliableDeltaReceivedOffset) { Bitstream& out = _sequencer.startPacket(); - out << QVariant(); + out << QVariant::fromValue(MetavoxelDeltaPendingMessage()); _sequencer.endPacket(); return false; } @@ -935,7 +936,7 @@ bool TestEndpoint::simulate(int iterationNumber) { _reliableDeltaLOD = _lod; Bitstream& out = _sequencer.startPacket(); - out << QVariant(); + out << QVariant::fromValue(MetavoxelDeltaPendingMessage()); _sequencer.endPacket(); } } else { @@ -1081,9 +1082,15 @@ 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()); + _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, + _dataLOD = getLastAcknowledgedSendRecord()->getLOD()); compareMetavoxelData(); + } else if (userType == MetavoxelDeltaPendingMessage::Type) { + if (!_reliableDeltaPending) { + _reliableDeltaLOD = getLastAcknowledgedSendRecord()->getLOD(); + _reliableDeltaPending = true; + } } else if (userType == QMetaType::QVariantList) { foreach (const QVariant& element, message.toList()) { handleMessage(element, in); @@ -1100,8 +1107,7 @@ PacketRecord* TestEndpoint::maybeCreateSendRecord() const { } PacketRecord* TestEndpoint::maybeCreateReceiveRecord() const { - return new TestReceiveRecord(getLastAcknowledgedSendRecord()->getLOD(), - (_mode == METAVOXEL_SERVER_MODE) ? MetavoxelData() : _data, _remoteState); + return new TestReceiveRecord(_dataLOD, (_mode == METAVOXEL_SERVER_MODE) ? MetavoxelData() : _data, _remoteState); } void TestEndpoint::handleHighPriorityMessage(const QVariant& message) { @@ -1121,8 +1127,9 @@ void TestEndpoint::handleHighPriorityMessage(const QVariant& message) { void TestEndpoint::handleReliableMessage(const QVariant& message, Bitstream& in) { if (message.userType() == MetavoxelDeltaMessage::Type) { PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord(); - _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, getLastAcknowledgedSendRecord()->getLOD()); + _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _dataLOD = _reliableDeltaLOD); compareMetavoxelData(); + _reliableDeltaPending = false; return; } if (message.userType() == ClearSharedObjectMessage::Type || diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index e946dfddaf..f451b5e8b6 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -77,6 +77,7 @@ private: SharedObjectPointer _remoteState; MetavoxelData _data; + MetavoxelLOD _dataLOD; MetavoxelLOD _lod; SharedObjectPointer _sphere; @@ -100,6 +101,7 @@ private: int _reliableDeltaReceivedOffset; MetavoxelData _reliableDeltaData; MetavoxelLOD _reliableDeltaLOD; + bool _reliableDeltaPending; }; /// A simple shared object. From bf2959675fd537ffcbfee9acfa19985baf9395a5 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 3 Jul 2014 16:09:54 -0700 Subject: [PATCH 010/135] First pass at laser oculus UI pointer --- examples/editModels.js | 3 +- examples/flockingBirds.js | 6 +- interface/src/Application.cpp | 5 + interface/src/devices/OculusManager.cpp | 28 +++++ interface/src/devices/OculusManager.h | 5 + interface/src/ui/ApplicationOverlay.cpp | 158 +++++++++++++++++------- 6 files changed, 153 insertions(+), 52 deletions(-) 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/interface/src/Application.cpp b/interface/src/Application.cpp index 7cfadc1ff2..c0f952d604 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2778,6 +2778,11 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { bool mirrorMode = (whichCamera.getInterpolatedMode() == CAMERA_MODE_MIRROR); { PerformanceTimer perfTimer("paintGL/displaySide/renderAvatars"); + + if (1 || OculusManager::isConnected()) { + OculusManager::renderLaserPointer(); + } + _avatarManager.renderAvatars(mirrorMode ? Avatar::MIRROR_RENDER_MODE : Avatar::NORMAL_RENDER_MODE, selfAvatarOnly); } diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index e156e148ab..71073d2647 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -455,3 +455,31 @@ QSize OculusManager::getRenderTargetSize() { #endif } +void OculusManager::renderLaserPointer() { +#ifdef HAVE_LIBOVR + const float PALM_TIP_ROD_RADIUS = 0.009f; + + MyAvatar* myAvatar = Application::getInstance()->getAvatar(); + + //If the Oculus is enabled, we will draw a blue cursor ray + + // Draw the palm ball and disk + 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(); + Avatar::renderJointConnectingCone(root, tip, PALM_TIP_ROD_RADIUS, PALM_TIP_ROD_RADIUS); + } + } +#endif +} + +glm::vec3 OculusManager::getLaserPointerTipPosition(const PalmData* palm) { +#ifdef HAVE_LIBOVR + const float PALM_TIP_ROD_LENGTH_MULT = 2.0f; + return (palm->getTipPosition() - palm->getPosition()) * PALM_TIP_ROD_LENGTH_MULT; +#endif + return glm::vec3(0.0f); +} \ No newline at end of file diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h index 7798875c2c..73ce6ef2d3 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 renderLaserPointer(); + static glm::vec3 getLaserPointerTipPosition(const PalmData* palm); private: #ifdef HAVE_LIBOVR diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index c423a967f2..f63a302cde 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -277,7 +277,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { glColor4f(1.0f, 1.0f, 1.0f, _alpha); renderTexturedHemisphere(); - + renderControllerPointersOculus(); glPopMatrix(); @@ -560,7 +560,26 @@ void ApplicationOverlay::renderControllerPointers() { } } +bool findSphereIntersection(const glm::vec3 &start, const glm::vec3 &end, const glm::vec3 &spos, const float r, glm::vec3 &result) { + double a = pow(end.x - start.x, 2) + pow(end.y - start.y, 2) + pow(end.z - start.z, 2); + double b = 2.0f * ((end.x - start.x) * (start.x - spos.x) + (end.y - start.x) * (start.y - spos.y) + (end.z - start.z) * (start.z - spos.z)); + double c = pow(start.x - spos.x, 2) + pow(start.y - spos.y, 2) + pow(start.z - spos.z, 2) - r*r; + + double delta = b * b - 4.0 * a * c; + printf("Intersection Delta %lf\n", delta); + + if (delta == 0) { + float d = -b / (2.0 * a); + result = start + d * (end - start); + } else { + return false; + } +} + void ApplicationOverlay::renderControllerPointersOculus() { + + const bool useLaser = true; + Application* application = Application::getInstance(); QGLWidget* glWidget = application->getGLWidget(); @@ -573,54 +592,99 @@ void ApplicationOverlay::renderControllerPointersOculus() { glDisable(GL_DEPTH_TEST); for (int i = 0; i < NUMBER_OF_MAGNIFIERS; i++) { + if (i > 0 && useLaser) { + MyAvatar* myAvatar = application->getAvatar(); + PalmData& palm = myAvatar->getHand()->getPalms()[i]; + if (palm.isActive()) { + + glm::vec3 tip = OculusManager::getLaserPointerTipPosition(&palm); + glm::vec3 result; - //Dont render the reticle if its inactive - if (!_reticleActive[i]) { - continue; + if (findSphereIntersection(myAvatar->getHead()->calculateAverageEyePosition(), + tip, glm::vec3(0, 0, 0), 1, result)){ + printf("Intersection Found: "); + printVector(result); + } + + + //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 dist; + ////Bottom Left + //dist = sqrt(lX * lX + bY * bY); + //float blZ = sqrt(1.0f - dist * dist); + ////Top Left + //dist = sqrt(lX * lX + tY * tY); + //float tlZ = sqrt(1.0f - dist * dist); + ////Bottom Right + //dist = sqrt(rX * rX + bY * bY); + //float brZ = sqrt(1.0f - dist * dist); + ////Top Right + //dist = sqrt(rX * rX + tY * tY); + //float trZ = sqrt(1.0f - dist * dist); + + //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); + + //glEnd(); + } + } else { + //Dont render the reticle if its inactive + if (!_reticleActive[i]) { + continue; + } + + float mouseX = (float)_mouseX[i]; + float mouseY = (float)_mouseY[i]; + mouseX -= reticleSize / 2; + mouseY += reticleSize / 2; + + //Get new UV coordinates from our magnification window + float newULeft = mouseX / widgetWidth; + float newURight = (mouseX + reticleSize) / widgetWidth; + float newVBottom = 1.0 - mouseY / widgetHeight; + float newVTop = 1.0 - (mouseY - reticleSize) / 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 dist; + //Bottom Left + dist = sqrt(lX * lX + bY * bY); + float blZ = sqrt(1.0f - dist * dist); + //Top Left + dist = sqrt(lX * lX + tY * tY); + float tlZ = sqrt(1.0f - dist * dist); + //Bottom Right + dist = sqrt(rX * rX + bY * bY); + float brZ = sqrt(1.0f - dist * dist); + //Top Right + dist = sqrt(rX * rX + tY * tY); + float trZ = sqrt(1.0f - dist * dist); + + 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); + + glEnd(); } - - float mouseX = (float)_mouseX[i]; - float mouseY = (float)_mouseY[i]; - mouseX -= reticleSize / 2; - mouseY += reticleSize / 2; - - //Get new UV coordinates from our magnification window - float newULeft = mouseX / widgetWidth; - float newURight = (mouseX + reticleSize) / widgetWidth; - float newVBottom = 1.0 - mouseY / widgetHeight; - float newVTop = 1.0 - (mouseY - reticleSize) / 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 dist; - //Bottom Left - dist = sqrt(lX * lX + bY * bY); - float blZ = sqrt(1.0f - dist * dist); - //Top Left - dist = sqrt(lX * lX + tY * tY); - float tlZ = sqrt(1.0f - dist * dist); - //Bottom Right - dist = sqrt(rX * rX + bY * bY); - float brZ = sqrt(1.0f - dist * dist); - //Top Right - dist = sqrt(rX * rX + tY * tY); - float trZ = sqrt(1.0f - dist * dist); - - 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); - - glEnd(); - } glEnable(GL_DEPTH_TEST); } From 8b04a9c8b600d3c3eb8c81f21af4d7419b9bc391 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 3 Jul 2014 16:19:05 -0700 Subject: [PATCH 011/135] Add toggle for user interface display - "/" toggles UI (incl. stats if displayed); menu View > User Interface - "%" = toggle status; menu View > Stats - Acts on normal, 3DTV, and Oculus display. --- interface/src/Application.cpp | 22 ++++++++++------------ interface/src/Menu.cpp | 6 ++---- interface/src/Menu.h | 2 +- interface/src/devices/OculusManager.cpp | 2 +- interface/src/devices/TV3DManager.cpp | 9 +++++++-- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9b2ed7b63d..0c469102cc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -683,16 +683,11 @@ void Application::paintGL() { _rearMirrorTools->render(true); } - { - 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); - _applicationOverlay.displayOverlayTexture(); - } + PerformanceTimer perfTimer("paintGL/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(); } } @@ -1012,6 +1007,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); @@ -1031,7 +1029,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: @@ -2793,7 +2791,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { if (!selfAvatarOnly) { // Render the world box - if (whichCamera.getMode() != CAMERA_MODE_MIRROR && Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { + if (whichCamera.getMode() != CAMERA_MODE_MIRROR && Menu::getInstance()->isOptionChecked(MenuOption::Stats) && Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) { PerformanceTimer perfTimer("paintGL/displaySide/renderWorldBox"); renderWorldBox(); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index dba5feca9e..7b80b0b529 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); 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())); @@ -407,9 +408,6 @@ 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); - QMenu* sixenseOptionsMenu = developerMenu->addMenu("Sixense Options"); addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseMouseInput, 0, true); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 06b5c5c9f4..2d13a81b1f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -349,7 +349,6 @@ namespace MenuOption { 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"; @@ -438,6 +437,7 @@ namespace MenuOption { const QString UploadAttachment = "Upload Attachment Model"; const QString UploadHead = "Upload Head Model"; const QString UploadSkeleton = "Upload Skeleton Model"; + const QString UserInterface = "UserInterface"; const QString Visage = "Visage"; const QString VoxelMode = "Cycle Voxel Mode"; const QString Voxels = "Voxels"; diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 199c313119..1260dc7036 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -269,7 +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); + const bool displayOverlays = Menu::getInstance()->isOptionChecked(MenuOption::UserInterface); //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)) { 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); From 97ca6d70fa73e87cfb6497012bf2496bee1f976e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 3 Jul 2014 17:15:46 -0700 Subject: [PATCH 012/135] Fixed performance timer scope --- interface/src/Application.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0c469102cc..96fee69bda 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -683,11 +683,13 @@ void Application::paintGL() { _rearMirrorTools->render(true); } - PerformanceTimer perfTimer("paintGL/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(); + { + PerformanceTimer perfTimer("paintGL/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(); + } } } From da32c6e89bf812b47cb3cf8b9ff7aa79311756f6 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 3 Jul 2014 17:29:10 -0700 Subject: [PATCH 013/135] Push persistent mappings into reliable delta channel before sending and pull transient mappings out after sending. --- libraries/metavoxels/src/Bitstream.cpp | 20 ++++++ libraries/metavoxels/src/Bitstream.h | 32 +++++++++ .../metavoxels/src/DatagramSequencer.cpp | 18 ++++- libraries/metavoxels/src/DatagramSequencer.h | 11 ++++ tests/metavoxels/src/MetavoxelTests.cpp | 65 ++++++++++++------- tests/metavoxels/src/MetavoxelTests.h | 4 +- 6 files changed, 123 insertions(+), 27 deletions(-) diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index 4a86344c8c..459a12dc15 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -278,6 +278,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) { diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index d900b34847..97f1b70ff0 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 @@ -353,6 +379,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; } diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index 3b16a829e6..2ef3d0213c 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -276,6 +276,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) { @@ -297,7 +302,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 @@ -312,7 +320,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 @@ -700,6 +711,7 @@ void ReliableChannel::handleMessage(const QVariant& message, Bitstream& in) { ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool output) : QObject(sequencer), _index(index), + _output(output), _dataStream(&_buffer), _bitstream(_dataStream), _priority(1.0f), @@ -713,6 +725,8 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o connect(&_bitstream, SIGNAL(sharedObjectCleared(int)), SLOT(sendClearSharedObjectMessage(int))); 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) { diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index 09d2f834ef..32f645b13c 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -78,6 +78,12 @@ 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 the packet number of the sent packet at the specified index. int getSentPacketNumber(int index) const { return _sendRecords.at(index).packetNumber; } @@ -147,6 +153,7 @@ private slots: void sendClearSharedObjectMessage(int id); void handleHighPriorityMessage(const QVariant& data); + void clearReliableChannel(QObject* object); private: @@ -325,6 +332,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; } @@ -390,6 +400,7 @@ private: void readData(QDataStream& in); int _index; + bool _output; CircularBuffer _buffer; CircularBuffer _assemblyBuffer; QDataStream _dataStream; diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 7c278f3c2f..cc2d34f180 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -642,13 +642,14 @@ TestReceiveRecord::TestReceiveRecord(const MetavoxelLOD& lod, _remoteState(remoteState) { } +const int RELIABLE_DELTA_CHANNEL_INDEX = 1; + TestEndpoint::TestEndpoint(Mode mode) : Endpoint(SharedNodePointer(), new TestSendRecord(), new TestReceiveRecord()), _mode(mode), _highPriorityMessagesToSend(0.0f), _reliableMessagesToSend(0.0f), - _reliableDeltaReceivedOffset(0), - _reliableDeltaPending(false) { + _reliableDeltaChannel(NULL) { connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleHighPriorityMessage(const QVariant&))); @@ -657,9 +658,13 @@ TestEndpoint::TestEndpoint(Mode mode) : 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(); @@ -667,11 +672,11 @@ TestEndpoint::TestEndpoint(Mode mode) : _data.guide(visitor); qDebug() << "Created" << visitor.leafCount << "base leaves"; - //_data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), new Sphere()); + _data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), new Sphere()); _sphere = new Sphere(); static_cast(_sphere.data())->setScale(0.01f); - //_data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), _sphere); + _data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), _sphere); return; } // create the object that represents out delta-encoded state @@ -894,7 +899,7 @@ bool TestEndpoint::simulate(int iterationNumber) { newSphere->setTranslation(newSphere->getTranslation() + glm::vec3(randFloatInRange(-0.01f, 0.01f), randFloatInRange(-0.01f, 0.01f), randFloatInRange(-0.01f, 0.01f))); } - //_data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), oldSphere, _sphere); + _data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), oldSphere, _sphere); spannerMutationsPerformed++; } @@ -903,15 +908,11 @@ bool TestEndpoint::simulate(int iterationNumber) { return false; } // if we're sending a reliable delta, wait until it's acknowledged - if (_reliableDeltaReceivedOffset > 0) { - if (_sequencer.getReliableOutputChannel()->getOffset() < _reliableDeltaReceivedOffset) { - Bitstream& out = _sequencer.startPacket(); - out << QVariant::fromValue(MetavoxelDeltaPendingMessage()); - _sequencer.endPacket(); - return false; - } - _reliableDeltaReceivedOffset = 0; - _reliableDeltaData = MetavoxelData(); + if (_reliableDeltaChannel) { + Bitstream& out = _sequencer.startPacket(); + out << QVariant::fromValue(MetavoxelDeltaPendingMessage()); + _sequencer.endPacket(); + return false; } Bitstream& out = _sequencer.startPacket(); out << QVariant::fromValue(MetavoxelDeltaMessage()); @@ -925,13 +926,16 @@ bool TestEndpoint::simulate(int iterationNumber) { _sequencer.cancelPacket(); // we need to send the delta on the reliable channel - ReliableChannel* channel = _sequencer.getReliableOutputChannel(); - channel->startMessage(); - channel->getBitstream() << QVariant::fromValue(MetavoxelDeltaMessage()); - _data.writeDelta(sendRecord->getData(), sendRecord->getLOD(), channel->getBitstream(), _lod); - channel->endMessage(); + _reliableDeltaChannel = _sequencer.getReliableOutputChannel(RELIABLE_DELTA_CHANNEL_INDEX); + _reliableDeltaChannel->getBitstream().copyPersistentMappings(_sequencer.getOutputStream()); + _reliableDeltaChannel->startMessage(); + _reliableDeltaChannel->getBitstream() << QVariant::fromValue(MetavoxelDeltaMessage()); + _data.writeDelta(sendRecord->getData(), sendRecord->getLOD(), _reliableDeltaChannel->getBitstream(), _lod); + _reliableDeltaWriteMappings = _reliableDeltaChannel->getBitstream().getAndResetWriteMappings(); + _reliableDeltaChannel->getBitstream().clearPersistentMappings(); + _reliableDeltaChannel->endMessage(); - _reliableDeltaReceivedOffset = channel->getBytesWritten(); + _reliableDeltaReceivedOffset = _reliableDeltaChannel->getBytesWritten(); _reliableDeltaData = _data; _reliableDeltaLOD = _lod; @@ -1087,9 +1091,10 @@ void TestEndpoint::handleMessage(const QVariant& message, Bitstream& in) { compareMetavoxelData(); } else if (userType == MetavoxelDeltaPendingMessage::Type) { - if (!_reliableDeltaPending) { + if (!_reliableDeltaChannel) { + _reliableDeltaChannel = _sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX); + _reliableDeltaChannel->getBitstream().copyPersistentMappings(_sequencer.getInputStream()); _reliableDeltaLOD = getLastAcknowledgedSendRecord()->getLOD(); - _reliableDeltaPending = true; } } else if (userType == QMetaType::QVariantList) { foreach (const QVariant& element, message.toList()) { @@ -1099,7 +1104,7 @@ void TestEndpoint::handleMessage(const QVariant& message, Bitstream& in) { } PacketRecord* TestEndpoint::maybeCreateSendRecord() const { - if (_reliableDeltaReceivedOffset > 0) { + if (_reliableDeltaChannel) { return new TestSendRecord(_reliableDeltaLOD, _reliableDeltaData, _localState, _sequencer.getOutgoingPacketNumber()); } return new TestSendRecord(_lod, (_mode == METAVOXEL_CLIENT_MODE) ? MetavoxelData() : _data, @@ -1128,8 +1133,10 @@ void TestEndpoint::handleReliableMessage(const QVariant& message, Bitstream& in) if (message.userType() == MetavoxelDeltaMessage::Type) { PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord(); _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _dataLOD = _reliableDeltaLOD); + _sequencer.getInputStream().persistReadMappings(in.getAndResetReadMappings()); + in.clearPersistentMappings(); compareMetavoxelData(); - _reliableDeltaPending = false; + _reliableDeltaChannel = NULL; return; } if (message.userType() == ClearSharedObjectMessage::Type || @@ -1160,6 +1167,16 @@ 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(); diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index f451b5e8b6..5d719ccfdf 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -66,6 +66,7 @@ private slots: void handleHighPriorityMessage(const QVariant& message); void handleReliableMessage(const QVariant& message, Bitstream& in); void readReliableChannel(); + void checkReliableDeltaReceived(); private: @@ -98,10 +99,11 @@ private: QVariantList _reliableMessagesSent; CircularBuffer _dataStreamed; + ReliableChannel* _reliableDeltaChannel; int _reliableDeltaReceivedOffset; MetavoxelData _reliableDeltaData; MetavoxelLOD _reliableDeltaLOD; - bool _reliableDeltaPending; + Bitstream::WriteMappings _reliableDeltaWriteMappings; }; /// A simple shared object. From 2ad2b6cd1ce6cd6b49d68d2c71275ab76c212718 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 3 Jul 2014 18:23:37 -0700 Subject: [PATCH 014/135] Resize the overlay framebuffer when the application window is resized --- interface/src/Application.cpp | 1 + interface/src/ui/ApplicationOverlay.cpp | 6 ++++++ interface/src/ui/ApplicationOverlay.h | 1 + 3 files changed, 8 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 96fee69bda..ab31eb09cd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -712,6 +712,7 @@ void Application::resizeGL(int width, int height) { resetCamerasOnResizeGL(_myCamera, width, height); glViewport(0, 0, width, height); // shouldn't this account for the menu??? + _applicationOverlay.resize(); updateProjectionMatrix(); glLoadIdentity(); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 4c445958ec..879c7cda32 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -998,6 +998,12 @@ void ApplicationOverlay::renderTexturedHemisphere() { } +void ApplicationOverlay::resize() { + delete _framebufferObject; + _framebufferObject = NULL; + // _framebufferObject is recreated at the correct size the next time it is accessed via getFramebufferObject(). +} + QOpenGLFramebufferObject* ApplicationOverlay::getFramebufferObject() { if (!_framebufferObject) { _framebufferObject = new QOpenGLFramebufferObject(Application::getInstance()->getGLWidget()->size()); diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 7c1f87d575..5e8d06ab89 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -32,6 +32,7 @@ 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; + void resize(); // Getters QOpenGLFramebufferObject* getFramebufferObject(); From d1cddb4f2854c844579f9031a2a800abc022a22d Mon Sep 17 00:00:00 2001 From: mpursley Date: Mon, 7 Jul 2014 01:02:04 -0700 Subject: [PATCH 015/135] adding "export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake" for qt5.2.1 --- BUILD.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BUILD.md b/BUILD.md index 674f0d24cc..e1935bd1c5 100644 --- a/BUILD.md +++ b/BUILD.md @@ -25,6 +25,8 @@ 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/ + ... or ... + 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. From f13c7634ad8af64fc2369fd897fd24f76f61200a Mon Sep 17 00:00:00 2001 From: mpursley Date: Mon, 7 Jul 2014 01:08:11 -0700 Subject: [PATCH 016/135] adding "export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.2.1/lib/cmake" for qt5.2.1 --- BUILD.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index e1935bd1c5..93aafcc3e0 100644 --- a/BUILD.md +++ b/BUILD.md @@ -24,11 +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). +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/ - ... or ... + 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. From e15f003639819234e5b6e8021e8dd11588119ae9 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 7 Jul 2014 11:08:43 -0700 Subject: [PATCH 017/135] Bring reliable delta streaming code over from test. --- .../src/metavoxels/MetavoxelServer.cpp | 61 ++++++++++++++++--- .../src/metavoxels/MetavoxelServer.h | 8 ++- libraries/metavoxels/src/Endpoint.h | 3 + .../metavoxels/src/MetavoxelClientManager.cpp | 31 ++++++++-- .../metavoxels/src/MetavoxelClientManager.h | 4 ++ tests/metavoxels/src/MetavoxelTests.cpp | 2 - 6 files changed, 91 insertions(+), 18 deletions(-) diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index e7e06c96d0..a01efddbf4 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -91,24 +91,56 @@ void MetavoxelServer::sendDeltas() { MetavoxelSession::MetavoxelSession(const SharedNodePointer& node, MetavoxelServer* server) : Endpoint(node, new PacketRecord(), NULL), - _server(server) { + _server(server), + _reliableDeltaChannel(NULL) { connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(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) { + Bitstream& out = _sequencer.startPacket(); + out << QVariant::fromValue(MetavoxelDeltaPendingMessage()); + _sequencer.endPacket(); + return; + } + Bitstream& out = _sequencer.startPacket(); out << QVariant::fromValue(MetavoxelDeltaMessage()); PacketRecord* sendRecord = getLastAcknowledgedSendRecord(); - _server->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod); + out.setBytesRemaining(_sequencer.getMaxPacketSize()); + try { + _server->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod); + _sequencer.endPacket(); + + } catch (const ByteLimitExceededException& exception) { + _sequencer.cancelPacket(); + + // we need to send the delta on the reliable channel + _reliableDeltaChannel = _sequencer.getReliableOutputChannel(RELIABLE_DELTA_CHANNEL_INDEX); + _reliableDeltaChannel->getBitstream().copyPersistentMappings(_sequencer.getOutputStream()); + _reliableDeltaChannel->startMessage(); + _reliableDeltaChannel->getBitstream() << QVariant::fromValue(MetavoxelDeltaMessage()); + _server->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), _reliableDeltaChannel->getBitstream(), _lod); + _reliableDeltaWriteMappings = _reliableDeltaChannel->getBitstream().getAndResetWriteMappings(); + _reliableDeltaChannel->getBitstream().clearPersistentMappings(); + _reliableDeltaChannel->endMessage(); + + _reliableDeltaReceivedOffset = _reliableDeltaChannel->getBytesWritten(); + _reliableDeltaData = _server->getData(); + _reliableDeltaLOD = _lod; + + Bitstream& out = _sequencer.startPacket(); + out << QVariant::fromValue(MetavoxelDeltaPendingMessage()); + _sequencer.endPacket(); + } } void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) { @@ -116,7 +148,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 +167,13 @@ 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; +} diff --git a/assignment-client/src/metavoxels/MetavoxelServer.h b/assignment-client/src/metavoxels/MetavoxelServer.h index d9b010e282..f2769f26f2 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.h +++ b/assignment-client/src/metavoxels/MetavoxelServer.h @@ -63,7 +63,6 @@ public: protected: - virtual void writeUpdateMessage(Bitstream& out); virtual void handleMessage(const QVariant& message, Bitstream& in); virtual PacketRecord* maybeCreateSendRecord() const; @@ -71,12 +70,19 @@ protected: private slots: void handleMessage(const QVariant& message); + void checkReliableDeltaReceived(); private: MetavoxelServer* _server; MetavoxelLOD _lod; + + ReliableChannel* _reliableDeltaChannel; + int _reliableDeltaReceivedOffset; + MetavoxelData _reliableDeltaData; + MetavoxelLOD _reliableDeltaLOD; + Bitstream::WriteMappings _reliableDeltaWriteMappings; }; #endif // hifi_MetavoxelServer_h diff --git a/libraries/metavoxels/src/Endpoint.h b/libraries/metavoxels/src/Endpoint.h index b1f468531b..3c681a7b98 100644 --- a/libraries/metavoxels/src/Endpoint.h +++ b/libraries/metavoxels/src/Endpoint.h @@ -24,6 +24,9 @@ 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); diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp index 008a477187..9abb5e9e5d 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.cpp +++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp @@ -86,7 +86,11 @@ void MetavoxelClientManager::updateClient(MetavoxelClient* client) { MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelClientManager* manager) : Endpoint(node, new PacketRecord(), new PacketRecord()), - _manager(manager) { + _manager(manager), + _reliableDeltaChannel(NULL) { + + connect(_sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX), + SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&))); } void MetavoxelClient::guide(MetavoxelVisitor& visitor) { @@ -124,19 +128,34 @@ void MetavoxelClient::readMessage(Bitstream& in) { } 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) { + _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _dataLOD = _reliableDeltaLOD); + _sequencer.getInputStream().persistReadMappings(in.getAndResetReadMappings()); + in.clearPersistentMappings(); + _reliableDeltaChannel = NULL; + + } else { + _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, + _dataLOD = getLastAcknowledgedSendRecord()->getLOD()); + } + } else if (userType == MetavoxelDeltaPendingMessage::Type) { + if (!_reliableDeltaChannel) { + _reliableDeltaChannel = _sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX); + _reliableDeltaChannel->getBitstream().copyPersistentMappings(_sequencer.getInputStream()); + _reliableDeltaLOD = getLastAcknowledgedSendRecord()->getLOD(); + } } 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(_dataLOD, _data); } diff --git a/libraries/metavoxels/src/MetavoxelClientManager.h b/libraries/metavoxels/src/MetavoxelClientManager.h index dd11e871ec..9ab2e2c8b0 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.h +++ b/libraries/metavoxels/src/MetavoxelClientManager.h @@ -70,6 +70,10 @@ private: MetavoxelClientManager* _manager; MetavoxelData _data; + MetavoxelLOD _dataLOD; + + ReliableChannel* _reliableDeltaChannel; + MetavoxelLOD _reliableDeltaLOD; }; #endif // hifi_MetavoxelClientManager_h diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index cc2d34f180..48ce4716b7 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -642,8 +642,6 @@ TestReceiveRecord::TestReceiveRecord(const MetavoxelLOD& lod, _remoteState(remoteState) { } -const int RELIABLE_DELTA_CHANNEL_INDEX = 1; - TestEndpoint::TestEndpoint(Mode mode) : Endpoint(SharedNodePointer(), new TestSendRecord(), new TestReceiveRecord()), _mode(mode), From 21ba9bf4105e67220a4be0024c91eb07229d7259 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 7 Jul 2014 11:22:17 -0700 Subject: [PATCH 018/135] Because attempting to compile the new voxel texture shader causes my system (Ubuntu LTS, Intel graphics) to hang, change it to compile lazily when activated. --- interface/src/voxels/VoxelSystem.cpp | 30 +++++++++++++++++----------- interface/src/voxels/VoxelSystem.h | 2 ++ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/interface/src/voxels/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp index 2b86a58d9b..ca79967109 100644 --- a/interface/src/voxels/VoxelSystem.cpp +++ b/interface/src/voxels/VoxelSystem.cpp @@ -499,17 +499,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, @@ -1509,7 +1499,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 +2149,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..ae8752605a 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; From 982a50dbcf8d922af62d59e2f36fbae17f200d65 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 7 Jul 2014 11:33:54 -0700 Subject: [PATCH 019/135] Remove multithread access from NetworkAccessManager for the time being --- .../networking/src/NetworkAccessManager.cpp | 150 +----------------- .../networking/src/NetworkAccessManager.h | 20 +-- 2 files changed, 10 insertions(+), 160 deletions(-) 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..c1e22f5082 100644 --- a/libraries/networking/src/NetworkAccessManager.h +++ b/libraries/networking/src/NetworkAccessManager.h @@ -13,32 +13,16 @@ #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(); + Q_DISABLE_COPY(NetworkAccessManager) }; #endif // hifi_NetworkAccessManager_h \ No newline at end of file From a225df63f66e94aebd83a311752f6eb2bd61b62c Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 7 Jul 2014 11:40:03 -0700 Subject: [PATCH 020/135] Removed deprecated moveToThread() --- assignment-client/src/Agent.cpp | 1 - interface/src/Application.cpp | 5 ----- 2 files changed, 6 deletions(-) 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/interface/src/Application.cpp b/interface/src/Application.cpp index c263b71238..ace265ad4f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -316,12 +316,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); From dd8f7e81b169f863bbb7aa03896dd8b122f8279b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 7 Jul 2014 11:46:57 -0700 Subject: [PATCH 021/135] update the interface icon --- interface/interface.icns | Bin 313931 -> 388541 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/interface/interface.icns b/interface/interface.icns index f32590eb21addbbb314d06ce15e0d9e6d4eb82c5..6e7a748d4d3222245925add1db021190846c680e 100644 GIT binary patch literal 388541 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%0DBf~Iyuuef>G+oLvUd$G5crS<=nuB>UtPE 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

J#*`?t?0Kf&`$P;vy4x!ydgi{#k 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 z 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

-y0?+>(^^vxLG%z2y5 z-y)%byN5!9M(tvv#~jg4U;B6@3;Zs}GLcl5dr_}E^8Bv(o5@h}gfVm{+NjU&7X-*+ zxy;4Yj6O5=GW-ammgNxGyyj_rh2R#5%_c&I;KU+*t;@;Lg{C0(zpn4@()#pSU6Rle z3pGp`&_uejdeHcK@$eUZZAn5#+A9O__~Yo_w6C=|evbN_*_ZApn#U9Y7Q9Ctf$bYWa?}WySw2V2PGlVVh9~pxy zf+vmO$4&euck^(WMep;Y^a(-ds~V3VJQrVyFk*VFT(vClR??HPF|t#qCK#zO+2h z`DX4*>9FD}XFjWTbOTWl9f)eg-uvZR`g@UEMk&%WV_zU3RVQUCMCNU?^|)%30X2A! zCk&0~wU?0v>1fun_7`8>SHAYT){4gtcmu8wugdF<;5RfW+ z`qc@Jiw6b!AVaPEhi-%Rk1xyFPc{LZQsdHQ0gXQTMSv-QcxHgne^>H1%5Cg^WPaqK z0Nl#Tv5(=~M~~MumrfJX<^B71^20DCO)$V}f;x~`56+}dG#ZSVp`3?#ffUhld>?BOm53O|Ff$|+Tp{FOl$A5!4CQ8`ifnKq| zhR++JXk(-qt6tyCOtCUx!wy1oiyR`TK_SD(G;Yagy;&YhbhJ)#T0hObK@-=OvRt03(Un zOKox?8ZKxo@M6BD*Dgi*5oUFLLtE(D@L8o|1)N)fSPU)V&nL~oJ>iFKipyx}orY3m+1Lh*l90>rEYyQ}vDJPp16lAS-H2*}^K=NSbZO#7!M{&le)^Ifg2CdhL8@T5_JjdG!(qzv(dKmqTW9(c`FjO*o+nqcw$Z6 zC5n9yfT+YYgu{=_B%X@NXz_LQf+a}sn58UM1n6VMLZWeKO`vSCvK4r)HApqV z!7+Ci^qiLHlHy3jVY4~`YiY-rbJ^7Qhyb09;BgZ+NSO<+co`P{0*!L4C&mqdq%768 zr`iF&2uetB9u-0!9ahulD$>R~m|C;as2)hhgYSAK&gNKjb5=pB68LX3EF_WJCYDbV zE4w`(|Eh;b1@QlVCR>$} z%1YsofCZzaN^g<*i|tNwPL-}pQYEN$K-FG6@g?Ey7I$p?&MUUscDPp7q6XhGTJ0I{ zPTjiq=3=x(I%92Zs%3GW={N%qZ1n?$!1zCh?-9Y%fpqBBax353kDmDkb=d&2dn16R z#1ur?9*}HADs9b{^9B10g$Cx&<)wml>}vTpC|F+6GGDCSc9VZtvAll7m6hXr)7BF- zBc&pB+jAZ9qtPqr-XSRLd}H;s26nz&i2p#5o2;iBB=mTAdf!sfwj&*3w>|2qOKyGH zyQRDJC;!QoWJv2@hW#sh=kEfW6TLl-HjURZN_+v!?IYd49>|UeX1#BXXSw1n_^heO zqRz~Lr7xy66C+pqb7;wFj!vo8w7$0!ml8q24zkkkRpwL={q{qCw&2&YRTkJOEq76Eu% zBmlAqN*kDY6}>moH89HJ9wSW*#Dfvi+jrk`*w?eZf~JD#!yxSBEkgIy>|@X?QxxS1 z|FMWaQ(s7?OQwy{iw9rAIzP;q_RlGHVHA1+Y|~)T6EPg$Re%j-AuBHDv`B=B-SKyZ zhD&Y$Bw*StGhJ-F%rEFgHB)?`SJL}M41tSvm3iyqVYxS0Ir4oV%pK;x`O&=Lxm$-n z1q5^#=mRxGB{&X^o3wDxyk3FAaNAx8OL4CU|^8n5$X>(yq~z~R4o4UJbp zY|ahKD=7)fLzDmh_C23X@4>`?1wVuiiCd8&Zl-$uAb7g~Dk8m0dvj-{oUbC7G&ixF z`y><5d0t1Vd(a;tXl@nzDXMHw@wz&O?KLW66i6%GFoZBC9UPcq%m1+;{KvZ%HxRJb z4xWPpa!x|o0b8tSP{+F6cB*%Ck0WfBh(V;l+v?)?o}bF53A{}hD|GcjDNIoB#vP{C zw&^wI4n)cTm2i$8MOc(|!GNcF2!$u_7$WGGtQ*%_^Y`-o{8|p0D1)f~E72;$qF}n{ z?y|trONYeL9nv7NOLqtYqBKYtAfPn6bO=&PrywB`Dvhv|w6KJ9cS!dF%lG)b&;2!j zX3pGu?wK>sj75&jh%N_MkRU@@n3_hx*TDToJ1m{8j=WOvV#}>9H=u57T>r^ptC-BD zG>0gMA%{EYM8=UN%j2&{#&)aj(9V9xpm0_RT{!>-27wqQzb)S_R6~H8XitIxZAC7F zfgK&#`wzZn)$|}owZAXwJ#9E+Yt@S6#uj*Yngt<=uOGg4hD}?Jd)t6J7C^;1Cr$Q6 zJ9CA~eP0{j#*MShqII5zw~{+<=uy1uh|5EHW%k@G-$$>$mKtgVjO=XYQx}lhcKV3b zvG|nwc^Eieoa0uh@4n>6$X%r7NqDX`I|bkIak8dWqBO(`TcT8F zXcMFGk3qV1oD7GvDc$+{dx<|AfX9cH+-DH%eA)aJVrp39X`2VlN4r>|z1Y2%_P;=m zLBMP@8)@ja?{C|Q{;xI!AhQ@ihmzRY*x%vBNBP#87aNPN$BWZcNj!IAH$y*j!;KZ2 zivU)iQn-J*;*icKB1aBImgMSmkSybEJ2r6iwd5^DDS51bq<&a+miHA%>snX1H1BZ$ zzcgY5|CWU;{*w-#g1=|A+$Se75NLC}u4e9T5dG-7>qNGsRvOOmD?|nM>_ebbB@*l} zcdZh63v4?mcz@fl9418VIkaEt{(%f!s-5z!o08Bdf!ICj)4IK6=!u16Wajk|LoGHp zJhmrnxsaLEUVYEVb(2O!=tNNnBrBQ)H{6%8?hN>+C<%tK( zylZ?#R^67!U|GA%#MVM`L8X890_C)R<3%lx`OC}dLvqLR)1HZG{A|a<0+pD~V`Q5#WU2Lo;%@YhEr;R;W znC>z~-ZW`BbeB$&*)4S+6!Y#zQQ6|kH!L!P9c*l+n+~C|B&>PkeCHC+Y z+uV4^?ffqZQA^cdwU~NfRg2``~fqPJpbiqfF+q3bt ztBdnUd#q-j*Utlq;nfyxu`PqCP)o8m4=!R_Ml9s}7c_KwAg=LupsNyZktF z{5(QGnn&~zPlJjx{#5H2jkM@ut7cc+hqriB^$8I=Fm4A)#EFD*?TbD zPu>Il-=4n~G#c2+wS5qe{mJ)+&F=mfKuWjnK0L-*+i3ilBj!vSM! zcR=E63>jht0aW|?&7L6vzTP$2m0qM5=|Kct%C%^Nm0IAo-tM{*7 z;UBk^mAPxWv8F}o&BY$qZ}c75{`!C%P%v|E_wpmV>kbg=YDq2lk~bcF*fN2oEh4_| zYw1z!ZMp0!p0|gd^IVj&Hbw#Pg^%fu8IGAAANFi}cb>9&5+76q+Vf}NXv4S4`O3aE z?hlKz+D==US5#fCoqF1+ud6@f$n5~hWf0yAiR@rHX89v1T9zjAsgJ5sNW%dJcsW-M z93_3KHnw|a!d_qxWUU;L7IWXY?J-)@y#Iz5r-5jX7ty_WudHAO40qhuujYDzcraaC zy47>q3h}&leU>`&tSZXm2mr8VKy=9XTwU8!&xzAQ31Yu5yf{LA)s-1MoIBJK&WAm!yT^}z6^?fl?0*-BC1*p*gq9Wqxo>RUh z0xFhDDB{!nnZibzyyL@T>2!XSa9@!lj@|ptLC*HAz2g#Fl$j7WL+l2drNd8)0hvj6 zYPA2gqSeJ>_s3=Od_bFqaj5*1X}j)^QoPjNz@M(Af8|ig=Uiy@_8|kS*V; z->I>2HQ7pvezJj^6+z5NK9~m!c)u6BKyp(Fe#$0=`P(jGQ%9(&vjf*>e<_|FFYUzJ zTaaP1&CNfZSUe;FS`oSqRhg1zkxEe&9th2?!f2EeHbkZvU%bk+1|#BpbryehBjoj9sgWwVC(gXABwFmYgN zvqL6hpM{G6?qqh?aZ)aaHu1sb#<79pk|U1GaeS-n>_;RVuwgHKZ-pY^isi1`%U7go zWKcOxFexkGwSM}^ut7pX>kk7uhfkjh_3s)(+&8QmD0)?Ub$d-bTvU`ae!QfRAM?Y# zSw7W0?>qG_Ds#3r*Xj8z2mtiIsjcmk6GKY|9_=iuXJ+9xh{3KS@Q?7QttYfk&i zoOV)nG9TUuxdKsuyp%}n7prUc1SzSPCS@UU@Asloi@99 zJ9ooc2wf7UHguok#@$S~QQFJMLToG{y<~xBR+8X`w1$#~dR&|V($KEz_3-P1?W6!m zqIsfoqEBK}Th$BMY>|3OUCGe$z+A%*{Kzq| zA8FIFw8O-u_Fw`Aj%qksng4gi|4X=EV&958@FI@b91I$9|>PmAN0G%3XLvR|?H zj3odF=JEliT=*&2`{-IIfH!RFpQE4Qq$?HW!jG@VPV=PMvns!%REx4DQimE`UY8Z$ zu#mh^jN!B-chPckzB_8%1pWDAUZPUKH99t@T_ks3Dv?yXgpiel%=5&GG5wZ&EssL= zTiooBqJA9XXdb@=>Vc4$zdXxE)W`2G}H~8gq&-ifV+!DpX6}*?X5(Sf-LVya-RZgRGf62y$Pt0&r z%L0?cM?E|CoWC>UaVt{)eqUOGzI^jjfr6|TzH9p0eP@pAV2t}DHRw%ZDGZsa2VjS| zDS+a^yn(mYF1JyLstuVJROrV?As#Bxm57}@gNq-2F^D{Zvod%*zV}t(;v$<`D{`d* z%UC;hwf36?=NQ|3Q0GT=)f}|l190rn`Gptw65F2Ya?`CQ6q!wy7Ds4H?uD7tjl;ix1Cm=LIBwAU{06@cs**hPhXq$y0ff3NpuiioXu>h<>s5k!%;* z#V&0T=C-ta`m&=WL1$M%ZL0Vt8jml-kB??VJyiT#Td{)~>5I_W&-|{@C)h`^1IAK^ zr|q|Da^d$Qhq;zmVauxtgGz+b=UU-4M6KAm&xAn!i{d+W#g&A=&44a<1nt$-p^W7*lCbg_dKhgx$*Zr&3T?D1rO_g~ z9^CP~e-w^e8Ak;Df=)kr=j1wVC6bf%-s=1wI`^jyau$dy- zzv#RIaezPix!u(Z*=Lr{pM=ka$7Mx~Rj{|s(|JD8TjzZ(=!(7DPtGd2DJ>C4w!TQ9 z1+*DA47BMNH+CzXioUy{rKL7W@{s2fi^y-|VWDv;2(ZREMaU6kW0)|5e*w^jAA&b4 zrr_D^aM=(zhW86p&?vs<-o^vLLbx?g3EL-jTV60cqV1r-DRZ=Hks&_DgHJ%3^%Wf( zOxEv_7Z|A*OYCwzj%C#C#F6<=kpRWfoCxGc@ylV!3i&$?*(2}`E>HJ_(x6DQQp@!m z)Jq`x{***(&%O0X0`KX2pZ@+pJTh-`wMD>Wo6`D2NdAVl>rc(fSJEE+V{%+BTrYrX z(&p_YeLGI~GCS;P9};pOjy%aH09T9R_EUEY%()D83)xZ#A!3ZFvPeLCAR#}1Q#fPC z94f0EEt~K&7<;+Ow0Ba?l)6#wqU;Cs1yv;Sol|?W(}-9rI*i=> zstB9K6I43a>z8e|!1hOQ>njy#DDZGvlt4g$P0YbKl^}fOz=K(1@#Gex_IX-=j(0=a;>=RmO44GG<{~+MeSyCu zbk=j4aPU~kcCGaxw z-3VQD;o2`86t&5KWI(czAq3nNw+@kHg^Zz@<1Q&oI}X^I>v0=1RncGIop#;hq@+Z=u9B|pJ_U(=1>7d(ZfKtIVk7($OvhQ%#W`O!_p+`lH*GMlO1{}A!r_z^h(9Vd8UawzrT7nKaundPqN}{?16KvtAdRj&ov2~Kfg2rsX}!OwN(DsSfNIuZ~swZ$^%A)c8pm zCZsr}?ny;|JPoZfXXecM{ejA$=hz6!#~bzZ8Mr?WDV170OW_F;tiHGu1akT$k!ZS+ zd^^Oznc*9tnOs_1B1tXZnuz^&lnL}NK9c~|n==lS8;T!-m#z&zlXh-v44L;+(i$AB zVY5W6CsG7&-_-cG41*FQAp!VC5DTB#KcZdsN3#(U&A&(T<#K08%oE%dAHy-^nz}(S+nXbutuciE2Wrbhy6NzCGP20 z?$hrNEZQ}+w-ynO#IZ{2O8k%9V#;XUu>X+4Rr6h7`I5-d9kY!CcqOvB-${)sL2F}C z{>kqfB4S|xUwFDik5NRZ*^7nSG8YGvoB=izbV*B$raE$?2t_fLig#wja@^d`k7WjWUV#r|c}rztOqCG$&P{lY=M$q-TC3-`57#nsX~7 z?|fa8#hP_2^1e;2A(Jcltxu zor^dqEkJrNiDbzKPbdE)`c)`AV2~~IE=zbJB&{MxiKo)VuXW>*&hQ8b{-FG`fLhM& z;SJD<{KwE9g$sK=i|@9w>wk>H1NAuIY9=z1y5z_B(7xNk)D(JI&zm;%eZ(rBBsjp~ z&4BknnloBKHDic{)JbfMQ?Y}aIF^?^_!zF2_vybK2S6MS9dZp=drHxp0PU1J1DP&+37DVIDEaMWycs<80(<8)po`WY?<@5_dH4k|5KVAc}UQNbTdXe@qd zL}CK2*y+e=s8xFOe=@;Umt_BOz=Y1xAs5Z8ccxl{rhH#9Hh3qC+Fg1s4Dbfz7v6mh z2M`c6MSKHH0}D;;h=IjDz}q~hFp7L6YM;eJa_#iszXaz*p+;}6KUE^1M<_Gk32m?; z$0S211@n|EYIBe(2Q4Zj04s|CZZPTP*(zStS%u&9Dn(ohe<(-=g+~@{+3^tKv62p$ zzz=HzI0nT;q9Wl{k*H5Oe|I!DY^;X5m69|2Ig|%aDEtV6v&({Gc`n>gZ6B&;XkN_0o|*r&8Z4`_ zoId5%@YoXQ!iY&ly#hS3`NE#hjuC{xGL$8QMh8Ov&~h*wK0I+Yu?dS0vY16~|2BDL zN{ie0=H1L{6^fz*G=b|l>G^d5Xt{&sG#p5`Exyc%gmQE{9Lx5;``0T7@PoB08Ahdz za%!9NKP}ONB(jgJ9iV96K+}I0<07bLY}I zljngRqXJN+&3(T8_%4b-1#W5Aluhm|nF|ddmwKnxpYIAM0ro@{;okjAv1kM!!yQTC7$0>SS-nrhIhrDmnN8<>jKYaXG^}%wg zi(WnT5ck|!%Rjp7(1GYqWYijm1+j&*U*1(r=yT#@w>{HEFou&ab)60Tpf+`*lgUBT z99a!@fHhA<+hy6pn9SLETsaOvdG03hF;;%yr^sb>vf7Kxx-VfkGhI}~NMm+*Yg#1g zUrvrxQ8fw5<2TyW+V{9m2rIaLdlxYqhu>Q1Zy07~zAv7zGnIR4`5Wm88613~Z&lr9 zW&R{9`*P~$DB8|%X#I@BK)sM?3U6kDxJ_MN6-MoewfLvpC90^nsDdEUYsL_8IUdYc;8LWA`)GKD;7*;)Ubaj!1Q>V7_5+JpF&Fh zRm1KbBGI?~wxZNLYooxu{68hvgThXni{OV7c2T7P!PA}BMf)OOlk)+pt^H~D?!kRoN z;Gfq5sh~&+lWsd!-?p#WRC3(v?;!XNS9NN;a(%9TjxA;e+o_EVfeh+QJB=Y%!bFLi7WtEg|= z3)K?;{ShZNtuIFNF%=*Z_x^3) zj6XU&3dKNsX~9~}rDrLrNySNZAy*QQn8<ZC=APllDh6E+`mGWf-g=ExcKz&=*&2!5bUQx28YvWxtvtC62K8c?4(SVsYeuwD--c-;FK_7JI-gGDKx_{B! zEf=&3Mm8pCaWV4a^-Z^9r$&4qa*gOktDso_J86Cq03Q}}@(0t7E>g^L`=XORMU2JW zyS6G`jpUXnR^l4Kep2%9{QCHNjODi$UH6QH4W3`$M#5$APP#&S`?mOG_Hlu? zyuZNCNvuGG%T!Ak5g-x4ZNfjD34y^0I0|o$9(&dFEpB^0m{(%AFS9}H-Z7PwzPpAJ z|B|Qy7%4nwcTV1>esC~ymDdhZTsp>nF_MiI0n(9ptJxEMqlgHzkD(zZe5_llzRmGO zOOb^vgq0NdOLnTaXZLjsj_+?LT-o(|QQsoH_q}gwg{r=<%F$QJyhO2jGZSG5lZREs z6W@w>=2v;&`2$T3DpN#4h$zs(pFGsqd_JZb>eNXVS|_?TEm7Mlw6$*}x{&sO@#MT! z3s@6qvG`KpSP;mON$9D@z2HY}B)^rTO_fXHjy0o`J6XPZG89Zvjgi98$Q7Qlv*Fy> zSfo4}%||EZKxGN&1!#O@se4VNfbAbp;9r$JnCI)z=RYse|EuhPQELi)&fVqhh#9&DRL{ zB#^K2%thkf1WEpMoQ+7!mBXT>L$}m3j>Z96xSB8)veZ+G0tXj8126->4{2o0ZrS{2 zqnhx6W&Ftlo!!G&t|GZ2#UezJI>hqryKsjzN-yiebY<7H{k2Kq>e=9Lb$}`I6=nrk zC%4DYoD8;kb>!MgX|W{EH;z%-Ocbb4EcXH*Qv7g`8IyzW1@H?ZAq%ml0DeAeeoD@` zWlm}P3O#~8I5{K!nzP(9QrRsp`ND3b2Tjz=Z4G9iD?6JsC4e4oYLrpA zKGSRb36zn)jL7crnoMR)yP?>uEWCN=ivJ+@9UVYE()e{wdk2EqYlDGa;rr6shv>Tz zM#$6bSPm(STCMO~x~{xhDY%OEUEx2EgAYl(kC{XYRj^q`%Im+S z{VfLo7)G?!5T@SrD_DQBkX(uoy$bZG4G$JpkNz4zaZobBM&EHe|5>7VK{eWO^iwWH zLGD?kC4g`J%n`M8x;@xUxv8jdr&nTfQ$Myi!PJJ9iYi<&(zWPHiLAT_NB#e*gCPLL zQi}Cfk$$@g>c@i|t5UL&c3u7;AgaN6y4)T`h?oTW9SzP%-DC~z@zUt{l)e$2m%&K* zz(MZubvcK_!1vfmJx1QpBI0HLsZuy#JH&0t z1i#oOoNtU34!>#*z$t?=uOGKpQAh@7Ufc19PvpH?qAv9)?)cVOO&jdK z9XGRh61)vTR-Dxn&QV=wbwp5Q)#biwhT6Vpt8pdxdRP2cWJ~Peb?Qg`r~*TV*QGe} zR3Z=Al`U=$hHpUqQXVZe%&U;M1z+w;*^Q2&OG@S&I33fY^2jPQY3&U=$&xbXy3k3J ztB~YM_ni1x&}7!u0-+bmVdkgbkl(g%VKQN8aEKYi1(;7uqj&(5koQ?}sCw$m)IDs~ zew<=Q);3_t9~St$2|ryJI`JwbHwui6mjnZVRQ^u;6m4_ETfqZ!qGUMj?B%zFi8J)E zmGlM|GCS@~Ym6X~l*mILMSnVQ4FL0F;-t`BqQ^US`f_GjOeDQ|%Dqc<5%FT!suJE- zJ_p(oUUM9Bhok}opTO{`L&i9m*jfs48vlHq8Vts$ypb8|geDCMirc49IdT4M6v#Ut z%D1|!c9NUkxgGKubFx}rY{L*zvqa;w+95}tGik%|eEy@W8!%tcNU;5R@oy=sli~-C z{YynrHgb5)SZDIQO}F&h@FdiZdk22+3!o&e8@YOyog+zgQT2NYzV=@eCi`blqwt*I zV7imC$OJS;^j7fY%_DB*l9ItakNgOC$*Z3xvgk&~jSp|YPkAb~&QO1s0Y<)~oi--u z+;F&tArPl@lr;GYUVT831H5C@5E9VK5MJ^?oM{HaA4+n(tY$O=n$dZ)$D_h}{{IX_ z!waTCXqJe%wwI&oY$9OFbSDLAdGP^`-e~%f+k&Zp>KkNza5pz{b#{vh_p7n^?U2s$ zyPRRng@`mn>ttd6`-{y-qQP5=-nP5uWSYlM_&COSX~{?I_;iWAW=4EfCMY261}Ie$ z5EUtwjV$C-D2ijv@rD^ubaFp$(R?Eb9d^Us|uz2!kb?^u$wD&8E*wF~SDp}Hvl6|1QUaR>TQhEhWjkw&a^l-5a3QDo^5b(`Mh(OEk299 z^9+^beRH_?q~yhFcx_{z(N>G>n5ETLO_mxYUH7}O~IiFmNqve24pHydoVr&TKHI4mZ^plWelEE{td zpY~K`&^qvByk5@k<-Ta|ciMSXT2|WmuG8>iQlK|uwzPkZ>N?iI|MX9JqOjQP_KAUH zJlAok0~GxAd&eLHMAPBZeHvOzXzz*qcF&m`gfBFw@3K5piSqDq((RYPsvm<@jhkB^pE6au1|Q9UYUp)#SC2@*moMKC zPZ~z~mE0gc-#*)4w+2eyCO=k%FPl%$Z&3%(gGbNM#{8tgodNV8G{3~nVwBbv6Yq;1 zyC6`C1Pq)Q@Fbie6u=37Y?G~39EKl2zpz!m#|j?LUCv!0AWl3Q8>e7HKl;IjuOQBV zri^(-*KvCf>97;h^te{&tjz6Vpk=mYdaFD@;GeFTm`m3m{3-6kj5k8TD?M~z24M&% z_jdxS@5@FcO4?m6l=<)AB%nqshM@Xfd|=<@H|^4+l##Rm#qY)m6nS5N$22gXU%LFc z?QWw)w1um;$&9MS0mASZBS>rU z5j`qhR5W1wPxvCCTwh3c218h5?_q13NP+6%k8If{gu3zNG+Z5`pLyFQ2J*+-oM{@A-l8ztl6O zp((TI`of7i?(j|L3>gYzcOhI|fLa3bvc*MQz ze4#WotQR$~t68=$o)oJ#Da*1SV0C}f;a#h2V}=oiKj?D;NrXRML|BA>Yw#<6)g?ZJ z?ciLk)B4DKo_cN4F2zveelEFCYiS@!XA!5N*RcW zi%cM{`*8*7MkGR@8AXpk%ntgL6vC$xP?LdXWa;f?(TL%m|nRGR=|i34afZz0n=E&y->pXlL1xo^{Zj%hNW*ZJPaBux_qWsMsti! zQY>dgA`XyCTFAqf&6%WEP;KARru1&;_Gn_>vY@bIK=PG<2leY zVXa#wYwJnqQv8|hw9%skB{$4?R~D!tOFY(kqWvP_dZB4M`SVW87^9qggCS52`Su=& zShG2ZGYF3?L=VDjW#lrdN}>@fzWR($&IbObJ;h~BPd%Z55PpwENQfrv2~5on@G4X- z88~d#Xctjx$)<<@3isD<2m2>LfMNLG;lob`ZI)Y#t_FBE0 zYFl9Z=i* zy*!Z$CxvlURoEod$Tw^Edgs~|QWF7?%qX*u{qf4+<#W5_m;_;Fccvp`i15-65j0v^ zCD*u_`5VYZGwxjy!JdaWfjl`q5RXtbsUez2m_CG#U?$Z3E)@ykTPaId(!WuX(Z6vL z&#xlo34UA?6&){~1${AJSyPuF*{Vpsoa-!~gH&l;aB@2z9Tc^e@vaEQr@VFL1=Env zUXcd5lZB8%(xa(Mz5Y0@2^t378Ys%~LlVa4{ z+`{ZE2>BR{vPL`L1iohl6;LCvM9Ox`2x0#E4;?oB8%HbyT)$>Ts(nS#$-taZ$ckOy zVn_xxbcb&Fu^YcCuzLCPufI*84~FGNVv`qJajP*GR?ZI$Y6h1z*_QhMCKxXPjbO+;wzi7DbQ&S7 z#A31r2KsNzVB?Qn@sk~wHT7UOcBW;2HPUvRpKrTj3R4yxGe81Z~2T?OW zModksz3XZC$P;hjHYOJWvgg|C2c|c{3rezVg)3~PT4>lyY0>-BXEF#=>?7kDpJ4Hn zlwmg(?iQ6+X1#HEHCr-6MK+~_b$k;Cx(EYZH3uut)JeyK^0Gla7~;6M z&+)shZwB7KZ9emU@N(HOJ?;kwJ4ue%zd;tS z1H@D3#oyaKnb7)}3tmkxzg^uHG!Z7vZL#d)47WBE$bzYI-DzdU(sjae2`)<@TPMdE z&UwBvC11_Pvok{)6`zd;8&JM0#$g=AVGSA zkyXY^v?|>|;9I^#Bw=0->tn+>Pzz0Bj4e}7`_X=c0eS8v^&tGieY`#kR}T+3`J{nF zVm^m$b!rX@$zD8dWEZ_IlMe~Ehd2tjXwRvN?K(lm@g0c$k8VqfUAHFbs&uTNc`RuE`EHy)sM0Kr-EwHMSoya3b^7w180LPrRDeZAwUDN0y-|__Bzj~&rzLtj zG!murkO|_lW;&IeiV8{*&hy-&=?N`}V@wQSpXTmM598+A7T8i1=eN!ON*ItXyEC9c zv7~CNeL^cOxBHQ6eREVXm9>@|t+?meMxooa!d7}5u(D_4zEsQBiZAFB>G8AVam?na z4O0AVW4UVhch)>cqIV(XO+&LG#O|-f_&oK>XEwCPfODawtkt~V<;tnr6p>*? zfB^FWWN>v{B4wjRG}OYfp2lK7;AuU@3yQjS{zO;88^B@0~WB^-n5+Ei$J`yTVI(BoFrmrd4q9hFKz5M!0ntkT`vIeo!a;6%?Te$7EEChIe zC(ZQS;Eiaa4^_|_*L815D`6mHLys5rmf-Xgit|PAbDoV9fS>)}PAK49pZVPK9Z8MB zQqG}*!t}kG<7ep~Y?QC(*gvjMelc+qvA1-U+izLuk_DGE}n^*dKF5FY;&?&-^XOGa(9bOqRd>+yf{^*lvI-E`a)OrUQGR(LgvOxyjOif)`8!Q5_vLi%4^zX$Y&l zP>3F;x6{wR(N2!~`SNBgn%HpuTFPBX(~*rfZXf7u~E_DkvW zvl6`H)A-Kp#e#AoT0He7M5gAKo>!u?ic%2#_&n>(iN-Yn-o>uBcZS|#NN1K-88;G} zpYpdiuOg!U>KJsewSyf|i|X_b#E2W*M|y2(#E4HnqrUb5N$AvnfD&^V39e|xS=83= zUXchc^V#28*Nk4n`RkU-pQ6ctHB66Un4`>_1|h+naj0{6zJ?AQ?(9w{;`vvmdbFKe z)0$jFjJQcBg6lq^&L}HH{p4plP-^Xc5Z?3%%iFm>ArJnHXToigo0I!be&Tu4KHPXa z%|`fFV8y=2*LPo4ztOsy+UggXn&cJ(kM`+RaR%eVWiM-_+!ZLz%GMM2%S<9i zn@+}xw-v{+*_5KE0FL)0o#T=YT$C~Vu%G$Xk!I?Urne`G%n-tfGPZT10sMT=Lcyz< z5cz|1Y(N~}Z8rejY>|-{;Qpk1{*oBh6qWFjJ{C6*m=%E=&C(^7QGL`cDE<`kHCGOG z-y;(bWyJ)b$m$R)K>$|79zo_L>@0b`m8q_UvLq;4=k9(#ryanqzbRJ{K*@gIceL(g zD-D&$D^2-h^-~A#WUC;!DyMCoNg1M$O-!Cjl5X%6Rm>CA+Wt0USENw1LCqn}?`OTv z>+MC=&L>rOxa)wjG2S~opaUhM5b$-le zev6?%OykqGvv+r9<-?-h`#0Iy}HUdF;#2?R(>k{pZko?*<8-c-dEJ zc;7S>d2Yt>F+O=lzbj)}^+D(s4x zpK=Kob4CtuU9)yf6&NqE^5qw_CmXl%c_8^PzZkaYGFWDI|K1HNI+-nb%Q5_vGS#8e zNS0+HO4+gtzq6ycwOQ(PvQ%k^=a1uVdo&m-c5Ub3D-jFAC^Hun5tzfanY|5MMZ>+I z4@pL@i-zRd9*`ouN{sU>U+ganX?_RH6-7{Z*TQq;g=c<}H^`J;D(Rr)Qax}mx3p(Z zjK%KFF=dzR*xS4ba;N`pYaq}{b}Da~dmr;yR|8nsHgBAN2lw!KIsNG0sGkpJbVhZ4 z1n}yh*$Ah+5U%`y*b2C3mUoTa(kqiUH>@A;$2&2$;_x@hs^UE>SUO>ti@G1{yc60g zobRILKy&I~q~OaK4? NpsjA8R;gke`afDd!eamc literal 313931 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

$OT?oA{{-8y*AYx8A;qx&7CnL0bo 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%0AI-wTD5ER{jNcq*@~E0{Q!N4iO6N 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

YYefceMvyrPpkKd{Iu0j-4WUhsU+))T8wGmh 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 ZWZdPM3cr_$jDwMpD3x{}I627DKU( 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

u|4{D&x0ZmoJ$Sma}g3amPn5> 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 From 8382f764cdb9422c77066fe8b829e87ae8e7c0c6 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 7 Jul 2014 12:15:55 -0700 Subject: [PATCH 022/135] Got rid of unneccessary Q_DISABLE_COPY() --- libraries/networking/src/NetworkAccessManager.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/networking/src/NetworkAccessManager.h b/libraries/networking/src/NetworkAccessManager.h index c1e22f5082..9594170518 100644 --- a/libraries/networking/src/NetworkAccessManager.h +++ b/libraries/networking/src/NetworkAccessManager.h @@ -22,7 +22,6 @@ public: private: NetworkAccessManager(); - Q_DISABLE_COPY(NetworkAccessManager) }; #endif // hifi_NetworkAccessManager_h \ No newline at end of file From df6bbd29447e66068b236d59e571e1c0c1c2db19 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 7 Jul 2014 13:47:31 -0700 Subject: [PATCH 023/135] Fix goTo* dialog bug with chat on Mac OSX If chat was open, when pressing enter on Mac OSX the operation would not go through and would focus on the chat window. There seems to be a deeper issue, but it was decided to go with the simple solution of making these dialog windows not of `Sheet` type. --- interface/src/Menu.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 402347c5d4..c9dfa16915 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -1000,7 +1000,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(); @@ -1038,7 +1037,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(); @@ -1154,7 +1152,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(); @@ -1208,7 +1205,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) { From 69e7a17f476f17aab40f24abd6b98d04f288abb0 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 7 Jul 2014 16:07:04 -0700 Subject: [PATCH 024/135] Rather than backing out when we find that the delta is too big, we can just write the whole thing and copy it over (with some mapping fiddlery) if it turns out to be too big. --- .../src/metavoxels/MetavoxelServer.cpp | 26 +++++++++---------- libraries/metavoxels/src/Bitstream.cpp | 6 +---- libraries/metavoxels/src/Bitstream.h | 10 +++---- .../metavoxels/src/DatagramSequencer.cpp | 25 +++++++++--------- libraries/metavoxels/src/DatagramSequencer.h | 3 +++ libraries/metavoxels/src/MetavoxelData.cpp | 14 ---------- libraries/metavoxels/src/MetavoxelData.h | 7 ----- tests/metavoxels/src/MetavoxelTests.cpp | 25 ++++++++---------- 8 files changed, 42 insertions(+), 74 deletions(-) diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index a01efddbf4..28f1a423c7 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -113,33 +113,31 @@ void MetavoxelSession::update() { return; } Bitstream& out = _sequencer.startPacket(); + int start = _sequencer.getOutputStream().getUnderlying().device()->pos(); out << QVariant::fromValue(MetavoxelDeltaMessage()); PacketRecord* sendRecord = getLastAcknowledgedSendRecord(); - out.setBytesRemaining(_sequencer.getMaxPacketSize()); - try { - _server->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod); - _sequencer.endPacket(); - - } catch (const ByteLimitExceededException& exception) { - _sequencer.cancelPacket(); - + _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->getBitstream().copyPersistentMappings(_sequencer.getOutputStream()); _reliableDeltaChannel->startMessage(); - _reliableDeltaChannel->getBitstream() << QVariant::fromValue(MetavoxelDeltaMessage()); - _server->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), _reliableDeltaChannel->getBitstream(), _lod); - _reliableDeltaWriteMappings = _reliableDeltaChannel->getBitstream().getAndResetWriteMappings(); - _reliableDeltaChannel->getBitstream().clearPersistentMappings(); + _reliableDeltaChannel->getBuffer().write(_sequencer.getOutgoingPacketData().constData() + start, end - start); _reliableDeltaChannel->endMessage(); + _reliableDeltaWriteMappings = out.getAndResetWriteMappings(); _reliableDeltaReceivedOffset = _reliableDeltaChannel->getBytesWritten(); _reliableDeltaData = _server->getData(); _reliableDeltaLOD = _lod; - Bitstream& out = _sequencer.startPacket(); + // go back to the beginning with the current packet and note that there's a delta pending + _sequencer.getOutputStream().getUnderlying().device()->seek(start); out << QVariant::fromValue(MetavoxelDeltaPendingMessage()); _sequencer.endPacket(); + + } else { + _sequencer.endPacket(); } } diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index 459a12dc15..bc662aa890 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -127,7 +127,6 @@ Bitstream::Bitstream(QDataStream& underlying, MetadataType metadataType, Generic _underlying(underlying), _byte(0), _position(0), - _bytesRemaining(INT_MAX), _metadataType(metadataType), _genericsMode(genericsMode), _objectStreamerStreamer(*this), @@ -194,16 +193,13 @@ Bitstream& Bitstream::read(void* data, int bits, int offset) { void Bitstream::flush() { if (_position != 0) { _underlying << _byte; - _bytesRemaining--; - _byte = 0; - _position = 0; + reset(); } } void Bitstream::reset() { _byte = 0; _position = 0; - _bytesRemaining = INT_MAX; } Bitstream::WriteMappings Bitstream::getAndResetWriteMappings() { diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 97f1b70ff0..70fde94b79 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -329,6 +329,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); @@ -355,12 +358,6 @@ public: /// Resets to the initial state. void reset(); - /// Sets the number of "bytes remaining," which will be decremented with each byte written. - void setBytesRemaining(int bytesRemaining) { _bytesRemaining = bytesRemaining; } - - /// Returns the number of bytes remaining. - int getBytesRemaining() const { return _bytesRemaining; } - /// Returns the set of transient mappings gathered during writing and resets them. WriteMappings getAndResetWriteMappings(); @@ -546,7 +543,6 @@ private: QDataStream& _underlying; quint8 _byte; int _position; - int _bytesRemaining; MetadataType _metadataType; GenericsMode _genericsMode; diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index 2ef3d0213c..2c594fc1ca 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -113,17 +113,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 +221,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; diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index 32f645b13c..b85916b561 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -84,6 +84,9 @@ public: /// 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; } diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 43206588cc..2d61ede796 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -681,12 +681,6 @@ void MetavoxelStreamState::setMinimum(const glm::vec3& lastMinimum, int index) { minimum = getNextMinimum(lastMinimum, size, index); } -void MetavoxelStreamState::checkByteLimitExceeded() { - if (stream.getBytesRemaining() < 0) { - throw ByteLimitExceededException(); - } -} - MetavoxelNode::MetavoxelNode(const AttributeValue& attributeValue, const MetavoxelNode* copyChildren) : _referenceCount(1) { @@ -778,13 +772,11 @@ void MetavoxelNode::read(MetavoxelStreamState& state) { void MetavoxelNode::write(MetavoxelStreamState& state) const { if (!state.shouldSubdivide()) { state.attribute->write(state.stream, _attributeValue, true); - state.checkByteLimitExceeded(); return; } bool leaf = isLeaf(); state.stream << leaf; state.attribute->write(state.stream, _attributeValue, leaf); - state.checkByteLimitExceeded(); if (!leaf) { MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute, state.stream, state.lod, state.referenceLOD }; @@ -838,13 +830,11 @@ void MetavoxelNode::readDelta(const MetavoxelNode& reference, MetavoxelStreamSta void MetavoxelNode::writeDelta(const MetavoxelNode& reference, MetavoxelStreamState& state) const { if (!state.shouldSubdivide()) { state.attribute->writeDelta(state.stream, _attributeValue, reference._attributeValue, true); - state.checkByteLimitExceeded(); return; } bool leaf = isLeaf(); state.stream << leaf; state.attribute->writeDelta(state.stream, _attributeValue, reference._attributeValue, leaf); - state.checkByteLimitExceeded(); if (!leaf) { MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute, state.stream, state.lod, state.referenceLOD }; @@ -907,7 +897,6 @@ void MetavoxelNode::writeSubdivision(MetavoxelStreamState& state) const { bool subdivideReference = state.shouldSubdivideReference(); if (!subdivideReference) { state.stream << leaf; - state.checkByteLimitExceeded(); } if (!leaf) { MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute, @@ -932,7 +921,6 @@ void MetavoxelNode::writeSpanners(MetavoxelStreamState& state) const { foreach (const SharedObjectPointer& object, decodeInline(_attributeValue)) { if (static_cast(object.data())->testAndSetVisited()) { state.stream << object; - state.checkByteLimitExceeded(); } } if (!state.shouldSubdivide() || isLeaf()) { @@ -952,13 +940,11 @@ void MetavoxelNode::writeSpannerDelta(const MetavoxelNode& reference, MetavoxelS foreach (const SharedObjectPointer& object, oldSet) { if (static_cast(object.data())->testAndSetVisited() && !newSet.contains(object)) { state.stream << object; - state.checkByteLimitExceeded(); } } foreach (const SharedObjectPointer& object, newSet) { if (static_cast(object.data())->testAndSetVisited() && !oldSet.contains(object)) { state.stream << object; - state.checkByteLimitExceeded(); } } if (isLeaf() || !state.shouldSubdivide()) { diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index a0b0b6ef47..6a7ba33eb5 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -164,13 +164,6 @@ public: bool becameSubdivided() const; void setMinimum(const glm::vec3& lastMinimum, int index); - - /// Throws ByteLimitExceededException if the stream has fewer than zero bytes remaining. - void checkByteLimitExceeded(); -}; - -/// Thrown when we have exceeded the byte limit in writing. -class ByteLimitExceededException { }; /// A single node within a metavoxel layer. diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 48ce4716b7..4132270620 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -913,33 +913,30 @@ bool TestEndpoint::simulate(int iterationNumber) { return false; } Bitstream& out = _sequencer.startPacket(); + int start = _sequencer.getOutputStream().getUnderlying().device()->pos(); out << QVariant::fromValue(MetavoxelDeltaMessage()); PacketRecord* sendRecord = getLastAcknowledgedSendRecord(); - out.setBytesRemaining(_sequencer.getMaxPacketSize()); - try { - _data.writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod); - _sequencer.endPacket(); - - } catch (const ByteLimitExceededException& exception) { - _sequencer.cancelPacket(); - + _data.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->getBitstream().copyPersistentMappings(_sequencer.getOutputStream()); _reliableDeltaChannel->startMessage(); - _reliableDeltaChannel->getBitstream() << QVariant::fromValue(MetavoxelDeltaMessage()); - _data.writeDelta(sendRecord->getData(), sendRecord->getLOD(), _reliableDeltaChannel->getBitstream(), _lod); - _reliableDeltaWriteMappings = _reliableDeltaChannel->getBitstream().getAndResetWriteMappings(); - _reliableDeltaChannel->getBitstream().clearPersistentMappings(); + _reliableDeltaChannel->getBuffer().write(_sequencer.getOutgoingPacketData().constData() + start, end - start); _reliableDeltaChannel->endMessage(); + _reliableDeltaWriteMappings = out.getAndResetWriteMappings(); _reliableDeltaReceivedOffset = _reliableDeltaChannel->getBytesWritten(); _reliableDeltaData = _data; _reliableDeltaLOD = _lod; - Bitstream& out = _sequencer.startPacket(); + _sequencer.getOutputStream().getUnderlying().device()->seek(start); out << QVariant::fromValue(MetavoxelDeltaPendingMessage()); _sequencer.endPacket(); + + } else { + _sequencer.endPacket(); } } else { // enqueue some number of high priority messages From 6bf4182804506262ee3ad6dcadb478ef9af9a068 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 7 Jul 2014 16:25:13 -0700 Subject: [PATCH 025/135] Fix for send timer. --- assignment-client/src/metavoxels/MetavoxelServer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index 28f1a423c7..c601478f70 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -21,7 +21,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())); From aaae07087c4d9d9b5299834a64b737c8c17882c4 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 7 Jul 2014 18:09:50 -0700 Subject: [PATCH 026/135] Fix for local edits. --- .../metavoxels/src/MetavoxelClientManager.cpp | 27 +++++++++---------- .../metavoxels/src/MetavoxelClientManager.h | 4 +-- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp index 9abb5e9e5d..abe54873bd 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.cpp +++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp @@ -116,30 +116,26 @@ 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) { int userType = message.userType(); if (userType == MetavoxelDeltaMessage::Type) { PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord(); if (_reliableDeltaChannel) { - _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _dataLOD = _reliableDeltaLOD); + _remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _remoteDataLOD = _reliableDeltaLOD); _sequencer.getInputStream().persistReadMappings(in.getAndResetReadMappings()); in.clearPersistentMappings(); _reliableDeltaChannel = NULL; } else { - _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, - _dataLOD = getLastAcknowledgedSendRecord()->getLOD()); + _remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, + _remoteDataLOD = getLastAcknowledgedSendRecord()->getLOD()); + } + // 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) { if (!_reliableDeltaChannel) { @@ -157,5 +153,6 @@ PacketRecord* MetavoxelClient::maybeCreateSendRecord() const { } PacketRecord* MetavoxelClient::maybeCreateReceiveRecord() const { - return new PacketRecord(_dataLOD, _data); + return new PacketRecord(_remoteDataLOD, _remoteData); } + diff --git a/libraries/metavoxels/src/MetavoxelClientManager.h b/libraries/metavoxels/src/MetavoxelClientManager.h index 9ab2e2c8b0..1f37b15c18 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,7 +69,8 @@ private: MetavoxelClientManager* _manager; MetavoxelData _data; - MetavoxelLOD _dataLOD; + MetavoxelData _remoteData; + MetavoxelLOD _remoteDataLOD; ReliableChannel* _reliableDeltaChannel; MetavoxelLOD _reliableDeltaLOD; From 365bc2a93e005a4f1d1ce46702cb77b46ff3e081 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 7 Jul 2014 19:27:04 -0700 Subject: [PATCH 027/135] Need to reset the stream here. --- libraries/metavoxels/src/MetavoxelClientManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp index abe54873bd..e69794917f 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.cpp +++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp @@ -129,6 +129,7 @@ void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) { } else { _remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _remoteDataLOD = getLastAcknowledgedSendRecord()->getLOD()); + in.reset(); } // copy to local and reapply local edits _data = _remoteData; From 94e81da5575aa2aa9a3b3cb48053426404f8e5b7 Mon Sep 17 00:00:00 2001 From: TonyPeng Date: Tue, 8 Jul 2014 09:44:28 -0700 Subject: [PATCH 028/135] Added local lights to avatar. Added haze to voxel. --- interface/resources/shaders/model.frag | 19 ++- interface/resources/shaders/model.vert | 9 ++ .../resources/shaders/model_normal_map.frag | 7 +- interface/resources/shaders/skin_model.vert | 11 ++ interface/src/avatar/Avatar.cpp | 35 +++++- interface/src/avatar/Avatar.h | 14 ++- interface/src/renderer/Model.cpp | 1 + interface/src/voxels/VoxelSystem.cpp | 118 +++++++++++++++++- interface/src/voxels/VoxelSystem.h | 14 ++- 9 files changed, 218 insertions(+), 10 deletions(-) diff --git a/interface/resources/shaders/model.frag b/interface/resources/shaders/model.frag index 488736abf9..86bc10179c 100644 --- a/interface/resources/shaders/model.frag +++ b/interface/resources/shaders/model.frag @@ -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 diffuse texture uniform sampler2D diffuseMap; @@ -20,13 +22,27 @@ varying vec4 position; // the interpolated normal varying vec4 normal; +// static local light position +varying vec4 localLightPos[MAX_LOCAL_LIGHTS]; + void main(void) { // compute the base color based on OpenGL lighting model 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 < MAX_LOCAL_LIGHTS; i++) { + float localDiffuse = dot(normalizedNormal, localLightPos[i]); + float localLight = step(0.0, localDiffuse); + float localLightVal = localDiffuse * localLight; + + totalLocalLight += (localLightVal * gl_LightSource[i+1].diffuse); + } + 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))), @@ -35,4 +51,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, 0.0); + } diff --git a/interface/resources/shaders/model.vert b/interface/resources/shaders/model.vert index da7e9640d9..06ad8a3300 100644 --- a/interface/resources/shaders/model.vert +++ b/interface/resources/shaders/model.vert @@ -11,12 +11,17 @@ // 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; // the interpolated normal varying vec4 normal; +// local light position that is always present +varying vec4 localLightPos[MAX_LOCAL_LIGHTS]; + void main(void) { // transform and store the normal for interpolation @@ -36,4 +41,8 @@ void main(void) { // use standard pipeline transform gl_Position = ftransform(); + + for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) { + localLightPos[i] = gl_ModelViewMatrixInverse * gl_LightSource[i+1].position; + } } 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/skin_model.vert b/interface/resources/shaders/skin_model.vert index d68347d33d..44f33da3e2 100644 --- a/interface/resources/shaders/skin_model.vert +++ b/interface/resources/shaders/skin_model.vert @@ -13,6 +13,7 @@ const int MAX_CLUSTERS = 128; const int INDICES_PER_VERTEX = 4; +const int MAX_LOCAL_LIGHTS = 4; uniform mat4 clusterMatrices[MAX_CLUSTERS]; @@ -25,6 +26,9 @@ varying vec4 position; // the interpolated normal varying vec4 normal; +// static local light position (inverse from eye space) +varying vec4 localLightPos[MAX_LOCAL_LIGHTS]; + void main(void) { position = vec4(0.0, 0.0, 0.0, 0.0); normal = vec4(0.0, 0.0, 0.0, 0.0); @@ -34,6 +38,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); @@ -47,4 +52,10 @@ void main(void) { gl_TexCoord[1] = vec4(dot(gl_EyePlaneS[0], position), dot(gl_EyePlaneT[0], position), dot(gl_EyePlaneR[0], position), 1.0); gl_Position = gl_ProjectionMatrix * position; + + // inverse view to make the light source position static + for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) { + localLightPos[i] = gl_ModelViewMatrixInverse * gl_LightSource[i+1].position; + } + } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 9b136980f4..38dbb02a14 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,16 @@ void Avatar::init() { _initialized = true; _shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE); initializeHair(); + + for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) { + _localLightColors[i] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); + _localLightDirections[i] = glm::vec3(0.0f, 0.0f, 1.0f); + } + + // initialize first light + _localLightColors[0].r = 0.2f; _localLightColors[0].g = 0.2f, _localLightColors[0].b = 0.2f; + _localLightDirections[0].x = 1.0f; _localLightDirections[0].y = 0.0f; _localLightDirections[0].z = 0.0f; + } glm::vec3 Avatar::getChestPosition() const { @@ -219,7 +230,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 +241,17 @@ 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; - + + + for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) { + glm::vec3 normalized = glm::normalize(_localLightDirections[i]); + glm::vec4 localLight = glm::vec4(normalized, 1.0f); + + // local light parameters + glLightfv(GL_LIGHT1 + i, GL_POSITION, glm::value_ptr(localLight)); + glLightfv(GL_LIGHT1 + i, GL_DIFFUSE, glm::value_ptr(_localLightColors[i])); + } + // render body if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { renderBody(renderMode, glowLevel); @@ -1107,3 +1128,13 @@ 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::vec4& color, int lightIndex) { + _localLightColors[lightIndex] = color; + qDebug( "set light %d color ( %f, %f, %f )\n", lightIndex, color.x, color.y, color.z ); +} + diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index f20db1019d..059264d43a 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -36,6 +36,8 @@ const int HAIR_STRANDS = 150; // Number of strands of hair const int HAIR_LINKS = 10; // Number of links in a hair strand const int HAIR_MAX_CONSTRAINTS = 2; // Hair verlet is connected to at most how many others +const int MAX_LOCAL_LIGHTS = 6; + enum DriveKeys { FWD = 0, BACK, @@ -154,7 +156,9 @@ public: public slots: void updateCollisionGroups(); - + void setLocalLightDirection(const glm::vec3& direction, int lightIndex); + void setLocalLightColor(const glm::vec4& color, int lightIndex); + signals: void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision); @@ -174,9 +178,13 @@ 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::vec4 _localLightColors[MAX_LOCAL_LIGHTS]; + // protected methods... glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; } glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; } diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 723297f6b4..6fe57ea705 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1488,6 +1488,7 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re if (cascadedShadows) { program->setUniform(skinLocations->shadowDistances, Application::getInstance()->getShadowDistances()); } + } else { glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]); program->bind(); diff --git a/interface/src/voxels/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp index ca79967109..109d561306 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" @@ -67,7 +69,13 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels, VoxelTree* tree) _inOcclusions(false), _showCulledSharedFaces(false), _usePrimitiveRenderer(false), - _renderer(0) + _renderer(0), + _drawHaze(true), + _updateHaze(false), + _farHazeDistance(300.0f), + _hazeColor(0.24f, 0.27f, 0.34f), + _lastHazeCameraPosition(0.0f, 0.0f, 0.0f), + _lastYawAngle(0.0f) { _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0; @@ -108,6 +116,9 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels, VoxelTree* tree) _lastKnownVoxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE; _lastKnownBoundaryLevelAdjust = 0; + + _voxelColors = NULL; + _voxelPositions = NULL; } void VoxelSystem::elementDeleted(OctreeElement* element) { @@ -373,6 +384,9 @@ void VoxelSystem::cleanupVoxelMemory() { delete[] _readVoxelDirtyArray; _writeVoxelDirtyArray = _readVoxelDirtyArray = NULL; _readArraysLock.unlock(); + + delete[] _voxelColors; + delete[] _voxelPositions; } } @@ -521,11 +535,17 @@ void VoxelSystem::initVoxelMemory() { _shadowDistancesLocation = _cascadedShadowMapProgram.uniformLocation("shadowDistances"); _cascadedShadowMapProgram.release(); } + } _renderer = new PrimitiveRenderer(_maxVoxels); _initialized = true; + _voxelColors = new xColor[_maxVoxels]; + memset(_voxelColors, 0, sizeof(xColor) *_maxVoxels); + + _voxelPositions = new glm::vec3[_maxVoxels]; + _writeArraysLock.unlock(); _readArraysLock.unlock(); } @@ -1114,6 +1134,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()); } @@ -1132,10 +1153,24 @@ int VoxelSystem::updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, boo void VoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex, float voxelScale, const nodeColor& color) { +// if (nodeIndex < 0 || nodeIndex > 1000) { +// return; +// } + if (_initialized && nodeIndex <= _maxVoxels) { _writeVoxelDirtyArray[nodeIndex] = true; - + + // cache the colors and position + _voxelColors[nodeIndex].red = color[0]; + _voxelColors[nodeIndex].green = color[1]; + _voxelColors[nodeIndex].blue = color[2]; + + // scaled voxel position + _voxelPositions[nodeIndex] = startVertex * (float)TREE_SCALE; + if (_useVoxelShader) { + // write in position, scale, and color for the voxel + if (_writeVoxelShaderData) { VoxelShaderVBOData* writeVerticesAt = &_writeVoxelShaderData[nodeIndex]; writeVerticesAt->x = startVertex.x * TREE_SCALE; @@ -1157,9 +1192,85 @@ void VoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& } } } + + // want new color for haze + if (_drawHaze) { + _updateHaze = true; + } } } +void VoxelSystem::updateHazeColors() { + + // only update when player moves + if (_lastHazeCameraPosition != Application::getInstance()->getAvatar()->getPosition()) { + _updateHaze = true; + _lastHazeCameraPosition = Application::getInstance()->getAvatar()->getPosition(); + } + + // update when yaw angle changes + float avatarYaw = Application::getInstance()->getAvatar()->getBodyYaw(); + if (_lastYawAngle != avatarYaw) { + _updateHaze = true; + _lastYawAngle = avatarYaw; + } + + if (!_updateHaze) { + return; + } + + glm::vec3 cameraPosition = Application::getInstance()->getAvatar()->getPosition(); + float* hazeColor = glm::value_ptr(_hazeColor); + GLubyte* writeColorsAt = _writeColorsArray; + + // update voxel color + int vertexPointsPerVoxel = GLOBAL_NORMALS_VERTEX_POINTS_PER_VOXEL; + for (int i = 0; i < _voxelsInWriteArrays; i++) { + + float distanceToCamera = glm::length(_voxelPositions[i] - cameraPosition); + if(distanceToCamera > _farHazeDistance) { + distanceToCamera = _farHazeDistance; + } + + // how much haze there is at the distance + float hazeStrength = 1.0f - distanceToCamera / _farHazeDistance; + + // [0, 1] clamp + if (hazeStrength > 1.0f) { + hazeStrength = 1.0f; + } + else if (hazeStrength < 0.0f) { + hazeStrength = 0.0f; + } + + // color [0.0, 1.0] + float floatColor[] = {(float)_voxelColors[i].red / 255.0f, (float)_voxelColors[i].green / 255.0f, (float)_voxelColors[i].blue / 255.0f }; + assert(i >= 0 && i < _maxVoxels); + + // color * haze_strength + haze_color * (1.0 - haze_strength) + float oneMinusHazeStrength = 1.0f - hazeStrength; + nodeColor colorWithHaze = { + (unsigned char)((floatColor[0] * hazeStrength + hazeColor[0] * oneMinusHazeStrength) * 255.0f), + (unsigned char)((floatColor[1] * hazeStrength + hazeColor[1] * oneMinusHazeStrength) * 255.0f), + (unsigned char)((floatColor[2] * hazeStrength + hazeColor[2] * oneMinusHazeStrength) * 255.0f), + }; + + for (int j = 0; j < vertexPointsPerVoxel/3; j++ ) { + *(writeColorsAt + j*3) = colorWithHaze[0]; + *(writeColorsAt + j*3+1) = colorWithHaze[1]; + *(writeColorsAt + j*3+2) = colorWithHaze[2]; + } + + writeColorsAt += vertexPointsPerVoxel; + } + + copyWrittenDataToReadArraysFullVBOs(); + _voxelsDirty = true; + _readRenderFullVBO = true; + _updateHaze = false; + +} + glm::vec3 VoxelSystem::computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const { const float* identityVertex = identityVertices + index * 3; return startVertex + glm::vec3(identityVertex[0], identityVertex[1], identityVertex[2]) * voxelScale; @@ -1340,6 +1451,9 @@ void VoxelSystem::render() { return; } + if (!_useVoxelShader && _drawHaze) { + updateHazeColors(); + } updateVBOs(); // if not don't... then do... diff --git a/interface/src/voxels/VoxelSystem.h b/interface/src/voxels/VoxelSystem.h index ae8752605a..08f44a4d92 100644 --- a/interface/src/voxels/VoxelSystem.h +++ b/interface/src/voxels/VoxelSystem.h @@ -273,7 +273,19 @@ 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 + xColor* _voxelColors; ///< Cached Voxel Colors + glm::vec3* _voxelPositions; ///< Cached Voxel Positions + bool _drawHaze; + bool _updateHaze; + float _farHazeDistance; + glm::vec3 _hazeColor; + glm::vec3 _lastHazeCameraPosition; + float _lastYawAngle; + + + void updateHazeColors(); }; #endif // hifi_VoxelSystem_h From 171660ded7db19ed6ac7f44cdde6f5719c40ecca Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 8 Jul 2014 09:52:50 -0700 Subject: [PATCH 029/135] fix stubbing of MIDIManager if HAVE_RTMIDI not defined --- interface/src/devices/MIDIManager.cpp | 12 +++++++----- interface/src/devices/MIDIManager.h | 10 ++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) 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..f9bd8ed4c7 100644 --- a/interface/src/devices/MIDIManager.h +++ b/interface/src/devices/MIDIManager.h @@ -12,15 +12,17 @@ #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 @@ -46,11 +48,11 @@ signals: void midiEvent(const MIDIEvent& event); private: +#ifdef HAVE_RTMIDI RtMidiIn* _midiInput; +#endif HAVE_RTMIDI }; -#endif - #endif // hifi_MIDIManager_h From 286d50f4f2879b3a1c8c94dcf67be08253ce5e25 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 8 Jul 2014 09:55:01 -0700 Subject: [PATCH 030/135] fix if/endif in MIDIManager header --- interface/src/devices/MIDIManager.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interface/src/devices/MIDIManager.h b/interface/src/devices/MIDIManager.h index f9bd8ed4c7..e77c63b56f 100644 --- a/interface/src/devices/MIDIManager.h +++ b/interface/src/devices/MIDIManager.h @@ -18,9 +18,7 @@ #include #ifdef HAVE_RTMIDI - #include - #endif class MIDIManager : public QObject { @@ -50,7 +48,7 @@ signals: private: #ifdef HAVE_RTMIDI RtMidiIn* _midiInput; -#endif HAVE_RTMIDI +#endif }; From db1a9fa09b40c36ef0e751323db066880211b2df Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 8 Jul 2014 10:14:41 -0700 Subject: [PATCH 031/135] Fix for streaming error. --- libraries/metavoxels/src/MetavoxelClientManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp index e69794917f..f16b6c2396 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.cpp +++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp @@ -143,6 +143,9 @@ void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) { _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); From e33b07819c75ef47fadc63c0d6a7def35c66c4f4 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 8 Jul 2014 10:24:21 -0700 Subject: [PATCH 032/135] wrap a missed midi method --- interface/src/devices/MIDIManager.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/devices/MIDIManager.h b/interface/src/devices/MIDIManager.h index e77c63b56f..9fc55d11da 100644 --- a/interface/src/devices/MIDIManager.h +++ b/interface/src/devices/MIDIManager.h @@ -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; } From 1baa187a668e240b0ca8f33ea831c407f4a05ac5 Mon Sep 17 00:00:00 2001 From: TonyPeng Date: Tue, 8 Jul 2014 10:37:42 -0700 Subject: [PATCH 033/135] add javascript example for changing avatar's local lights. --- examples/avatarLocalLight.js | 125 +++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 examples/avatarLocalLight.js diff --git a/examples/avatarLocalLight.js b/examples/avatarLocalLight.js new file mode 100644 index 0000000000..600713a037 --- /dev/null +++ b/examples/avatarLocalLight.js @@ -0,0 +1,125 @@ +// +// 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, w: 1.0}, {x: 0.0, y:0.0, z: 0.0, w: 1.0}, {x: 0.0, y:0.0, z: 0.0, w: 1.0}, {x: 0.0, y:0.0, z: 0.0, w: 1.0} ]; + +var currentSelection = 0; +var currentNumLights = 1; + +function keyPressEvent(event) { + + var choice = parseInt(event.text); + + if (event.text == "1") { + currentSelection = 0; + print("selection = " + currentSelection); + } + else if (event.text == "2" ) { + currentSelection = 1; + print("selection = " + currentSelection); + } + else if (event.text == "3" ) { + currentSelection = 2; + print("selection = " + currentSelection); + } + else if (event.text == "4" ) { + currentSelection = 3; + print("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 < 4) { + + // default light + localLightColors[currentNumLights].x = 0.1; + localLightColors[currentNumLights].y = 0.1; + localLightColors[currentNumLights].z = 0.1; + + 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.setLocalLightColor(localLightColors[currentNumLights - 1], currentNumLights - 1); + + --currentNumLights; + } + } + +} + +Controller.keyPressEvent.connect(keyPressEvent); From 2e73ac8bc116b23ba42e841c1d1d39b3a24f330d Mon Sep 17 00:00:00 2001 From: wangyix Date: Tue, 8 Jul 2014 11:16:39 -0700 Subject: [PATCH 034/135] changed Audio.cpp to not overflow _audioOutput buffer, and 2 other things added _consecutiveNotMixedCount to prevent premature injector stream deletion; made silent-frame drop only occur in dynamic jitter buffer mode --- .../src/audio/AudioMixerClientData.cpp | 5 ++++- interface/src/Audio.cpp | 20 ++++++++++--------- .../audio/src/PositionalAudioRingBuffer.cpp | 10 +++++++--- .../audio/src/PositionalAudioRingBuffer.h | 6 +++--- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index f6437f9c97..d3883501d6 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -138,11 +138,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(); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 6bbd769d25..c604040bc8 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -67,7 +67,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), @@ -869,14 +869,11 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) { _numFramesDisplayStarve = 10; } - // 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 numSamplesAudioOutputRoomFor = _audioOutput->bytesFree() / sizeof(int16_t); + int 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()); @@ -885,6 +882,11 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) { // We are still waiting for enough samples to begin playback // qDebug() << numNetworkOutputSamples << " samples so far, waiting for " << numSamplesNeededToStartPlayback; } 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/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 0fe75f1239..6b3a1eb94f 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -99,7 +99,8 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer:: _listenerUnattenuatedZone(NULL), _desiredJitterBufferFrames(1), _currentJitterBufferFrames(-1), - _dynamicJitterBuffers(dynamicJitterBuffers) + _dynamicJitterBuffers(dynamicJitterBuffers), + _consecutiveNotMixedCount(0) { } @@ -129,7 +130,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(); @@ -206,11 +207,12 @@ 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 @@ -222,6 +224,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() { // reset our _shouldOutputStarveDebug to true so the next is printed _shouldOutputStarveDebug = true; + _consecutiveNotMixedCount++; return false; } @@ -231,6 +234,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() { // minus one (since a frame will be read immediately after this) is the length of the jitter buffer _currentJitterBufferFrames = samplesAvailable() / samplesPerFrame - 1; _isStarved = false; + _consecutiveNotMixedCount = 0; } // since we've read data from ring buffer at least once - we've started diff --git a/libraries/audio/src/PositionalAudioRingBuffer.h b/libraries/audio/src/PositionalAudioRingBuffer.h index 0322afb47b..31b0524b3b 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.h +++ b/libraries/audio/src/PositionalAudioRingBuffer.h @@ -83,6 +83,8 @@ public: int getDesiredJitterBufferFrames() const { return _desiredJitterBufferFrames; } int getCurrentJitterBufferFrames() const { return _currentJitterBufferFrames; } + int getConsecutiveNotMixedCount() const { return _consecutiveNotMixedCount; } + protected: // disallow copying of PositionalAudioRingBuffer objects PositionalAudioRingBuffer(const PositionalAudioRingBuffer&); @@ -107,9 +109,7 @@ protected: bool _dynamicJitterBuffers; // extra stats - int _starveCount; - int _silentFramesDropped; - + int _consecutiveNotMixedCount; }; #endif // hifi_PositionalAudioRingBuffer_h From 0e117966f703e51e505eff1c25a27b6fc50d5030 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 8 Jul 2014 11:43:06 -0700 Subject: [PATCH 035/135] better clapping --- examples/clap.js | 153 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 109 insertions(+), 44 deletions(-) diff --git a/examples/clap.js b/examples/clap.js index 9da36ba094..7a2a77afc4 100644 --- a/examples/clap.js +++ b/examples/clap.js @@ -10,27 +10,34 @@ // 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 startEndFrames = []; +startEndFrames.push({ start: 0, end: 8}); +startEndFrames.push({ start: 10, end: 20}); +startEndFrames.push({ start: 20, end: 28}); +startEndFrames.push({ start: 30, end: 37}); +startEndFrames.push({ start: 41, end: 46}); +startEndFrames.push({ start: 53, end: 58}); +var lastClapFrame = 0; +var lastAnimFrame = 0; -function printVector(v) { - print(v.x + ", " + v.y + ", " + v.z + "\n"); -} +var claps = []; +claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap1Reverb.wav")); +claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap2Reverb.wav")); +claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap3Reverb.wav")); +claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap4Reverb.wav")); +claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap5Reverb.wav")); +claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap6Reverb.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 clickClappingNow = false; +var CLAP_START_RATE = 15.0; +var clapRate = CLAP_START_RATE; +var startedTimer = false; var clapping = new Array(); clapping[0] = false; @@ -38,36 +45,94 @@ clapping[1] = 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) { + print("frame " + frame); + 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); +} + +function keepClapping() { + playClap(1.0, Camera.getPosition()); +} + +Controller.keyPressEvent.connect(function(event) { + if(event.text == "SHIFT") { + if (!clickClappingNow) { + playClap(1.0, Camera.getPosition()); + var whichClip = Math.floor(Math.random() * startEndFrames.length); + lastClapFrame = 0; + MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false); + clickClappingNow = true; + } else { + clapRate *= 1.25; + MyAvatar.stopAnimation(clapAnimation); + MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false); + collectedClicks = collectedClicks + 1; + } + } +}); + +var CLAP_END_WAIT_MSECS = 500; +Controller.keyReleaseEvent.connect(function(event) { + if (event.text == "SHIFT") { + if (!startedTimer) { + startedTimer = true; + collectedClicks = 0; + Script.setTimeout(stopClapping, CLAP_END_WAIT_MSECS); + } + } +}); + +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 -Script.update.connect(maybePlaySound); \ No newline at end of file +Script.update.connect(maybePlaySound); +//Controller.keyPressEvent.connect(keyPressEvent); \ No newline at end of file From cc63f706a745dbc1c2c57aa9d405752526ec6b6f Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Tue, 8 Jul 2014 11:43:24 -0700 Subject: [PATCH 036/135] Functional sixense "laser" oculus selection mode. --- interface/src/devices/OculusManager.cpp | 14 +- interface/src/devices/SixenseManager.cpp | 28 ++- interface/src/ui/ApplicationOverlay.cpp | 238 ++++++++++++++++++----- interface/src/ui/ApplicationOverlay.h | 1 + 4 files changed, 215 insertions(+), 66 deletions(-) diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index befe48e42d..976bd5ba8e 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -443,7 +443,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 } @@ -461,7 +469,7 @@ QSize OculusManager::getRenderTargetSize() { void OculusManager::renderLaserPointer() { #ifdef HAVE_LIBOVR - const float PALM_TIP_ROD_RADIUS = 0.009f; + const float PALM_TIP_ROD_RADIUS = 0.002f; MyAvatar* myAvatar = Application::getInstance()->getAvatar(); @@ -482,8 +490,8 @@ void OculusManager::renderLaserPointer() { glm::vec3 OculusManager::getLaserPointerTipPosition(const PalmData* palm) { #ifdef HAVE_LIBOVR - const float PALM_TIP_ROD_LENGTH_MULT = 2.0f; - return (palm->getTipPosition() - palm->getPosition()) * PALM_TIP_ROD_LENGTH_MULT; + const float PALM_TIP_ROD_LENGTH_MULT = 11.0f; + return palm->getPosition() + (palm->getTipPosition() - palm->getPosition()) * PALM_TIP_ROD_LENGTH_MULT; #endif return glm::vec3(0.0f); } diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index c50fc887d6..ecf7fea5cb 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -13,6 +13,7 @@ #include "Application.h" #include "SixenseManager.h" +#include "devices/OculusManager.h" #include "UserActivityLogger.h" #ifdef HAVE_SIXENSE @@ -358,9 +359,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,15 +371,24 @@ 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); + printf("CLICK: %d %d\n", pos.x(), pos.y()); + } 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. diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 42c4f36470..64aafbb49c 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -205,6 +205,103 @@ 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; + } +} + + +QPoint ApplicationOverlay::getOculusPalmClickLocation(PalmData *palm) const { + + Application* application = Application::getInstance(); + QGLWidget* glWidget = application->getGLWidget(); + MyAvatar* myAvatar = application->getAvatar(); + + const int widgetWidth = glWidget->width(); + const int widgetHeight = glWidget->height(); + + + 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(eyePos - tip); //direction of ray goes towards camera + glm::vec3 tipPos = orientation * (tip - eyePos); + + QPoint rv; + + float t; + + //Find intersection of crosshair ray + if (raySphereIntersect(dir, tipPos, 1, &t)){ + glm::vec3 collisionPos = tipPos + dir * t; + + 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; +} + // Draws the FBO texture for Oculus rift. void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { @@ -221,11 +318,14 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { 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); + renderControllerPointersOculus(); + + glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture()); + glMatrixMode(GL_MODELVIEW); glPushMatrix(); @@ -277,8 +377,6 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { renderTexturedHemisphere(); - renderControllerPointersOculus(); - glPopMatrix(); glDepthMask(GL_TRUE); @@ -559,22 +657,6 @@ void ApplicationOverlay::renderControllerPointers() { } } -bool findSphereIntersection(const glm::vec3 &start, const glm::vec3 &end, const glm::vec3 &spos, const float r, glm::vec3 &result) { - double a = pow(end.x - start.x, 2) + pow(end.y - start.y, 2) + pow(end.z - start.z, 2); - double b = 2.0f * ((end.x - start.x) * (start.x - spos.x) + (end.y - start.x) * (start.y - spos.y) + (end.z - start.z) * (start.z - spos.z)); - double c = pow(start.x - spos.x, 2) + pow(start.y - spos.y, 2) + pow(start.z - spos.z, 2) - r*r; - - double delta = b * b - 4.0 * a * c; - printf("Intersection Delta %lf\n", delta); - - if (delta == 0) { - float d = -b / (2.0 * a); - result = start + d * (end - start); - } else { - return false; - } -} - void ApplicationOverlay::renderControllerPointersOculus() { const bool useLaser = true; @@ -589,54 +671,104 @@ void ApplicationOverlay::renderControllerPointersOculus() { glBindTexture(GL_TEXTURE_2D, _crosshairTexture); glDisable(GL_DEPTH_TEST); - - for (int i = 0; i < NUMBER_OF_MAGNIFIERS; i++) { - if (i > 0 && useLaser) { - MyAvatar* myAvatar = application->getAvatar(); + glMatrixMode(GL_MODELVIEW); + MyAvatar* myAvatar = application->getAvatar(); + + //Determine how much we need to iterate + const int ITERATIONS = max(myAvatar->getHand()->getNumPalms(), 3); + + for (int i = 0; i < ITERATIONS; i++) { + if (useLaser && i < myAvatar->getHand()->getNumPalms()) { + PalmData& palm = myAvatar->getHand()->getPalms()[i]; if (palm.isActive()) { - glm::vec3 tip = OculusManager::getLaserPointerTipPosition(&palm); glm::vec3 result; + glm::vec3 eyePos = myAvatar->getHead()->calculateAverageEyePosition(); + glm::quat orientation = glm::inverse(myAvatar->getOrientation()); + glm::vec3 dir = orientation * glm::normalize(eyePos - tip); //direction of ray goes towards camera + glm::vec3 tipPos = (tip - eyePos); + + float t; + float length = glm::length(eyePos - tip); + float size = 0.045f * length; - if (findSphereIntersection(myAvatar->getHead()->calculateAverageEyePosition(), - tip, glm::vec3(0, 0, 0), 1, result)){ - printf("Intersection Found: "); - printVector(result); + glm::vec3 up = glm::vec3(0.0, 1.0, 0.0) * size; + glm::vec3 right = glm::vec3(1.0, 0.0, 0.0) * size; + + glm::vec3 cursorVerts[4]; + cursorVerts[0] = -right + up; + cursorVerts[1] = right + up; + cursorVerts[2] = right - up; + cursorVerts[3] = -right - up; + + glPushMatrix(); + // glLoadIdentity(); + + // 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); + + // This is the original lookAt vector for the object + // in world coordinates + glm::vec3 direction(0, 0, 1); + glTranslatef(tip.x, tip.y, tip.z); + + // 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); + + // perform the rotation. The if statement is used for stability reasons + // if the lookAt and objToCamProj vectors are too close together then + // |angleCosine| could be bigger than 1 due to lack of precision + if ((angleCosine < 0.999999) && (angleCosine > -0.999999)) { + glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, upAux[0], upAux[1], upAux[2]); } + // so far it is just like the cylindrical billboard. The code for the + // second rotation comes now + // The second part tilts the object so that it faces the camera - //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); + // objToCam is the vector in world coordinates from + // the local origin to the camera + glm::vec3 cursorToCamera = glm::normalize(-tipPos); - //float dist; - ////Bottom Left - //dist = sqrt(lX * lX + bY * bY); - //float blZ = sqrt(1.0f - dist * dist); - ////Top Left - //dist = sqrt(lX * lX + tY * tY); - //float tlZ = sqrt(1.0f - dist * dist); - ////Bottom Right - //dist = sqrt(rX * rX + bY * bY); - //float brZ = sqrt(1.0f - dist * dist); - ////Top Right - //dist = sqrt(rX * rX + tY * tY); - //float trZ = sqrt(1.0f - dist * dist); + // Compute the angle between objToCamProj and objToCam, + //i.e. compute the required angle for the lookup vector - //glBegin(GL_QUADS); + angleCosine = glm::dot(cursorToCameraXZ, cursorToCamera); - //glColor4f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], _alpha); + // Tilt the object. The test is done to prevent instability + // when objToCam and objToCamProj have a very small + // angle between them - //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); + if ((angleCosine < 0.99990) && (angleCosine > -0.9999)) + if (cursorToCamera.y < 0) { + glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, 1, 0, 0); + } else { + glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, -1, 0, 0); + } - //glEnd(); + 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(); } - } else { + } else if (i < NUMBER_OF_MAGNIFIERS) { //Dont render the reticle if its inactive if (!_reticleActive[i]) { continue; diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 7c1f87d575..ebac5f3cfc 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -32,6 +32,7 @@ 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(PalmData *palm) const; // Getters QOpenGLFramebufferObject* getFramebufferObject(); From 84aa4b9cda859f73751b3a3f8573bfb379d2cb60 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 8 Jul 2014 11:59:10 -0700 Subject: [PATCH 037/135] use compiler default C++ Standard Library on OS X --- CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) 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) From d1afe127c3e47db8d754ce2afc92900391de702d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 8 Jul 2014 12:06:43 -0700 Subject: [PATCH 038/135] updates to RtMidi find module to look for static or dynamic lib --- cmake/modules/FindRtMidi.cmake | 4 ++-- interface/CMakeLists.txt | 28 +++++++++++++--------------- interface/external/rtmidi/readme.txt | 4 +++- 3 files changed, 18 insertions(+), 18 deletions(-) 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/interface/CMakeLists.txt b/interface/CMakeLists.txt index 68ba2761aa..49a6da7438 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -111,16 +111,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}) @@ -151,6 +141,7 @@ find_package(Sixense) find_package(Visage) 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) @@ -223,11 +214,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. From 6ee52e97c80c584c0e0c959cb587dfc8620b4a16 Mon Sep 17 00:00:00 2001 From: wangyix Date: Tue, 8 Jul 2014 12:23:43 -0700 Subject: [PATCH 039/135] added dev menu option to disable _audioOutput overflow check --- interface/src/Audio.cpp | 11 ++++++++--- interface/src/Menu.cpp | 4 ++++ interface/src/Menu.h | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index c604040bc8..cc455c5544 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -869,9 +869,14 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) { _numFramesDisplayStarve = 10; } - int numSamplesAudioOutputRoomFor = _audioOutput->bytesFree() / sizeof(int16_t); - int numNetworkOutputSamples = std::min(_ringBuffer.samplesAvailable(), (int)(numSamplesAudioOutputRoomFor * networkOutputToOutputRatio)); - + 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) { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 6b32eb5770..69d95b34db 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -428,6 +428,10 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false); + QMenu* audioOptionsMenu = developerMenu->addMenu("Audio Options"); + + addCheckableActionToQMenuAndActionHash(audioOptionsMenu, MenuOption::DisableQAudioOutputOverflowCheck, 0, false); + addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisableNackPackets, 0, false); addDisabledActionAndSeparator(developerMenu, "Testing"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 06b5c5c9f4..e8146f8038 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -343,6 +343,7 @@ 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"; From 4e04cecd8c9b3b7291aa63d0b41d49b68d7e69f5 Mon Sep 17 00:00:00 2001 From: TonyPeng Date: Tue, 8 Jul 2014 12:28:44 -0700 Subject: [PATCH 040/135] Use glFog for haze (better performance). --- interface/src/voxels/VoxelSystem.cpp | 122 ++++----------------------- interface/src/voxels/VoxelSystem.h | 8 -- 2 files changed, 16 insertions(+), 114 deletions(-) diff --git a/interface/src/voxels/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp index 109d561306..ee8b306ef3 100644 --- a/interface/src/voxels/VoxelSystem.cpp +++ b/interface/src/voxels/VoxelSystem.cpp @@ -70,12 +70,9 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels, VoxelTree* tree) _showCulledSharedFaces(false), _usePrimitiveRenderer(false), _renderer(0), - _drawHaze(true), - _updateHaze(false), + _drawHaze(false), _farHazeDistance(300.0f), - _hazeColor(0.24f, 0.27f, 0.34f), - _lastHazeCameraPosition(0.0f, 0.0f, 0.0f), - _lastYawAngle(0.0f) + _hazeColor(0.24f, 0.27f, 0.34f) { _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0; @@ -116,9 +113,6 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels, VoxelTree* tree) _lastKnownVoxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE; _lastKnownBoundaryLevelAdjust = 0; - - _voxelColors = NULL; - _voxelPositions = NULL; } void VoxelSystem::elementDeleted(OctreeElement* element) { @@ -384,9 +378,7 @@ void VoxelSystem::cleanupVoxelMemory() { delete[] _readVoxelDirtyArray; _writeVoxelDirtyArray = _readVoxelDirtyArray = NULL; _readArraysLock.unlock(); - - delete[] _voxelColors; - delete[] _voxelPositions; + } } @@ -468,6 +460,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 @@ -540,14 +533,21 @@ void VoxelSystem::initVoxelMemory() { _renderer = new PrimitiveRenderer(_maxVoxels); _initialized = true; - - _voxelColors = new xColor[_maxVoxels]; - memset(_voxelColors, 0, sizeof(xColor) *_maxVoxels); - - _voxelPositions = new glm::vec3[_maxVoxels]; _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_DENSITY, 0.25f); + glHint(GL_FOG_HINT, GL_DONT_CARE); + glFogf(GL_FOG_START, 20.0f); + glFogf(GL_FOG_END, _farHazeDistance); + glEnable(GL_FOG); + } } int VoxelSystem::parseData(const QByteArray& packet) { @@ -1152,22 +1152,10 @@ int VoxelSystem::updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, boo void VoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex, float voxelScale, const nodeColor& color) { - -// if (nodeIndex < 0 || nodeIndex > 1000) { -// return; -// } if (_initialized && nodeIndex <= _maxVoxels) { _writeVoxelDirtyArray[nodeIndex] = true; - // cache the colors and position - _voxelColors[nodeIndex].red = color[0]; - _voxelColors[nodeIndex].green = color[1]; - _voxelColors[nodeIndex].blue = color[2]; - - // scaled voxel position - _voxelPositions[nodeIndex] = startVertex * (float)TREE_SCALE; - if (_useVoxelShader) { // write in position, scale, and color for the voxel @@ -1193,84 +1181,9 @@ void VoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& } } - // want new color for haze - if (_drawHaze) { - _updateHaze = true; - } } } -void VoxelSystem::updateHazeColors() { - - // only update when player moves - if (_lastHazeCameraPosition != Application::getInstance()->getAvatar()->getPosition()) { - _updateHaze = true; - _lastHazeCameraPosition = Application::getInstance()->getAvatar()->getPosition(); - } - - // update when yaw angle changes - float avatarYaw = Application::getInstance()->getAvatar()->getBodyYaw(); - if (_lastYawAngle != avatarYaw) { - _updateHaze = true; - _lastYawAngle = avatarYaw; - } - - if (!_updateHaze) { - return; - } - - glm::vec3 cameraPosition = Application::getInstance()->getAvatar()->getPosition(); - float* hazeColor = glm::value_ptr(_hazeColor); - GLubyte* writeColorsAt = _writeColorsArray; - - // update voxel color - int vertexPointsPerVoxel = GLOBAL_NORMALS_VERTEX_POINTS_PER_VOXEL; - for (int i = 0; i < _voxelsInWriteArrays; i++) { - - float distanceToCamera = glm::length(_voxelPositions[i] - cameraPosition); - if(distanceToCamera > _farHazeDistance) { - distanceToCamera = _farHazeDistance; - } - - // how much haze there is at the distance - float hazeStrength = 1.0f - distanceToCamera / _farHazeDistance; - - // [0, 1] clamp - if (hazeStrength > 1.0f) { - hazeStrength = 1.0f; - } - else if (hazeStrength < 0.0f) { - hazeStrength = 0.0f; - } - - // color [0.0, 1.0] - float floatColor[] = {(float)_voxelColors[i].red / 255.0f, (float)_voxelColors[i].green / 255.0f, (float)_voxelColors[i].blue / 255.0f }; - assert(i >= 0 && i < _maxVoxels); - - // color * haze_strength + haze_color * (1.0 - haze_strength) - float oneMinusHazeStrength = 1.0f - hazeStrength; - nodeColor colorWithHaze = { - (unsigned char)((floatColor[0] * hazeStrength + hazeColor[0] * oneMinusHazeStrength) * 255.0f), - (unsigned char)((floatColor[1] * hazeStrength + hazeColor[1] * oneMinusHazeStrength) * 255.0f), - (unsigned char)((floatColor[2] * hazeStrength + hazeColor[2] * oneMinusHazeStrength) * 255.0f), - }; - - for (int j = 0; j < vertexPointsPerVoxel/3; j++ ) { - *(writeColorsAt + j*3) = colorWithHaze[0]; - *(writeColorsAt + j*3+1) = colorWithHaze[1]; - *(writeColorsAt + j*3+2) = colorWithHaze[2]; - } - - writeColorsAt += vertexPointsPerVoxel; - } - - copyWrittenDataToReadArraysFullVBOs(); - _voxelsDirty = true; - _readRenderFullVBO = true; - _updateHaze = false; - -} - glm::vec3 VoxelSystem::computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const { const float* identityVertex = identityVertices + index * 3; return startVertex + glm::vec3(identityVertex[0], identityVertex[1], identityVertex[2]) * voxelScale; @@ -1451,9 +1364,6 @@ void VoxelSystem::render() { return; } - if (!_useVoxelShader && _drawHaze) { - updateHazeColors(); - } updateVBOs(); // if not don't... then do... diff --git a/interface/src/voxels/VoxelSystem.h b/interface/src/voxels/VoxelSystem.h index 08f44a4d92..9f61e0579c 100644 --- a/interface/src/voxels/VoxelSystem.h +++ b/interface/src/voxels/VoxelSystem.h @@ -275,17 +275,9 @@ private: static unsigned char _sOctantIndexToSharedBitMask[8][8]; ///< Map octant indices to shared partition mask // haze - xColor* _voxelColors; ///< Cached Voxel Colors - glm::vec3* _voxelPositions; ///< Cached Voxel Positions bool _drawHaze; - bool _updateHaze; float _farHazeDistance; glm::vec3 _hazeColor; - glm::vec3 _lastHazeCameraPosition; - float _lastYawAngle; - - - void updateHazeColors(); }; #endif // hifi_VoxelSystem_h From 0ce7b178b081d76816623b901cb3fa0d00e1efdc Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Tue, 8 Jul 2014 12:47:41 -0700 Subject: [PATCH 041/135] Made Oculus UI framebuffer resize when window resizes --- interface/src/ui/ApplicationOverlay.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 64aafbb49c..dfa6b63fa5 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -95,6 +95,8 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { glMatrixMode(GL_PROJECTION); glPushMatrix(); + printf("%d %d\n", glWidget->width(), glWidget->height()); + glLoadIdentity(); gluOrtho2D(0, glWidget->width(), glWidget->height(), 0); glDisable(GL_DEPTH_TEST); @@ -205,7 +207,6 @@ 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) @@ -1194,8 +1195,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) { + if (_framebufferObject){ + 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); From e212f9f42eb56e7faf4722241f5eef8cd4090a94 Mon Sep 17 00:00:00 2001 From: TonyPeng Date: Tue, 8 Jul 2014 12:48:01 -0700 Subject: [PATCH 042/135] Change fog start to use default value. --- interface/src/voxels/VoxelSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/voxels/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp index ee8b306ef3..e76a1cee7f 100644 --- a/interface/src/voxels/VoxelSystem.cpp +++ b/interface/src/voxels/VoxelSystem.cpp @@ -544,7 +544,7 @@ void VoxelSystem::initVoxelMemory() { glFogfv(GL_FOG_COLOR, fogColor); glFogf(GL_FOG_DENSITY, 0.25f); glHint(GL_FOG_HINT, GL_DONT_CARE); - glFogf(GL_FOG_START, 20.0f); + glFogf(GL_FOG_START, 0.0f); glFogf(GL_FOG_END, _farHazeDistance); glEnable(GL_FOG); } From df1b4107fd92a88bc443312c4c234b05ed18e081 Mon Sep 17 00:00:00 2001 From: wangyix Date: Tue, 8 Jul 2014 13:01:05 -0700 Subject: [PATCH 043/135] moved qAudioOutput buffer overflow debug option to existing audio menu --- interface/src/Menu.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 69d95b34db..c0c25bb00d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -428,10 +428,6 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false); - QMenu* audioOptionsMenu = developerMenu->addMenu("Audio Options"); - - addCheckableActionToQMenuAndActionHash(audioOptionsMenu, MenuOption::DisableQAudioOutputOverflowCheck, 0, false); - addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisableNackPackets, 0, false); addDisabledActionAndSeparator(developerMenu, "Testing"); @@ -579,6 +575,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, From 02cf1f006e14a4f2fde6db2e85ea159934778d8f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 8 Jul 2014 13:41:42 -0700 Subject: [PATCH 044/135] Change tests to reflect transmission changes and fixed issue with reprocessing already-handled reliable delta. --- .../src/metavoxels/MetavoxelServer.cpp | 9 ++++--- .../src/metavoxels/MetavoxelServer.h | 1 + .../metavoxels/src/MetavoxelClientManager.cpp | 8 ++++-- .../metavoxels/src/MetavoxelClientManager.h | 1 + libraries/metavoxels/src/MetavoxelMessages.h | 4 +++ tests/metavoxels/src/MetavoxelTests.cpp | 27 +++++++++++++------ tests/metavoxels/src/MetavoxelTests.h | 3 +++ 7 files changed, 40 insertions(+), 13 deletions(-) diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index c601478f70..b0cf93fb3a 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -93,7 +93,8 @@ void MetavoxelServer::sendDeltas() { MetavoxelSession::MetavoxelSession(const SharedNodePointer& node, MetavoxelServer* server) : Endpoint(node, new PacketRecord(), NULL), _server(server), - _reliableDeltaChannel(NULL) { + _reliableDeltaChannel(NULL), + _reliableDeltaID(0) { connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleMessage(const QVariant&))); connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(checkReliableDeltaReceived())); @@ -109,7 +110,8 @@ void MetavoxelSession::update() { // if we're sending a reliable delta, wait until it's acknowledged if (_reliableDeltaChannel) { Bitstream& out = _sequencer.startPacket(); - out << QVariant::fromValue(MetavoxelDeltaPendingMessage()); + MetavoxelDeltaPendingMessage msg = { _reliableDeltaID }; + out << QVariant::fromValue(msg); _sequencer.endPacket(); return; } @@ -134,7 +136,8 @@ void MetavoxelSession::update() { // go back to the beginning with the current packet and note that there's a delta pending _sequencer.getOutputStream().getUnderlying().device()->seek(start); - out << QVariant::fromValue(MetavoxelDeltaPendingMessage()); + MetavoxelDeltaPendingMessage msg = { ++_reliableDeltaID }; + out << QVariant::fromValue(msg); _sequencer.endPacket(); } else { diff --git a/assignment-client/src/metavoxels/MetavoxelServer.h b/assignment-client/src/metavoxels/MetavoxelServer.h index f2769f26f2..0e8db55587 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.h +++ b/assignment-client/src/metavoxels/MetavoxelServer.h @@ -83,6 +83,7 @@ private: MetavoxelData _reliableDeltaData; MetavoxelLOD _reliableDeltaLOD; Bitstream::WriteMappings _reliableDeltaWriteMappings; + int _reliableDeltaID; }; #endif // hifi_MetavoxelServer_h diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp index f16b6c2396..f3ea1ae8c5 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.cpp +++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp @@ -87,7 +87,8 @@ void MetavoxelClientManager::updateClient(MetavoxelClient* client) { MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelClientManager* manager) : Endpoint(node, new PacketRecord(), new PacketRecord()), _manager(manager), - _reliableDeltaChannel(NULL) { + _reliableDeltaChannel(NULL), + _reliableDeltaID(0) { connect(_sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX), SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&))); @@ -139,7 +140,10 @@ void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) { } } } else if (userType == MetavoxelDeltaPendingMessage::Type) { - if (!_reliableDeltaChannel) { + // 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(); diff --git a/libraries/metavoxels/src/MetavoxelClientManager.h b/libraries/metavoxels/src/MetavoxelClientManager.h index 1f37b15c18..ad6c86c8fc 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.h +++ b/libraries/metavoxels/src/MetavoxelClientManager.h @@ -74,6 +74,7 @@ private: ReliableChannel* _reliableDeltaChannel; MetavoxelLOD _reliableDeltaLOD; + int _reliableDeltaID; }; #endif // hifi_MetavoxelClientManager_h diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index b822f1c561..91d73c08a9 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -64,6 +64,10 @@ 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) diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 4132270620..49808fe080 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -647,7 +647,8 @@ TestEndpoint::TestEndpoint(Mode mode) : _mode(mode), _highPriorityMessagesToSend(0.0f), _reliableMessagesToSend(0.0f), - _reliableDeltaChannel(NULL) { + _reliableDeltaChannel(NULL), + _reliableDeltaID(0) { connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleHighPriorityMessage(const QVariant&))); @@ -908,7 +909,8 @@ bool TestEndpoint::simulate(int iterationNumber) { // if we're sending a reliable delta, wait until it's acknowledged if (_reliableDeltaChannel) { Bitstream& out = _sequencer.startPacket(); - out << QVariant::fromValue(MetavoxelDeltaPendingMessage()); + MetavoxelDeltaPendingMessage msg = { _reliableDeltaID }; + out << QVariant::fromValue(msg); _sequencer.endPacket(); return false; } @@ -932,7 +934,8 @@ bool TestEndpoint::simulate(int iterationNumber) { _reliableDeltaLOD = _lod; _sequencer.getOutputStream().getUnderlying().device()->seek(start); - out << QVariant::fromValue(MetavoxelDeltaPendingMessage()); + MetavoxelDeltaPendingMessage msg = { ++_reliableDeltaID }; + out << QVariant::fromValue(msg); _sequencer.endPacket(); } else { @@ -1081,15 +1084,22 @@ void TestEndpoint::handleMessage(const QVariant& message, Bitstream& in) { } else if (userType == MetavoxelDeltaMessage::Type) { PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord(); - _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, - _dataLOD = getLastAcknowledgedSendRecord()->getLOD()); + _remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, + _remoteDataLOD = getLastAcknowledgedSendRecord()->getLOD()); + in.reset(); + _data = _remoteData; compareMetavoxelData(); } else if (userType == MetavoxelDeltaPendingMessage::Type) { - if (!_reliableDeltaChannel) { + 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()) { @@ -1107,7 +1117,7 @@ PacketRecord* TestEndpoint::maybeCreateSendRecord() const { } PacketRecord* TestEndpoint::maybeCreateReceiveRecord() const { - return new TestReceiveRecord(_dataLOD, (_mode == METAVOXEL_SERVER_MODE) ? MetavoxelData() : _data, _remoteState); + return new TestReceiveRecord(_remoteDataLOD, _remoteData, _remoteState); } void TestEndpoint::handleHighPriorityMessage(const QVariant& message) { @@ -1127,9 +1137,10 @@ void TestEndpoint::handleHighPriorityMessage(const QVariant& message) { void TestEndpoint::handleReliableMessage(const QVariant& message, Bitstream& in) { if (message.userType() == MetavoxelDeltaMessage::Type) { PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord(); - _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _dataLOD = _reliableDeltaLOD); + _remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _remoteDataLOD = _reliableDeltaLOD); _sequencer.getInputStream().persistReadMappings(in.getAndResetReadMappings()); in.clearPersistentMappings(); + _data = _remoteData; compareMetavoxelData(); _reliableDeltaChannel = NULL; return; diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index 5d719ccfdf..ce357fbb90 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -79,6 +79,8 @@ private: MetavoxelData _data; MetavoxelLOD _dataLOD; + MetavoxelData _remoteData; + MetavoxelLOD _remoteDataLOD; MetavoxelLOD _lod; SharedObjectPointer _sphere; @@ -104,6 +106,7 @@ private: MetavoxelData _reliableDeltaData; MetavoxelLOD _reliableDeltaLOD; Bitstream::WriteMappings _reliableDeltaWriteMappings; + int _reliableDeltaID; }; /// A simple shared object. From e732436783bac75e951270ff2bf20f5800f28db9 Mon Sep 17 00:00:00 2001 From: wangyix Date: Tue, 8 Jul 2014 14:13:42 -0700 Subject: [PATCH 045/135] added _starveCount, _silentFramesDropped --- libraries/audio/src/PositionalAudioRingBuffer.cpp | 8 +++++++- libraries/audio/src/PositionalAudioRingBuffer.h | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 6b3a1eb94f..8a03561140 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -100,7 +100,9 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer:: _desiredJitterBufferFrames(1), _currentJitterBufferFrames(-1), _dynamicJitterBuffers(dynamicJitterBuffers), - _consecutiveNotMixedCount(0) + _consecutiveNotMixedCount(0), + _starveCount(0), + _silentFramesDropped(0) { } @@ -143,9 +145,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); @@ -217,6 +222,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() { } 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; diff --git a/libraries/audio/src/PositionalAudioRingBuffer.h b/libraries/audio/src/PositionalAudioRingBuffer.h index 31b0524b3b..5922b27002 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.h +++ b/libraries/audio/src/PositionalAudioRingBuffer.h @@ -110,6 +110,8 @@ protected: // extra stats int _consecutiveNotMixedCount; + int _starveCount; + int _silentFramesDropped; }; #endif // hifi_PositionalAudioRingBuffer_h From 56361718369be6d464cbfd8bba02083675a4c67e Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 8 Jul 2014 14:41:16 -0700 Subject: [PATCH 046/135] Timing fix. --- assignment-client/src/metavoxels/MetavoxelServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index b0cf93fb3a..63776de7fa 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -87,7 +87,7 @@ 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) : From 40ad03dbf52ffcffb0498c73652d90a76e324e79 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 8 Jul 2014 14:53:41 -0700 Subject: [PATCH 047/135] Another improvement to clapping to match animation pace with keystrokes --- examples/clap.js | 54 +++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/examples/clap.js b/examples/clap.js index 7a2a77afc4..35c344165f 100644 --- a/examples/clap.js +++ b/examples/clap.js @@ -1,23 +1,26 @@ // -// 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 // 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: 8}); +startEndFrames.push({ start: 0, end: 10}); startEndFrames.push({ start: 10, end: 20}); -startEndFrames.push({ start: 20, end: 28}); -startEndFrames.push({ start: 30, end: 37}); -startEndFrames.push({ start: 41, end: 46}); -startEndFrames.push({ start: 53, end: 58}); +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; @@ -34,15 +37,12 @@ var numberOfSounds = claps.length; var clappingNow = false; var collectedClicks = 0; +var clickStartTime, clickEndTime; var clickClappingNow = false; var CLAP_START_RATE = 15.0; var clapRate = CLAP_START_RATE; var startedTimer = false; -var clapping = new Array(); -clapping[0] = false; -clapping[1] = false; - function maybePlaySound(deltaTime) { // Set the location and other info for the sound to play @@ -51,9 +51,9 @@ function maybePlaySound(deltaTime) { var frame = Math.floor(animationDetails.frameIndex); if (frame != lastAnimFrame) { - print("frame " + frame); lastAnimFrame = frame; } + for (var i = 0; i < startEndFrames.length; i++) { if (frame == startEndFrames[i].start && (frame != lastClapFrame)) { playClap(1.0, Camera.getPosition()); @@ -90,34 +90,41 @@ function playClap(volume, position) { Audio.playSound(claps[clip], options); } -function keepClapping() { - playClap(1.0, Camera.getPosition()); -} +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()); - var whichClip = Math.floor(Math.random() * startEndFrames.length); lastClapFrame = 0; MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false); - clickClappingNow = true; } else { - clapRate *= 1.25; - MyAvatar.stopAnimation(clapAnimation); - MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false); + // 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 = 500; +var CLAP_END_WAIT_MSECS = 300; Controller.keyReleaseEvent.connect(function(event) { if (event.text == "SHIFT") { + collectedClicks = 0; if (!startedTimer) { - startedTimer = true; collectedClicks = 0; Script.setTimeout(stopClapping, CLAP_END_WAIT_MSECS); + startedTimer = true; } } }); @@ -134,5 +141,4 @@ function stopClapping() { } // Connect a call back that happens every frame -Script.update.connect(maybePlaySound); -//Controller.keyPressEvent.connect(keyPressEvent); \ No newline at end of file +Script.update.connect(maybePlaySound); \ No newline at end of file From b6570dc4ee83971e4cd8b480cf7eafaa5854a609 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 8 Jul 2014 15:10:05 -0700 Subject: [PATCH 048/135] Use congestion control on server. --- .../src/metavoxels/MetavoxelServer.cpp | 22 +++++++++++++++---- .../src/metavoxels/MetavoxelServer.h | 2 ++ .../metavoxels/src/DatagramSequencer.cpp | 2 +- libraries/metavoxels/src/DatagramSequencer.h | 4 ++-- tests/metavoxels/src/MetavoxelTests.cpp | 2 +- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index 63776de7fa..14765e2ddc 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -109,10 +109,7 @@ void MetavoxelSession::update() { } // 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(); + sendPacketGroup(); return; } Bitstream& out = _sequencer.startPacket(); @@ -143,6 +140,9 @@ void MetavoxelSession::update() { } else { _sequencer.endPacket(); } + + // perhaps send additional packets to fill out the group + sendPacketGroup(1); } void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) { @@ -179,3 +179,17 @@ void MetavoxelSession::checkReliableDeltaReceived() { _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(); + } +} diff --git a/assignment-client/src/metavoxels/MetavoxelServer.h b/assignment-client/src/metavoxels/MetavoxelServer.h index 0e8db55587..6df769227c 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.h +++ b/assignment-client/src/metavoxels/MetavoxelServer.h @@ -74,6 +74,8 @@ private slots: private: + void sendPacketGroup(int alreadySent = 0); + MetavoxelServer* _server; MetavoxelLOD _lod; diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index 2c594fc1ca..536cfc9dfb 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -79,7 +79,7 @@ ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) { return channel; } -int DatagramSequencer::startPacketGroup(int desiredPackets) { +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) { diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index b85916b561..b6dce464f7 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -108,10 +108,10 @@ public: /// Returns the intput channel at the specified index, creating it if necessary. ReliableChannel* getReliableInputChannel(int index = 0); - /// Starts a packet group. + /// 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 diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 49808fe080..0a6a5de96d 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -859,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++) { From 1b890a9e48ea88f27ff154db0851ee50c08d7754 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 8 Jul 2014 15:37:59 -0700 Subject: [PATCH 049/135] Use congestion control for client sends, too. --- libraries/metavoxels/src/Endpoint.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/metavoxels/src/Endpoint.cpp b/libraries/metavoxels/src/Endpoint.cpp index 666ffe52d9..420a52ef95 100644 --- a/libraries/metavoxels/src/Endpoint.cpp +++ b/libraries/metavoxels/src/Endpoint.cpp @@ -39,9 +39,12 @@ Endpoint::~Endpoint() { } void Endpoint::update() { - Bitstream& out = _sequencer.startPacket(); - writeUpdateMessage(out); - _sequencer.endPacket(); + 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) { From 87ad87e10fb1666bca84d4b1e6ddb1dbef8c0819 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Tue, 8 Jul 2014 16:25:21 -0700 Subject: [PATCH 050/135] Actual stereoscopic 3D UI. Looks better now. --- interface/src/ui/ApplicationOverlay.cpp | 85 ++++++++++++++----------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index dfa6b63fa5..b768c522ad 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -313,8 +313,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { Application* application = Application::getInstance(); MyAvatar* myAvatar = application->getAvatar(); - const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation(); - + glActiveTexture(GL_TEXTURE0); glEnable(GL_BLEND); @@ -323,34 +322,25 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { glDisable(GL_LIGHTING); glEnable(GL_TEXTURE_2D); - renderControllerPointersOculus(); - 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]) { @@ -370,6 +360,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { renderMagnifier(_magX[i], _magY[i], _magSizeMult[i], i != MOUSE); } } + glPopMatrix(); glDepthMask(GL_FALSE); glDisable(GL_ALPHA_TEST); @@ -378,7 +369,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { renderTexturedHemisphere(); - glPopMatrix(); + renderControllerPointersOculus(); glDepthMask(GL_TRUE); glBindTexture(GL_TEXTURE_2D, 0); @@ -504,7 +495,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; @@ -513,7 +503,6 @@ void ApplicationOverlay::renderPointers() { _mouseY[MOUSE] = application->getMouseY(); _magX[MOUSE] = _mouseX[MOUSE]; _magY[MOUSE] = _mouseY[MOUSE]; - _reticleActive[LEFT_CONTROLLER] = false; _reticleActive[RIGHT_CONTROLLER] = false; @@ -664,6 +653,7 @@ void ApplicationOverlay::renderControllerPointersOculus() { Application* application = Application::getInstance(); QGLWidget* glWidget = application->getGLWidget(); + glm::vec3 cursorVerts[4]; const int widgetWidth = glWidget->width(); const int widgetHeight = glWidget->height(); @@ -678,14 +668,14 @@ void ApplicationOverlay::renderControllerPointersOculus() { //Determine how much we need to iterate const int ITERATIONS = max(myAvatar->getHand()->getNumPalms(), 3); + glm::vec3 eyePos = myAvatar->getHead()->calculateAverageEyePosition(); + for (int i = 0; i < ITERATIONS; i++) { if (useLaser && i < myAvatar->getHand()->getNumPalms()) { PalmData& palm = myAvatar->getHand()->getPalms()[i]; if (palm.isActive()) { glm::vec3 tip = OculusManager::getLaserPointerTipPosition(&palm); - glm::vec3 result; - glm::vec3 eyePos = myAvatar->getHead()->calculateAverageEyePosition(); glm::quat orientation = glm::inverse(myAvatar->getOrientation()); glm::vec3 dir = orientation * glm::normalize(eyePos - tip); //direction of ray goes towards camera glm::vec3 tipPos = (tip - eyePos); @@ -697,7 +687,6 @@ void ApplicationOverlay::renderControllerPointersOculus() { glm::vec3 up = glm::vec3(0.0, 1.0, 0.0) * size; glm::vec3 right = glm::vec3(1.0, 0.0, 0.0) * size; - glm::vec3 cursorVerts[4]; cursorVerts[0] = -right + up; cursorVerts[1] = right + up; cursorVerts[2] = right - up; @@ -749,12 +738,13 @@ void ApplicationOverlay::renderControllerPointersOculus() { // when objToCam and objToCamProj have a very small // angle between them - if ((angleCosine < 0.99990) && (angleCosine > -0.9999)) - if (cursorToCamera.y < 0) { - glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, 1, 0, 0); - } else { - glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, -1, 0, 0); - } + // if ((angleCosine < 0.9999) && (angleCosine > -0.99999)) { + 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); @@ -780,6 +770,8 @@ void ApplicationOverlay::renderControllerPointersOculus() { mouseX -= reticleSize / 2; mouseY += reticleSize / 2; + printf("MOUSEPOS: %f %f\n", mouseX, mouseY); + //Get new UV coordinates from our magnification window float newULeft = mouseX / widgetWidth; float newURight = (mouseX + reticleSize) / widgetWidth; @@ -810,10 +802,17 @@ void ApplicationOverlay::renderControllerPointersOculus() { 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(); } @@ -1183,9 +1182,23 @@ 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]); + glDrawRangeElements(GL_TRIANGLES, 0, vertices - 1, indices, GL_UNSIGNED_SHORT, 0); + glPopMatrix(); + glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); From bc785115a938c36763e418e8707c88475270c1c7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 8 Jul 2014 16:33:31 -0700 Subject: [PATCH 051/135] Fix script errors not being reported Script.update event needs to be emitted after reporting any script errors. --- libraries/script-engine/src/ScriptEngine.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index e93a7125b9..f5f15331ac 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -515,8 +515,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(); @@ -524,6 +522,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(); From f8fdd2d11f3411a4f7a521cfcb88b4bfd154fde7 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Tue, 8 Jul 2014 17:15:11 -0700 Subject: [PATCH 052/135] Minor adjustments --- interface/src/devices/OculusManager.cpp | 2 +- interface/src/ui/ApplicationOverlay.cpp | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 976bd5ba8e..804ebd1873 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -490,7 +490,7 @@ void OculusManager::renderLaserPointer() { glm::vec3 OculusManager::getLaserPointerTipPosition(const PalmData* palm) { #ifdef HAVE_LIBOVR - const float PALM_TIP_ROD_LENGTH_MULT = 11.0f; + const float PALM_TIP_ROD_LENGTH_MULT = 4.0f; return palm->getPosition() + (palm->getTipPosition() - palm->getPosition()) * PALM_TIP_ROD_LENGTH_MULT; #endif return glm::vec3(0.0f); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index b768c522ad..bc30ab6b54 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -274,7 +274,6 @@ QPoint ApplicationOverlay::getOculusPalmClickLocation(PalmData *palm) const { const int widgetWidth = glWidget->width(); const int widgetHeight = glWidget->height(); - glm::vec3 tip = OculusManager::getLaserPointerTipPosition(palm); glm::vec3 eyePos = myAvatar->getHead()->calculateAverageEyePosition(); glm::quat orientation = glm::inverse(myAvatar->getOrientation()); @@ -285,9 +284,11 @@ QPoint ApplicationOverlay::getOculusPalmClickLocation(PalmData *palm) const { float t; - //Find intersection of crosshair ray - if (raySphereIntersect(dir, tipPos, 1, &t)){ - glm::vec3 collisionPos = tipPos + dir * 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, 1, &t)){ + glm::vec3 collisionPos = adjustedPos + dir * t; float u = asin(collisionPos.x) / (_textureFov)+0.5f; float v = 1.0 - (asin(collisionPos.y) / (_textureFov)+0.5f); @@ -682,7 +683,7 @@ void ApplicationOverlay::renderControllerPointersOculus() { float t; float length = glm::length(eyePos - tip); - float size = 0.045f * length; + 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; From b2912552f68e4acb394708ee2630442a9f090b59 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 8 Jul 2014 21:00:02 -0700 Subject: [PATCH 053/135] switched to better sounds for clapping --- examples/clap.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/clap.js b/examples/clap.js index 35c344165f..28835c1f00 100644 --- a/examples/clap.js +++ b/examples/clap.js @@ -26,12 +26,16 @@ var lastClapFrame = 0; var lastAnimFrame = 0; var claps = []; -claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap1Reverb.wav")); -claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap2Reverb.wav")); -claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap3Reverb.wav")); -claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap4Reverb.wav")); -claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap5Reverb.wav")); -claps.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/Clap6Reverb.wav")); +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; var clappingNow = false; From 1e7a18e62535e1f200158da3cb5360f4555535f3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 8 Jul 2014 21:02:32 -0700 Subject: [PATCH 054/135] Guard _framebufferObject deletion --- interface/src/ui/ApplicationOverlay.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 879c7cda32..302494852a 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -999,8 +999,10 @@ void ApplicationOverlay::renderTexturedHemisphere() { } void ApplicationOverlay::resize() { - delete _framebufferObject; - _framebufferObject = NULL; + if (_framebufferObject != NULL) { + delete _framebufferObject; + _framebufferObject = NULL; + } // _framebufferObject is recreated at the correct size the next time it is accessed via getFramebufferObject(). } From 51f7e8904d70325c11b2e4a144a9334e33fc2de5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 8 Jul 2014 21:04:30 -0700 Subject: [PATCH 055/135] Integrate UI toggling menu item and Hydra UI toggling They work together and both fade the UI in / out. --- interface/src/ui/ApplicationOverlay.cpp | 11 ++++++----- interface/src/ui/ApplicationOverlay.h | 1 - 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 302494852a..b7e18df0b4 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -40,7 +40,6 @@ ApplicationOverlay::ApplicationOverlay() : _framebufferObject(NULL), _textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE), _alpha(1.0f), - _active(true), _crosshairTexture(0) { memset(_reticleActive, 0, sizeof(_reticleActive)); @@ -70,8 +69,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; @@ -485,7 +484,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,7 +495,8 @@ 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; } diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 5e8d06ab89..e2ffb506d4 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -69,7 +69,6 @@ private: float _magSizeMult[NUMBER_OF_MAGNIFIERS]; float _alpha; - bool _active; GLuint _crosshairTexture; }; From 305623d8a7d3a2a45e592d48eac2e765be630c40 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 8 Jul 2014 21:37:08 -0700 Subject: [PATCH 056/135] Add option to hide focus indicators --- interface/src/Menu.cpp | 1 + interface/src/Menu.h | 1 + interface/src/avatar/Avatar.cpp | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c0c25bb00d..019ce1df9e 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -406,6 +406,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::GlowWhenSpeaking, 0, true); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::HideFocusIndicators, 0, false); QMenu* oculusOptionsMenu = developerMenu->addMenu("Oculus Options"); addCheckableActionToQMenuAndActionHash(oculusOptionsMenu, MenuOption::DisplayOculusOverlays, 0, true); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index e8146f8038..32458e1883 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -382,6 +382,7 @@ namespace MenuOption { const QString ObeyEnvironmentalGravity = "Obey Environmental Gravity"; const QString HandsCollideWithSelf = "Collide With Self"; const QString HeadMouse = "Head Mouse"; + const QString HideFocusIndicators = "Hide Focus Indicators"; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IncreaseVoxelSize = "Increase Voxel Size"; const QString LoadScript = "Open and Run Script File..."; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 9bb54efe20..e2e111c210 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -257,7 +257,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::HideFocusIndicators)) { 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 }; From c50d9ae3e4333e6519ed9b16fddc259439c25480 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 8 Jul 2014 22:22:27 -0700 Subject: [PATCH 057/135] Hand low velocity filter as a menu option --- interface/src/Application.cpp | 7 +++++++ interface/src/Application.h | 1 + interface/src/Menu.cpp | 7 +++++++ interface/src/Menu.h | 1 + interface/src/devices/SixenseManager.cpp | 19 ++++++++++++------- interface/src/devices/SixenseManager.h | 3 +++ 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ace265ad4f..f900ea18c4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -354,6 +354,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(); @@ -1426,6 +1429,10 @@ void Application::setRenderVoxels(bool voxelRender) { } } +void Application::setLowVelocityFilter(bool lowVelocityFilter) { + getSixenseManager()->setLowVelocityFilter(lowVelocityFilter); +} + void Application::doKillLocalVoxels() { _wantToKillLocalVoxels = true; } diff --git a/interface/src/Application.h b/interface/src/Application.h index 11f406abf0..321a43d548 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -317,6 +317,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(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c0c25bb00d..b1f83c47cb 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -421,6 +421,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); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index e8146f8038..4fa02b802f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -368,6 +368,7 @@ namespace MenuOption { 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 FrameTimer = "Show Timer"; const QString FrustumRenderMode = "Render Mode"; diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index c50fc887d6..15d9da43b0 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -32,6 +32,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 @@ -160,17 +161,21 @@ 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); + palm->setRawPosition(palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter)); + palm->setRawRotation(safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter)); + } 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; 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 From f8185dec75035cb581f8128fc5464e41b86fe0a7 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 8 Jul 2014 22:26:11 -0700 Subject: [PATCH 058/135] removed some debug logs --- interface/src/avatar/Avatar.cpp | 1 - interface/src/devices/SixenseManager.cpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 9bb54efe20..758c903b5c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -611,7 +611,6 @@ void Avatar::initializeHair() { } } - qDebug() << "Initialize Hair"; } bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const { diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index 15d9da43b0..026c1d3eb4 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -61,10 +61,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 From 801be7edee99278d3fccf53a8a7626c18ddd641c Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Wed, 9 Jul 2014 13:55:38 +0200 Subject: [PATCH 059/135] Stop QMenuBar to steal keyboard focus when pressing Alt key in Linux. --- interface/src/Application.cpp | 2 ++ interface/src/GLCanvas.cpp | 36 +++++++++++++++++++++++++++++++++++ interface/src/GLCanvas.h | 1 + 3 files changed, 39 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ace265ad4f..95cfaab4cf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -803,6 +803,7 @@ bool Application::event(QEvent* event) { } void Application::keyPressEvent(QKeyEvent* event) { + qDebug("Application::keyPressEvent(%x)", event->key()); _keysPressed.insert(event->key()); @@ -1053,6 +1054,7 @@ void Application::keyPressEvent(QKeyEvent* event) { } void Application::keyReleaseEvent(QKeyEvent* event) { + qDebug("Application::keyReleaseEvent(%x)", event->key()); _keysPressed.remove(event->key()); diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 17026b5d5c..87c1016c5e 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,34 @@ 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 From 54e8ed5e1125505a1cd37b75d6a3d066cc145d06 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 09:41:49 -0700 Subject: [PATCH 060/135] added MovingMinMaxAvg and unit test; added additional stats to AudioStreamStats struct --- .gitignore | 3 + libraries/audio/src/AudioStreamStats.h | 27 +++- libraries/shared/src/MovingMinMaxAvg.h | 175 ++++++++++++++++++++++++ tests/shared/src/MovingMinMaxAvgTests.h | 25 ++++ tests/shared/src/main.cpp | 3 + 5 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 libraries/shared/src/MovingMinMaxAvg.h create mode 100644 tests/shared/src/MovingMinMaxAvgTests.h diff --git a/.gitignore b/.gitignore index 4176dcc652..d3af3c4535 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,6 @@ interface/external/rtmidi/* # Ignore interfaceCache for Linux users interface/interfaceCache/ +tests/shared/src/MovingMinMaxAvgTests.cpp +examples/dancer.js +examples/happyBirthday.js diff --git a/libraries/audio/src/AudioStreamStats.h b/libraries/audio/src/AudioStreamStats.h index 004d697fcf..69a8751af9 100644 --- a/libraries/audio/src/AudioStreamStats.h +++ b/libraries/audio/src/AudioStreamStats.h @@ -19,7 +19,18 @@ public: AudioStreamStats() : _streamType(PositionalAudioRingBuffer::Microphone), _streamIdentifier(), - _jitterBufferFrames(0), + _timeGapMin(0), + _timeGapMax(0), + _timeGapAverage(0.0f), + _timeGapMovingMin(0), + _timeGapMovingMax(0), + _timeGapMovingAverage(0.0f), + _ringBufferFramesAvailable(0), + _ringBufferCurrentJitterBufferFrames(0), + _ringBufferDesiredJitterBufferFrames(0), + _ringBufferStarveCount(0), + _ringBufferOverflowCount(0), + _ringBufferSilentFramesDropped(0), _packetsReceived(0), _packetsUnreasonable(0), _packetsEarly(0), @@ -32,7 +43,19 @@ public: PositionalAudioRingBuffer::Type _streamType; QUuid _streamIdentifier; - quint16 _jitterBufferFrames; + quint64 _timeGapMin; + quint64 _timeGapMax; + float _timeGapAverage; + quint64 _timeGapMovingMin; + quint64 _timeGapMovingMax; + float _timeGapMovingAverage; + + quint32 _ringBufferFramesAvailable; + quint16 _ringBufferCurrentJitterBufferFrames; + quint16 _ringBufferDesiredJitterBufferFrames; + quint32 _ringBufferStarveCount; + quint32 _ringBufferOverflowCount; + quint32 _ringBufferSilentFramesDropped; quint32 _packetsReceived; quint32 _packetsUnreasonable; diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h new file mode 100644 index 0000000000..606bf0b481 --- /dev/null +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -0,0 +1,175 @@ +// +// 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 + +template +class MovingMinMaxAvg { + +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) + : _min(std::numeric_limits::max()), + _max(std::numeric_limits::min()), + _average(0.0), + _samplesCollected(0), + _intervalLength(intervalLength), + _windowIntervals(windowIntervals), + _existingSamplesInCurrentInterval(0), + _existingIntervals(0), + _windowMin(std::numeric_limits::max()), + _windowMax(std::numeric_limits::min()), + _windowAverage(0.0), + _currentIntervalMin(std::numeric_limits::max()), + _currentIntervalMax(std::numeric_limits::min()), + _currentIntervalAverage(0.0), + _newestIntervalStatsAt(0), + _newStatsAvailable(false) + { + _intervalMins = new T[_windowIntervals]; + _intervalMaxes = new T[_windowIntervals]; + _intervalAverages = new double[_windowIntervals]; + } + + ~MovingMinMaxAvg() { + delete[] _intervalMins; + delete[] _intervalMaxes; + delete[] _intervalAverages; + } + + void reset() { + _min = std::numeric_limits::max(); + _max = std::numeric_limits::min(); + _average = 0.0; + _samplesCollected = 0; + _existingSamplesInCurrentInterval = 0; + _existingIntervals = 0; + _windowMin = std::numeric_limits::max(); + _windowMax = std::numeric_limits::min(); + _windowAverage = 0.0; + _currentIntervalMin = std::numeric_limits::max(); + _currentIntervalMax = std::numeric_limits::min(); + _currentIntervalAverage = 0.0; + _newStatsAvailableFlag = false; + } + + void update(T newSample) { + + // update overall stats + if (newSample < _min) { + _min = newSample; + } + if (newSample > _max) { + _max = newSample; + } + _average = (_average * _samplesCollected + newSample) / (_samplesCollected + 1); + _samplesCollected++; + + // update the current interval stats + if (newSample < _currentIntervalMin) { + _currentIntervalMin = newSample; + } + if (newSample > _currentIntervalMax) { + _currentIntervalMax = newSample; + } + _currentIntervalAverage = (_currentIntervalAverage * _existingSamplesInCurrentInterval + newSample) / (_existingSamplesInCurrentInterval + 1); + _existingSamplesInCurrentInterval++; + + // if the current interval of samples is now full, record its stats into our past intervals' stats + if (_existingSamplesInCurrentInterval == _intervalLength) { + + // increment index of the newest interval's stats cyclically + _newestIntervalStatsAt = _newestIntervalStatsAt == _windowIntervals - 1 ? 0 : _newestIntervalStatsAt + 1; + + // record current interval's stats, then reset them + _intervalMins[_newestIntervalStatsAt] = _currentIntervalMin; + _intervalMaxes[_newestIntervalStatsAt] = _currentIntervalMax; + _intervalAverages[_newestIntervalStatsAt] = _currentIntervalAverage; + _currentIntervalMin = std::numeric_limits::max(); + _currentIntervalMax = std::numeric_limits::min(); + _currentIntervalAverage = 0.0; + _existingSamplesInCurrentInterval = 0; + + if (_existingIntervals < _windowIntervals) { + _existingIntervals++; + } + + // update the window's stats + int k = _newestIntervalStatsAt; + _windowMin = _intervalMins[k]; + _windowMax = _intervalMaxes[k]; + double intervalAveragesSum = _intervalAverages[k]; + for (int i = 1; i < _existingIntervals; i++) { + k = k == 0 ? _windowIntervals - 1 : k - 1; + if (_intervalMins[k] < _windowMin) { + _windowMin = _intervalMins[k]; + } + if (_intervalMaxes[k] > _windowMax) { + _windowMax = _intervalMaxes[k]; + } + intervalAveragesSum += _intervalAverages[k]; + } + _windowAverage = intervalAveragesSum / _existingIntervals; + + _newStatsAvailable = true; + } + } + + + bool getNewStatsAvailableFlag() const { return _newStatsAvailable; } + void clearNewStatsAvailableFlag() { _newStatsAvailable = false; } + + T getMin() const { return _min; } + T getMax() const { return _max; } + double getAverage() const { return _average; } + T getWindowMin() const { return _windowMin; } + T getWindowMax() const { return _windowMax; } + double getWindowAverage() const { return _windowAverage; } + +private: + // these are min/max/avg stats for all samples collected. + T _min; + T _max; + double _average; + int _samplesCollected; + + int _intervalLength; + int _windowIntervals; + + int _existingSamplesInCurrentInterval; + int _existingIntervals; + + // these are the min/max/avg stats for the samples in the moving window + T _windowMin; + T _windowMax; + double _windowAverage; + + T _currentIntervalMin; + T _currentIntervalMax; + double _currentIntervalAverage; + + T* _intervalMins; + T* _intervalMaxes; + double* _intervalAverages; + int _newestIntervalStatsAt; + + bool _newStatsAvailable; +}; + +#endif // hifi_OctalCode_h 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; } From d044af140001d6eec01e56b352d301fb2a8770ef Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Wed, 9 Jul 2014 11:12:02 -0700 Subject: [PATCH 061/135] Made it so you can only select in a 180 degree radius with oculus --- interface/src/devices/SixenseManager.cpp | 3 +-- interface/src/ui/ApplicationOverlay.cpp | 14 ++++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index ecf7fea5cb..33d485ea84 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -373,7 +373,6 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) { if (OculusManager::isConnected()) { pos = application->getApplicationOverlay().getOculusPalmClickLocation(palm); - printf("CLICK: %d %d\n", pos.x(), pos.y()); } else { // Get directon relative to avatar orientation glm::vec3 direction = glm::inverse(avatar->getOrientation()) * palm->getFingerDirection(); @@ -392,7 +391,7 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) { //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/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index bc30ab6b54..87ee46ca2b 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -290,12 +290,18 @@ QPoint ApplicationOverlay::getOculusPalmClickLocation(PalmData *palm) const { if (raySphereIntersect(dir, adjustedPos, 1, &t)){ glm::vec3 collisionPos = adjustedPos + dir * t; - float u = asin(collisionPos.x) / (_textureFov)+0.5f; - float v = 1.0 - (asin(collisionPos.y) / (_textureFov)+0.5f); + //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 { - rv.setX(u * glWidget->width()); - rv.setY(v * glWidget->height()); + 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); From 603ca02ef97f3018fb5e2e8d89ba74a66d17d0e5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 9 Jul 2014 11:21:16 -0700 Subject: [PATCH 062/135] Default UI to "on" --- interface/src/Menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 70faec4057..2c9f7b9c28 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -276,7 +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); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::UserInterface, Qt::Key_Slash, true); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::EnableVRMode, 0, false, From 862f31130682519e478584bbb81af8e425cb584d Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Wed, 9 Jul 2014 11:29:33 -0700 Subject: [PATCH 063/135] Fixed full screen mirror mode for non oculus --- interface/src/Application.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7eb66bafd4..bf464f98aa 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -604,9 +604,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 From 527f6b2a3f4d18f285a26aeff083174cb5ac2912 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 9 Jul 2014 11:32:38 -0700 Subject: [PATCH 064/135] Fix User Interface menu item string --- interface/src/Menu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 98c6af0be9..a2e1dd0c20 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -439,7 +439,7 @@ namespace MenuOption { const QString UploadAttachment = "Upload Attachment Model"; const QString UploadHead = "Upload Head Model"; const QString UploadSkeleton = "Upload Skeleton Model"; - const QString UserInterface = "UserInterface"; + const QString UserInterface = "User Interface"; const QString Visage = "Visage"; const QString VoxelMode = "Cycle Voxel Mode"; const QString Voxels = "Voxels"; From d214998d1aa67fb3fb296d6cc2e08d4f00c74376 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 9 Jul 2014 11:53:09 -0700 Subject: [PATCH 065/135] enforce coding standard: explicit casts --- libraries/shared/src/SimpleMovingAverage.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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()); } From d26585728b2cc5897120d1446cc278a54029ba93 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 9 Jul 2014 11:53:53 -0700 Subject: [PATCH 066/135] Improved stat accumulation and context names --- interface/src/Application.cpp | 125 +++++++++++-------------- interface/src/avatar/Avatar.cpp | 33 ++++--- interface/src/avatar/AvatarManager.cpp | 4 + interface/src/avatar/MyAvatar.cpp | 44 +++------ interface/src/renderer/GlowEffect.cpp | 2 +- interface/src/ui/Stats.cpp | 2 + libraries/shared/src/PerfStat.cpp | 50 +++++++++- libraries/shared/src/PerfStat.h | 29 ++++-- 8 files changed, 163 insertions(+), 126 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ace265ad4f..b8368b96c4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -682,7 +682,7 @@ void Application::paintGL() { } { - PerformanceTimer perfTimer("paintGL/renderOverlay"); + PerformanceTimer perfTimer("renderOverlay"); //If alpha is 1, we can render directly to the screen. if (_applicationOverlay.getAlpha() == 1.0f) { _applicationOverlay.renderOverlay(); @@ -1355,18 +1355,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); @@ -1378,7 +1378,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()); } @@ -1792,7 +1792,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); @@ -1802,7 +1802,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()"); @@ -1835,7 +1835,7 @@ void Application::updateMouseRay() { } void Application::updateFaceshift() { - PerformanceTimer perfTimer("idle/update/updateFaceshift"); + PerformanceTimer perfTimer("faceshift"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateFaceshift()"); @@ -1850,7 +1850,7 @@ void Application::updateFaceshift() { } void Application::updateVisage() { - PerformanceTimer perfTimer("idle/update/updateVisage"); + PerformanceTimer perfTimer("visage"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateVisage()"); @@ -1860,11 +1860,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; @@ -1927,7 +1927,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()"); @@ -1942,7 +1942,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()"); @@ -1972,7 +1972,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()"); @@ -1990,7 +1990,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()"); @@ -2007,7 +2007,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()"); @@ -2032,8 +2032,6 @@ 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()"); @@ -2043,72 +2041,61 @@ void Application::update(float deltaTime) { updateVisage(); { - PerformanceTimer perfTimer("idle/update/updateLookAtTargetAvatar"); - _myAvatar->updateLookAtTargetAvatar(); - } - updateMyAvatarLookAtPosition(); - { - PerformanceTimer perfTimer("idle/update/sixense,joystick,prioVR"); - _sixenseManager.update(deltaTime); - _joystickManager.update(); - _prioVR.update(deltaTime); - } - - { - PerformanceTimer perfTimer("idle/update/updateMyAvatar"); + PerformanceTimer perfTimer("myAvatar"); + updateMyAvatarLookAtPosition(); + { + PerformanceTimer perfTimer("devices"); + _sixenseManager.update(deltaTime); + _joystickManager.update(); + _prioVR.update(deltaTime); + } updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes } 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); @@ -2121,13 +2108,13 @@ 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); } // Update my voxel servers with my current voxel query... { - PerformanceTimer perfTimer("updateMyAvatar/queryOctree"); + PerformanceTimer perfTimer("queryOctree"); quint64 now = usecTimestampNow(); quint64 sinceLastQuery = now - _lastQueriedTime; const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; @@ -2460,7 +2447,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); @@ -2622,7 +2609,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 @@ -2656,7 +2643,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(); } @@ -2675,7 +2662,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()) { @@ -2704,7 +2691,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); @@ -2725,13 +2712,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(); @@ -2739,14 +2726,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(); @@ -2754,7 +2741,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(); @@ -2762,7 +2749,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(); @@ -2770,7 +2757,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(); @@ -2785,20 +2772,20 @@ 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"); + 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); @@ -2806,7 +2793,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(); @@ -2823,13 +2810,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/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 9b136980f4..d8cfd58a5c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -15,12 +15,12 @@ #include #include +#include #include #include +#include #include -#include - #include "Application.h" #include "Avatar.h" #include "Hand.h" @@ -118,29 +118,36 @@ 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) { + PerformanceTimer perfTimer("skeleton"); 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"); + _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); - + 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); } } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 59f31388f8..86ec7c2680 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() > 1) { + 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/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6cb6ba6840..9154331cee 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,18 +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; @@ -150,31 +141,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 +172,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 +184,14 @@ void MyAvatar::simulate(float deltaTime) { } { - PerformanceTimer perfTimer("MyAvatar::simulate/hair Simulate"); + PerformanceTimer perfTimer("hair"); if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { simulateHair(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 +204,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 +213,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); } */ @@ -910,7 +898,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 +963,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/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/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 379dd35df7..3de21b449b 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -601,6 +601,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/libraries/shared/src/PerfStat.cpp b/libraries/shared/src/PerfStat.cpp index 4dca3f3d49..908dca61e7 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..69bda0af5e 100644 --- a/libraries/shared/src/PerfStat.h +++ b/libraries/shared/src/PerfStat.h @@ -52,16 +52,21 @@ 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; + quint64 _lastTotal; + quint64 _numAccumulations; + quint64 _numTallies; + quint64 _expiry; SimpleMovingAverage _movingAverage; }; @@ -69,20 +74,24 @@ 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 QString _fullName; static QMap _records; }; From d03d3ef8176113abeaf53a81ad784b3f40c80581 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 11:59:50 -0700 Subject: [PATCH 067/135] new auidostreamstats displayed in interface, domain page stats updated --- .../src/audio/AudioMixerClientData.cpp | 93 +++++--- .../src/audio/AvatarAudioRingBuffer.cpp | 2 +- interface/src/ui/Stats.cpp | 43 +++- libraries/audio/src/AudioRingBuffer.h | 4 +- libraries/audio/src/AudioStreamStats.h | 2 + .../audio/src/InjectedAudioRingBuffer.cpp | 2 +- .../audio/src/PositionalAudioRingBuffer.cpp | 87 ++----- .../audio/src/PositionalAudioRingBuffer.h | 42 ++-- libraries/networking/src/PacketHeaders.cpp | 2 + libraries/shared/src/MovingMinMaxAvg.h | 21 +- tests/shared/src/MovingMinMaxAvgTests.cpp | 218 ++++++++++++++++++ 11 files changed, 369 insertions(+), 147 deletions(-) create mode 100644 tests/shared/src/MovingMinMaxAvgTests.cpp diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index d3883501d6..9e27103bd1 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -18,6 +18,7 @@ #include "AudioMixer.h" #include "AudioMixerClientData.h" +#include "MovingMinMaxAvg.h" AudioMixerClientData::AudioMixerClientData() : _ringBuffers(), @@ -159,25 +160,41 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() { } AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const { + AudioStreamStats streamStats; - SequenceNumberStats streamSequenceNumberStats; + const SequenceNumberStats* streamSequenceNumberStats; streamStats._streamType = ringBuffer->getType(); if (streamStats._streamType == PositionalAudioRingBuffer::Injector) { streamStats._streamIdentifier = ((InjectedAudioRingBuffer*)ringBuffer)->getStreamIdentifier(); - streamSequenceNumberStats = _incomingInjectedAudioSequenceNumberStatsMap.value(streamStats._streamIdentifier); + streamSequenceNumberStats = &_incomingInjectedAudioSequenceNumberStatsMap[streamStats._streamIdentifier]; } else { - streamSequenceNumberStats = _incomingAvatarAudioSequenceNumberStats; + streamSequenceNumberStats = &_incomingAvatarAudioSequenceNumberStats; } - 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._timeGapMovingMin = timeGapStats.getWindowMin(); + streamStats._timeGapMovingMax = timeGapStats.getWindowMax(); + streamStats._timeGapMovingAverage = 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(); + + 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(); return streamStats; } @@ -236,44 +253,46 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { QString result; 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) + result += "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) + " early:" + QString::number(streamStats._packetsEarly) + " late:" + QString::number(streamStats._packetsLate) - + " lost:" + QString::number(streamStats._packetsLost); + + " lost:" + QString::number(streamStats._packetsLost) + + " min gap:" + QString::number(streamStats._timeGapMin) + + " max gap:" + QString::number(streamStats._timeGapMax) + + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) + + " min 30s gap:" + QString::number(streamStats._timeGapMovingMin) + + " max 30s gap:" + QString::number(streamStats._timeGapMovingMax) + + " avg 30s gap:" + QString::number(streamStats._timeGapMovingAverage, 'g', 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) + result += "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) + " early:" + QString::number(streamStats._packetsEarly) + " late:" + QString::number(streamStats._packetsLate) - + " lost:" + QString::number(streamStats._packetsLost); + + " lost:" + QString::number(streamStats._packetsLost) + + " min gap:" + QString::number(streamStats._timeGapMin) + + " max gap:" + QString::number(streamStats._timeGapMax) + + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) + + " min 30s gap:" + QString::number(streamStats._timeGapMovingMin) + + " max 30s gap:" + QString::number(streamStats._timeGapMovingMax) + + " avg 30s gap:" + QString::number(streamStats._timeGapMovingAverage, 'g', 2); } } return result; diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp index 9c6cc32f57..0177bc48ea 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(); + frameReceived(); updateDesiredJitterBufferFrames(); _shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 379dd35df7..0165f82591 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -291,7 +291,7 @@ void Stats::display( const AudioStreamStats& audioMixerAvatarStreamStats = audio->getAudioMixerAvatarStreamStats(); const QHash& audioMixerInjectedStreamStatsMap = audio->getAudioMixerInjectedStreamStatsMap(); - lines = _expanded ? 10 + audioMixerInjectedStreamStatsMap.size(): 3; + lines = _expanded ? 12 + audioMixerInjectedStreamStatsMap.size() * 3: 3; drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10); horizontalOffset += 5; @@ -354,17 +354,48 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamLabelString, color); char upstreamAudioStatsString[30]; - sprintf(upstreamAudioStatsString, " mic: %d/%d/%d, %d", audioMixerAvatarStreamStats._packetsEarly, + sprintf(upstreamAudioStatsString, " mic: %d/%d/%d, %d/%d/%d", audioMixerAvatarStreamStats._packetsEarly, audioMixerAvatarStreamStats._packetsLate, audioMixerAvatarStreamStats._packetsLost, - audioMixerAvatarStreamStats._jitterBufferFrames); + audioMixerAvatarStreamStats._ringBufferFramesAvailable, audioMixerAvatarStreamStats._ringBufferCurrentJitterBufferFrames, + audioMixerAvatarStreamStats._ringBufferDesiredJitterBufferFrames); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); + + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarStreamStats._timeGapMin, + audioMixerAvatarStreamStats._timeGapMax, audioMixerAvatarStreamStats._timeGapAverage, + audioMixerAvatarStreamStats._ringBufferStarveCount, audioMixerAvatarStreamStats._ringBufferOverflowCount); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); + + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarStreamStats._timeGapMovingMin, + audioMixerAvatarStreamStats._timeGapMovingMax, audioMixerAvatarStreamStats._timeGapMovingAverage, + audioMixerAvatarStreamStats._ringBufferConsecutiveNotMixedCount, audioMixerAvatarStreamStats._ringBufferSilentFramesDropped); 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, " inj: %d/%d/%d, %d/%d/%d", injectedStreamStats._packetsEarly, + injectedStreamStats._packetsLate, injectedStreamStats._packetsLost, + injectedStreamStats._ringBufferFramesAvailable, injectedStreamStats._ringBufferCurrentJitterBufferFrames, + injectedStreamStats._ringBufferDesiredJitterBufferFrames); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); + + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamStats._timeGapMin, + injectedStreamStats._timeGapMax, injectedStreamStats._timeGapAverage, + injectedStreamStats._ringBufferStarveCount, injectedStreamStats._ringBufferOverflowCount); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); + + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamStats._timeGapMovingMin, + injectedStreamStats._timeGapMovingMax, injectedStreamStats._timeGapMovingAverage, + injectedStreamStats._ringBufferConsecutiveNotMixedCount, injectedStreamStats._ringBufferSilentFramesDropped); + verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); } diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 047db70693..38f1adec21 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,7 @@ public: void shiftReadPosition(unsigned int numSamples); int samplesAvailable() const; + int framesAvailable() const { return samplesAvailable() / _numFrameSamples; } bool isNotStarvedOrHasMinimumSamples(int numRequiredSamples) const; diff --git a/libraries/audio/src/AudioStreamStats.h b/libraries/audio/src/AudioStreamStats.h index 69a8751af9..2c66187309 100644 --- a/libraries/audio/src/AudioStreamStats.h +++ b/libraries/audio/src/AudioStreamStats.h @@ -29,6 +29,7 @@ public: _ringBufferCurrentJitterBufferFrames(0), _ringBufferDesiredJitterBufferFrames(0), _ringBufferStarveCount(0), + _ringBufferConsecutiveNotMixedCount(0), _ringBufferOverflowCount(0), _ringBufferSilentFramesDropped(0), _packetsReceived(0), @@ -54,6 +55,7 @@ public: quint16 _ringBufferCurrentJitterBufferFrames; quint16 _ringBufferDesiredJitterBufferFrames; quint32 _ringBufferStarveCount; + quint32 _ringBufferConsecutiveNotMixedCount; quint32 _ringBufferOverflowCount; quint32 _ringBufferSilentFramesDropped; diff --git a/libraries/audio/src/InjectedAudioRingBuffer.cpp b/libraries/audio/src/InjectedAudioRingBuffer.cpp index c84fe173c9..d3d0cdfb8d 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(); + frameReceived(); 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 8a03561140..666b89e568 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,6 +33,9 @@ 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), @@ -230,7 +169,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() { // reset our _shouldOutputStarveDebug to true so the next is printed _shouldOutputStarveDebug = true; - _consecutiveNotMixedCount++; + _consecutiveNotMixedCount = 1; return false; } @@ -240,7 +179,6 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() { // minus one (since a frame will be read immediately after this) is the length of the jitter buffer _currentJitterBufferFrames = samplesAvailable() / samplesPerFrame - 1; _isStarved = false; - _consecutiveNotMixedCount = 0; } // since we've read data from ring buffer at least once - we've started @@ -253,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::frameReceived() { + 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; } @@ -276,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 5922b27002..a3adec0117 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,17 +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 frameReceived(); void updateDesiredJitterBufferFrames(); PositionalAudioRingBuffer::Type _type; @@ -103,7 +94,10 @@ protected: float _nextOutputTrailingLoudness; AABox* _listenerUnattenuatedZone; - InterframeTimeGapStats _interframeTimeGapStats; + quint64 _lastFrameReceivedTime; + MovingMinMaxAvg _interframeTimeGapStatsForJitterCalc; + MovingMinMaxAvg _interframeTimeGapStatsForStatsPacket; + int _desiredJitterBufferFrames; int _currentJitterBufferFrames; bool _dynamicJitterBuffers; 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/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 606bf0b481..5263e2ab6c 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -78,8 +78,7 @@ public: if (newSample > _max) { _max = newSample; } - _average = (_average * _samplesCollected + newSample) / (_samplesCollected + 1); - _samplesCollected++; + updateAverage(_average, _samplesCollected, (double)newSample); // update the current interval stats if (newSample < _currentIntervalMin) { @@ -88,8 +87,7 @@ public: if (newSample > _currentIntervalMax) { _currentIntervalMax = newSample; } - _currentIntervalAverage = (_currentIntervalAverage * _existingSamplesInCurrentInterval + newSample) / (_existingSamplesInCurrentInterval + 1); - _existingSamplesInCurrentInterval++; + updateAverage(_currentIntervalAverage, _existingSamplesInCurrentInterval, (double)newSample); // if the current interval of samples is now full, record its stats into our past intervals' stats if (_existingSamplesInCurrentInterval == _intervalLength) { @@ -114,8 +112,9 @@ public: int k = _newestIntervalStatsAt; _windowMin = _intervalMins[k]; _windowMax = _intervalMaxes[k]; - double intervalAveragesSum = _intervalAverages[k]; - for (int i = 1; i < _existingIntervals; i++) { + _windowAverage = _intervalAverages[k]; + int intervalsIncludedInWindowStats = 1; + while (intervalsIncludedInWindowStats < _existingIntervals) { k = k == 0 ? _windowIntervals - 1 : k - 1; if (_intervalMins[k] < _windowMin) { _windowMin = _intervalMins[k]; @@ -123,9 +122,8 @@ public: if (_intervalMaxes[k] > _windowMax) { _windowMax = _intervalMaxes[k]; } - intervalAveragesSum += _intervalAverages[k]; + updateAverage(_windowAverage, intervalsIncludedInWindowStats, _intervalAverages[k]); } - _windowAverage = intervalAveragesSum / _existingIntervals; _newStatsAvailable = true; } @@ -142,6 +140,13 @@ public: T getWindowMax() const { return _windowMax; } double getWindowAverage() const { return _windowAverage; } +private: + void updateAverage(double& average, int& numSamples, double newSample) { + // update some running average without overflowing it + average = average * ((double)numSamples / (numSamples + 1)) + newSample / (numSamples + 1); + numSamples++; + } + private: // these are min/max/avg stats for all samples collected. T _min; diff --git a/tests/shared/src/MovingMinMaxAvgTests.cpp b/tests/shared/src/MovingMinMaxAvgTests.cpp new file mode 100644 index 0000000000..ae23248e5f --- /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); + + 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); + + } 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"); +} + From e64f34a257d44056e46e85ac368747e3fde4871f Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Wed, 9 Jul 2014 12:03:32 -0700 Subject: [PATCH 068/135] Separated mouse and controller pointers --- interface/src/ui/ApplicationOverlay.cpp | 125 ++++++++++++------------ interface/src/ui/ApplicationOverlay.h | 2 +- 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 87ee46ca2b..b9b10f697e 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -376,7 +376,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { renderTexturedHemisphere(); - renderControllerPointersOculus(); + renderPointersOculus(); glDepthMask(GL_TRUE); glBindTexture(GL_TEXTURE_2D, 0); @@ -654,7 +654,7 @@ void ApplicationOverlay::renderControllerPointers() { } } -void ApplicationOverlay::renderControllerPointersOculus() { +void ApplicationOverlay::renderPointersOculus() { const bool useLaser = true; @@ -672,12 +672,10 @@ void ApplicationOverlay::renderControllerPointersOculus() { glMatrixMode(GL_MODELVIEW); MyAvatar* myAvatar = application->getAvatar(); - //Determine how much we need to iterate - const int ITERATIONS = max(myAvatar->getHand()->getNumPalms(), 3); - glm::vec3 eyePos = myAvatar->getHead()->calculateAverageEyePosition(); - for (int i = 0; i < ITERATIONS; i++) { + //Controller Pointers + for (int i = 0; i < myAvatar->getHand()->getNumPalms(); i++) { if (useLaser && i < myAvatar->getHand()->getNumPalms()) { PalmData& palm = myAvatar->getHand()->getPalms()[i]; @@ -766,64 +764,65 @@ void ApplicationOverlay::renderControllerPointersOculus() { glPopMatrix(); } - } else if (i < NUMBER_OF_MAGNIFIERS) { - //Dont render the reticle if its inactive - if (!_reticleActive[i]) { - continue; - } - - float mouseX = (float)_mouseX[i]; - float mouseY = (float)_mouseY[i]; - mouseX -= reticleSize / 2; - mouseY += reticleSize / 2; - - printf("MOUSEPOS: %f %f\n", mouseX, mouseY); - - //Get new UV coordinates from our magnification window - float newULeft = mouseX / widgetWidth; - float newURight = (mouseX + reticleSize) / widgetWidth; - float newVBottom = 1.0 - mouseY / widgetHeight; - float newVTop = 1.0 - (mouseY - reticleSize) / 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 dist; - //Bottom Left - dist = sqrt(lX * lX + bY * bY); - float blZ = sqrt(1.0f - dist * dist); - //Top Left - dist = sqrt(lX * lX + tY * tY); - float tlZ = sqrt(1.0f - dist * dist); - //Bottom Right - dist = sqrt(rX * rX + bY * bY); - float brZ = sqrt(1.0f - dist * dist); - //Top Right - dist = sqrt(rX * rX + tY * tY); - float trZ = sqrt(1.0f - dist * dist); - - glBegin(GL_QUADS); - - glColor4f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], _alpha); - - - 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(); - } + } } + + //Mouse Pointer + if (_reticleActive[MOUSE]) { + + float mouseX = (float)_mouseX[MOUSE]; + float mouseY = (float)_mouseY[MOUSE]; + mouseX -= reticleSize / 2; + mouseY += reticleSize / 2; + + printf("MOUSEPOS: %f %f\n", mouseX, mouseY); + + //Get new UV coordinates from our magnification window + float newULeft = mouseX / widgetWidth; + float newURight = (mouseX + reticleSize) / widgetWidth; + float newVBottom = 1.0 - mouseY / widgetHeight; + float newVTop = 1.0 - (mouseY - reticleSize) / 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 dist; + //Bottom Left + dist = sqrt(lX * lX + bY * bY); + float blZ = sqrt(1.0f - dist * dist); + //Top Left + dist = sqrt(lX * lX + tY * tY); + float tlZ = sqrt(1.0f - dist * dist); + //Bottom Right + dist = sqrt(rX * rX + bY * bY); + float brZ = sqrt(1.0f - dist * dist); + //Top Right + dist = sqrt(rX * rX + tY * tY); + float trZ = sqrt(1.0f - dist * dist); + + glBegin(GL_QUADS); + + glColor4f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], _alpha); + + + 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); } diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index ebac5f3cfc..117dd02f3b 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -49,7 +49,7 @@ private: void renderPointers(); void renderControllerPointers(); - void renderControllerPointersOculus(); + void renderPointersOculus(); void renderMagnifier(int mouseX, int mouseY, float sizeMult, bool showBorder) const; void renderAudioMeter(); void renderStatsAndLogs(); From 56eadcb0dcae9d48e5054a97c76cb5ab2c194047 Mon Sep 17 00:00:00 2001 From: TonyPeng Date: Wed, 9 Jul 2014 12:05:34 -0700 Subject: [PATCH 069/135] Get rid of redundant GL calls for fog. Enable and disable fog during rendering stage of voxels. Moved the local light parameters to shader uniform arrays. --- examples/avatarLocalLight.js | 29 +++++----- interface/resources/shaders/model.frag | 17 +++--- interface/resources/shaders/model.vert | 7 --- .../resources/shaders/model_specular_map.frag | 18 ++++++- interface/resources/shaders/skin_model.vert | 9 ---- interface/src/avatar/Avatar.cpp | 54 ++++++++++++++----- interface/src/avatar/Avatar.h | 13 ++--- interface/src/avatar/SkeletonModel.h | 1 + interface/src/renderer/Model.cpp | 22 +++++++- interface/src/renderer/Model.h | 10 ++++ interface/src/renderer/ProgramObject.cpp | 15 ++++++ interface/src/renderer/ProgramObject.h | 1 + interface/src/voxels/VoxelSystem.cpp | 15 ++++-- 13 files changed, 148 insertions(+), 63 deletions(-) diff --git a/examples/avatarLocalLight.js b/examples/avatarLocalLight.js index 600713a037..69b491d430 100644 --- a/examples/avatarLocalLight.js +++ b/examples/avatarLocalLight.js @@ -11,10 +11,11 @@ // 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, w: 1.0}, {x: 0.0, y:0.0, z: 0.0, w: 1.0}, {x: 0.0, y:0.0, z: 0.0, w: 1.0}, {x: 0.0, y:0.0, z: 0.0, w: 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) { @@ -92,34 +93,34 @@ function keyPressEvent(event) { MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection); } - else if (event.text == "+" ) { - if (currentNumLights + 1 < 4) { - + else if (event.text == "," ) { + if (currentNumLights + 1 <= maxNumLights) { + var darkGrayColor = {x:0.3, y:0.3, z:0.3}; + // default light - localLightColors[currentNumLights].x = 0.1; - localLightColors[currentNumLights].y = 0.1; - localLightColors[currentNumLights].z = 0.1; + 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 == "-" ) { + 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.setLocalLightColor(localLightColors[currentNumLights - 1], currentNumLights - 1); - + + MyAvatar.removeLocalLight(); --currentNumLights; } - } - + } } Controller.keyPressEvent.connect(keyPressEvent); diff --git a/interface/resources/shaders/model.frag b/interface/resources/shaders/model.frag index 86bc10179c..95afaa0e5f 100644 --- a/interface/resources/shaders/model.frag +++ b/interface/resources/shaders/model.frag @@ -11,20 +11,22 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -const int MAX_LOCAL_LIGHTS = 4; // 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; // the interpolated normal varying vec4 normal; -// static local light position -varying vec4 localLightPos[MAX_LOCAL_LIGHTS]; - void main(void) { // compute the base color based on OpenGL lighting model vec4 normalizedNormal = normalize(normal); @@ -33,12 +35,12 @@ void main(void) { // the local light that is always present vec4 totalLocalLight = vec4(0.0, 0.0, 0.0, 1.0); - for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) { - float localDiffuse = dot(normalizedNormal, localLightPos[i]); + 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 * gl_LightSource[i+1].diffuse); + totalLocalLight += (localLightVal * vec4( localLightColors[i], 0.0)); } vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + @@ -51,5 +53,4 @@ 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, 0.0); - } diff --git a/interface/resources/shaders/model.vert b/interface/resources/shaders/model.vert index 06ad8a3300..789c90817b 100644 --- a/interface/resources/shaders/model.vert +++ b/interface/resources/shaders/model.vert @@ -19,9 +19,6 @@ varying vec4 position; // the interpolated normal varying vec4 normal; -// local light position that is always present -varying vec4 localLightPos[MAX_LOCAL_LIGHTS]; - void main(void) { // transform and store the normal for interpolation @@ -41,8 +38,4 @@ void main(void) { // use standard pipeline transform gl_Position = ftransform(); - - for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) { - localLightPos[i] = gl_ModelViewMatrixInverse * gl_LightSource[i+1].position; - } } diff --git a/interface/resources/shaders/model_specular_map.frag b/interface/resources/shaders/model_specular_map.frag index a07324cd1b..7038ea84f7 100644 --- a/interface/resources/shaders/model_specular_map.frag +++ b/interface/resources/shaders/model_specular_map.frag @@ -10,6 +10,10 @@ // 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 vec3 localLightDirections[MAX_LOCAL_LIGHTS]; +uniform vec3 localLightColors[MAX_LOCAL_LIGHTS]; // the diffuse texture uniform sampler2D diffuseMap; @@ -28,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 < MAX_LOCAL_LIGHTS; 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 +53,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 44f33da3e2..1ce1baa76f 100644 --- a/interface/resources/shaders/skin_model.vert +++ b/interface/resources/shaders/skin_model.vert @@ -26,9 +26,6 @@ varying vec4 position; // the interpolated normal varying vec4 normal; -// static local light position (inverse from eye space) -varying vec4 localLightPos[MAX_LOCAL_LIGHTS]; - void main(void) { position = vec4(0.0, 0.0, 0.0, 0.0); normal = vec4(0.0, 0.0, 0.0, 0.0); @@ -52,10 +49,4 @@ void main(void) { gl_TexCoord[1] = vec4(dot(gl_EyePlaneS[0], position), dot(gl_EyePlaneT[0], position), dot(gl_EyePlaneR[0], position), 1.0); gl_Position = gl_ProjectionMatrix * position; - - // inverse view to make the light source position static - for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) { - localLightPos[i] = gl_ModelViewMatrixInverse * gl_LightSource[i+1].position; - } - } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 38dbb02a14..e6bd204505 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -61,7 +61,8 @@ Avatar::Avatar() : _moving(false), _collisionGroups(0), _initialized(false), - _shouldRenderBillboard(true) + _shouldRenderBillboard(true), + _numLocalLights(1) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); @@ -82,16 +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::vec4(0.0f, 0.0f, 0.0f, 1.0f); + _localLightColors[i] = glm::vec3(0.0f, 0.0f, 0.0f); _localLightDirections[i] = glm::vec3(0.0f, 0.0f, 1.0f); } + + glm::vec3 darkGrayColor(0.3f, 0.3f, 0.3f); + 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] = greenColor; - // initialize first light - _localLightColors[0].r = 0.2f; _localLightColors[0].g = 0.2f, _localLightColors[0].b = 0.2f; - _localLightDirections[0].x = 1.0f; _localLightDirections[0].y = 0.0f; _localLightDirections[0].z = 0.0f; - + _localLightDirections[0] = directionX; + _localLightDirections[1] = directionY; } glm::vec3 Avatar::getChestPosition() const { @@ -243,13 +251,19 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { : 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]); - glm::vec4 localLight = glm::vec4(normalized, 1.0f); - // local light parameters - glLightfv(GL_LIGHT1 + i, GL_POSITION, glm::value_ptr(localLight)); - glLightfv(GL_LIGHT1 + i, GL_DIFFUSE, glm::value_ptr(_localLightColors[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 @@ -1133,8 +1147,24 @@ void Avatar::setLocalLightDirection(const glm::vec3& direction, int lightIndex) qDebug( "set light %d direction ( %f, %f, %f )\n", lightIndex, direction.x, direction.y, direction.z ); } -void Avatar::setLocalLightColor(const glm::vec4& color, int lightIndex) { +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 059264d43a..79718c40df 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -36,8 +36,6 @@ const int HAIR_STRANDS = 150; // Number of strands of hair const int HAIR_LINKS = 10; // Number of links in a hair strand const int HAIR_MAX_CONSTRAINTS = 2; // Hair verlet is connected to at most how many others -const int MAX_LOCAL_LIGHTS = 6; - enum DriveKeys { FWD = 0, BACK, @@ -157,8 +155,10 @@ public: public slots: void updateCollisionGroups(); void setLocalLightDirection(const glm::vec3& direction, int lightIndex); - void setLocalLightColor(const glm::vec4& color, 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); @@ -183,8 +183,9 @@ protected: // always-present local lighting for the avatar glm::vec3 _localLightDirections[MAX_LOCAL_LIGHTS]; - glm::vec4 _localLightColors[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/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/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 6fe57ea705..053e928ca3 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1489,14 +1489,18 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re program->setUniform(skinLocations->shadowDistances, Application::getInstance()->getShadowDistances()); } - } else { + // local light + 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); @@ -1623,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); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 5e29b869e0..89b8843cf9 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..bcc80960d7 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..17d0ee0d22 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/voxels/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp index e76a1cee7f..2354bbf13e 100644 --- a/interface/src/voxels/VoxelSystem.cpp +++ b/interface/src/voxels/VoxelSystem.cpp @@ -59,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), @@ -72,7 +74,7 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels, VoxelTree* tree) _renderer(0), _drawHaze(false), _farHazeDistance(300.0f), - _hazeColor(0.24f, 0.27f, 0.34f) + _hazeColor(grayColor) { _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0; @@ -542,11 +544,8 @@ void VoxelSystem::initVoxelMemory() { GLfloat fogColor[] = {_hazeColor.x, _hazeColor.y, _hazeColor.z, 1.0f}; glFogi(GL_FOG_MODE, GL_LINEAR); glFogfv(GL_FOG_COLOR, fogColor); - glFogf(GL_FOG_DENSITY, 0.25f); - glHint(GL_FOG_HINT, GL_DONT_CARE); glFogf(GL_FOG_START, 0.0f); glFogf(GL_FOG_END, _farHazeDistance); - glEnable(GL_FOG); } } @@ -1431,6 +1430,10 @@ void VoxelSystem::render() { } } else if (!_usePrimitiveRenderer) { + if (_drawHaze) { + glEnable(GL_FOG); + } + PerformanceWarning warn(showWarnings, "render().. TRIANGLES..."); { @@ -1502,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); From a8cf19925677ae7df0387df87a223123ba399f54 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 12:08:41 -0700 Subject: [PATCH 070/135] added format labels in interface for new audio stats --- interface/src/ui/Stats.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 0165f82591..e86e656f2e 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -291,7 +291,7 @@ void Stats::display( const AudioStreamStats& audioMixerAvatarStreamStats = audio->getAudioMixerAvatarStreamStats(); const QHash& audioMixerInjectedStreamStatsMap = audio->getAudioMixerInjectedStreamStatsMap(); - lines = _expanded ? 12 + audioMixerInjectedStreamStatsMap.size() * 3: 3; + lines = _expanded ? 12 + (audioMixerInjectedStreamStatsMap.size() + 1) * 3: 3; drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10); horizontalOffset += 5; @@ -328,12 +328,21 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color); char audioMixerStatsLabelString[] = "AudioMixer stats:"; - char streamStatsFormatLabelString[] = "early/late/lost, jframes"; + char streamStatsFormatLabelString[] = "early/late/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:"; From a2225da13d53dfba7ce9436f3896fc26438ed796 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Wed, 9 Jul 2014 12:11:06 -0700 Subject: [PATCH 071/135] Code cleanup --- interface/src/ui/ApplicationOverlay.cpp | 135 ++++++++++-------------- 1 file changed, 53 insertions(+), 82 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index b9b10f697e..15dc18e74e 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -95,8 +95,6 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { glMatrixMode(GL_PROJECTION); glPushMatrix(); - printf("%d %d\n", glWidget->width(), glWidget->height()); - glLoadIdentity(); gluOrtho2D(0, glWidget->width(), glWidget->height(), 0); glDisable(GL_DEPTH_TEST); @@ -656,8 +654,6 @@ void ApplicationOverlay::renderControllerPointers() { void ApplicationOverlay::renderPointersOculus() { - const bool useLaser = true; - Application* application = Application::getInstance(); QGLWidget* glWidget = application->getGLWidget(); glm::vec3 cursorVerts[4]; @@ -676,94 +672,72 @@ void ApplicationOverlay::renderPointersOculus() { //Controller Pointers for (int i = 0; i < myAvatar->getHand()->getNumPalms(); i++) { - if (useLaser && i < myAvatar->getHand()->getNumPalms()) { - - PalmData& palm = myAvatar->getHand()->getPalms()[i]; - if (palm.isActive()) { - glm::vec3 tip = OculusManager::getLaserPointerTipPosition(&palm); - glm::quat orientation = glm::inverse(myAvatar->getOrientation()); - glm::vec3 dir = orientation * glm::normalize(eyePos - tip); //direction of ray goes towards camera - glm::vec3 tipPos = (tip - eyePos); + + PalmData& palm = myAvatar->getHand()->getPalms()[i]; + if (palm.isActive()) { + glm::vec3 tip = OculusManager::getLaserPointerTipPosition(&palm); + glm::quat orientation = glm::inverse(myAvatar->getOrientation()); + glm::vec3 dir = orientation * glm::normalize(eyePos - tip); //direction of ray goes towards camera + glm::vec3 tipPos = (tip - eyePos); - float t; - float length = glm::length(eyePos - tip); - float size = 0.03f * length; + 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; + 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; + cursorVerts[0] = -right + up; + cursorVerts[1] = right + up; + cursorVerts[2] = right - up; + cursorVerts[3] = -right - up; - glPushMatrix(); - // glLoadIdentity(); + 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); + // 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); - // This is the original lookAt vector for the object - // in world coordinates - glm::vec3 direction(0, 0, 1); - glTranslatef(tip.x, tip.y, tip.z); + //Translate the cursor to the tip of the oculus ray + glTranslatef(tip.x, tip.y, tip.z); - // 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); + 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); + // compute the angle + float angleCosine = glm::dot(direction, cursorToCameraXZ); - // perform the rotation. The if statement is used for stability reasons - // if the lookAt and objToCamProj vectors are too close together then - // |angleCosine| could be bigger than 1 due to lack of precision - if ((angleCosine < 0.999999) && (angleCosine > -0.999999)) { - glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, upAux[0], upAux[1], upAux[2]); - } + //Rotate in XZ direction + glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, upAux[0], upAux[1], upAux[2]); - // so far it is just like the cylindrical billboard. The code for the - // second rotation comes now - // The second part tilts the object so that it faces the camera + glm::vec3 cursorToCamera = glm::normalize(-tipPos); - // objToCam is the vector in world coordinates from - // the local origin to the camera - glm::vec3 cursorToCamera = glm::normalize(-tipPos); + // Compute the angle between cursorToCameraXZ and cursorToCamera, + angleCosine = glm::dot(cursorToCameraXZ, cursorToCamera); - // Compute the angle between objToCamProj and objToCam, - //i.e. compute the required angle for the lookup vector - - angleCosine = glm::dot(cursorToCameraXZ, cursorToCamera); - - // Tilt the object. The test is done to prevent instability - // when objToCam and objToCamProj have a very small - // angle between them - - // if ((angleCosine < 0.9999) && (angleCosine > -0.99999)) { - 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(); + //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(); } } @@ -775,8 +749,6 @@ void ApplicationOverlay::renderPointersOculus() { mouseX -= reticleSize / 2; mouseY += reticleSize / 2; - printf("MOUSEPOS: %f %f\n", mouseX, mouseY); - //Get new UV coordinates from our magnification window float newULeft = mouseX / widgetWidth; float newURight = (mouseX + reticleSize) / widgetWidth; @@ -807,7 +779,6 @@ void ApplicationOverlay::renderPointersOculus() { glColor4f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], _alpha); - const glm::quat& orientation = myAvatar->getOrientation(); cursorVerts[0] = orientation * glm::vec3(lX, tY, -tlZ) + eyePos; cursorVerts[1] = orientation * glm::vec3(rX, tY, -trZ) + eyePos; From ef4b95c6e0b1c50da01fce0bce9312e4ce5b8077 Mon Sep 17 00:00:00 2001 From: TonyPeng Date: Wed, 9 Jul 2014 13:27:42 -0700 Subject: [PATCH 072/135] Merged from master. Get rid of redundant GL fog states. Moved local light parameters to shader uniform arrays. --- examples/avatarLocalLight.js | 8 ++++---- interface/resources/shaders/model.frag | 2 +- interface/resources/shaders/model.vert | 1 + interface/resources/shaders/model_specular_map.frag | 3 ++- interface/resources/shaders/skin_model.vert | 1 - interface/src/avatar/Avatar.cpp | 2 +- interface/src/avatar/Avatar.h | 2 +- interface/src/renderer/Model.cpp | 2 +- interface/src/renderer/Model.h | 4 ++-- interface/src/renderer/ProgramObject.cpp | 2 +- interface/src/renderer/ProgramObject.h | 2 +- interface/src/voxels/VoxelSystem.cpp | 2 +- 12 files changed, 16 insertions(+), 15 deletions(-) diff --git a/examples/avatarLocalLight.js b/examples/avatarLocalLight.js index 69b491d430..57f9d84ffe 100644 --- a/examples/avatarLocalLight.js +++ b/examples/avatarLocalLight.js @@ -23,19 +23,19 @@ function keyPressEvent(event) { if (event.text == "1") { currentSelection = 0; - print("selection = " + currentSelection); + print("light election = " + currentSelection); } else if (event.text == "2" ) { currentSelection = 1; - print("selection = " + currentSelection); + print("light selection = " + currentSelection); } else if (event.text == "3" ) { currentSelection = 2; - print("selection = " + currentSelection); + print("light selection = " + currentSelection); } else if (event.text == "4" ) { currentSelection = 3; - print("selection = " + currentSelection); + print("light selection = " + currentSelection); } else if (event.text == "5" ) { localLightColors[currentSelection].x += 0.01; diff --git a/interface/resources/shaders/model.frag b/interface/resources/shaders/model.frag index 95afaa0e5f..468a892686 100644 --- a/interface/resources/shaders/model.frag +++ b/interface/resources/shaders/model.frag @@ -16,7 +16,7 @@ uniform sampler2D diffuseMap; // local lights -const int MAX_LOCAL_LIGHTS = 2; // 2 lights for now, will probably need more later on +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]; diff --git a/interface/resources/shaders/model.vert b/interface/resources/shaders/model.vert index 789c90817b..13eee2fa3d 100644 --- a/interface/resources/shaders/model.vert +++ b/interface/resources/shaders/model.vert @@ -39,3 +39,4 @@ void main(void) { // use standard pipeline transform gl_Position = ftransform(); } + diff --git a/interface/resources/shaders/model_specular_map.frag b/interface/resources/shaders/model_specular_map.frag index 7038ea84f7..329da65e9e 100644 --- a/interface/resources/shaders/model_specular_map.frag +++ b/interface/resources/shaders/model_specular_map.frag @@ -12,6 +12,7 @@ // const int MAX_LOCAL_LIGHTS = 2; +uniform int numLocalLights; uniform vec3 localLightDirections[MAX_LOCAL_LIGHTS]; uniform vec3 localLightColors[MAX_LOCAL_LIGHTS]; @@ -35,7 +36,7 @@ void main(void) { // the local light that is always present vec4 totalLocalLight = vec4(0.0, 0.0, 0.0, 1.0); - for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) { + 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; diff --git a/interface/resources/shaders/skin_model.vert b/interface/resources/shaders/skin_model.vert index 1ce1baa76f..943daf9061 100644 --- a/interface/resources/shaders/skin_model.vert +++ b/interface/resources/shaders/skin_model.vert @@ -13,7 +13,6 @@ const int MAX_CLUSTERS = 128; const int INDICES_PER_VERTEX = 4; -const int MAX_LOCAL_LIGHTS = 4; uniform mat4 clusterMatrices[MAX_CLUSTERS]; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index e6bd204505..83105c9022 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -86,7 +86,7 @@ void Avatar::init() { 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, 1.0f); + _localLightDirections[i] = glm::vec3(0.0f, 0.0f, 0.0f); } glm::vec3 darkGrayColor(0.3f, 0.3f, 0.3f); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 79718c40df..31161e657b 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -184,7 +184,7 @@ protected: // always-present local lighting for the avatar glm::vec3 _localLightDirections[MAX_LOCAL_LIGHTS]; glm::vec3 _localLightColors[MAX_LOCAL_LIGHTS]; - int _numLocalLights; + int _numLocalLights; // protected methods... glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; } diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 053e928ca3..8b8557709c 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1489,7 +1489,7 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re program->setUniform(skinLocations->shadowDistances, Application::getInstance()->getShadowDistances()); } - // local light + // local light uniforms skinProgram->setUniformValue("numLocalLights", _numLocalLights); skinProgram->setUniformArray("localLightDirections", _localLightDirections, MAX_LOCAL_LIGHTS); skinProgram->setUniformArray("localLightColors", _localLightColors, MAX_LOCAL_LIGHTS); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 89b8843cf9..9e2e0d8348 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -166,8 +166,8 @@ protected: glm::vec3 _localLightDirections[MAX_LOCAL_LIGHTS]; glm::vec3 _localLightColors[MAX_LOCAL_LIGHTS]; - int _numLocalLights; - + int _numLocalLights; + QVector _jointStates; class MeshState { diff --git a/interface/src/renderer/ProgramObject.cpp b/interface/src/renderer/ProgramObject.cpp index bcc80960d7..16b3461ad0 100644 --- a/interface/src/renderer/ProgramObject.cpp +++ b/interface/src/renderer/ProgramObject.cpp @@ -26,7 +26,7 @@ void ProgramObject::setUniform(const char* name, const glm::vec3& value) { 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++) { + for (int i = 0; i < count; i++) { assert(index < count*3); const float* valPtr = glm::value_ptr(values[i]); floatVal[index++] = valPtr[0]; diff --git a/interface/src/renderer/ProgramObject.h b/interface/src/renderer/ProgramObject.h index 17d0ee0d22..8e66ce9bc9 100644 --- a/interface/src/renderer/ProgramObject.h +++ b/interface/src/renderer/ProgramObject.h @@ -23,7 +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); + void setUniformArray(const char* name, const glm::vec3* values, int count); }; #endif // hifi_ProgramObject_h diff --git a/interface/src/voxels/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp index 2354bbf13e..6c19cfd4c2 100644 --- a/interface/src/voxels/VoxelSystem.cpp +++ b/interface/src/voxels/VoxelSystem.cpp @@ -540,7 +540,7 @@ void VoxelSystem::initVoxelMemory() { _readArraysLock.unlock(); // fog for haze - if(_drawHaze) { + if (_drawHaze) { GLfloat fogColor[] = {_hazeColor.x, _hazeColor.y, _hazeColor.z, 1.0f}; glFogi(GL_FOG_MODE, GL_LINEAR); glFogfv(GL_FOG_COLOR, fogColor); From 6ffa09f9e62b3c2fb2436b19cf9dec7a800fd996 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 9 Jul 2014 13:58:54 -0700 Subject: [PATCH 073/135] modified scope of devices stats --- interface/src/Application.cpp | 21 ++++++++------------- interface/src/devices/Faceshift.cpp | 2 ++ interface/src/devices/JoystickManager.cpp | 4 +++- interface/src/devices/PrioVR.cpp | 2 ++ interface/src/devices/SixenseManager.cpp | 7 ++++++- interface/src/devices/Visage.cpp | 2 ++ 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e5aa1d0050..d7464f57a1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1853,8 +1853,6 @@ void Application::updateMouseRay() { } void Application::updateFaceshift() { - PerformanceTimer perfTimer("faceshift"); - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateFaceshift()"); @@ -1868,8 +1866,6 @@ void Application::updateFaceshift() { } void Application::updateVisage() { - PerformanceTimer perfTimer("visage"); - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateVisage()"); @@ -2055,18 +2051,17 @@ void Application::update(float deltaTime) { updateLOD(); updateMouseRay(); // check what's under the mouse and update the mouse voxel - updateFaceshift(); - updateVisage(); - + { + PerformanceTimer perfTimer("devices"); + updateFaceshift(); + updateVisage(); + _sixenseManager.update(deltaTime); + _joystickManager.update(); + _prioVR.update(deltaTime); + } { PerformanceTimer perfTimer("myAvatar"); updateMyAvatarLookAtPosition(); - { - PerformanceTimer perfTimer("devices"); - _sixenseManager.update(deltaTime); - _joystickManager.update(); - _prioVR.update(deltaTime); - } updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes } 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/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 026c1d3eb4..d44bc33dc4 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -11,6 +11,8 @@ #include +#include + #include "Application.h" #include "SixenseManager.h" #include "UserActivityLogger.h" @@ -83,7 +85,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"); } 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; From efe443e2ff5b930b10bc8c075152af8e7b286b96 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 9 Jul 2014 14:01:46 -0700 Subject: [PATCH 074/135] Very basic metavoxel save/load. --- .../src/metavoxels/MetavoxelServer.cpp | 59 +++++++++++++++++++ .../src/metavoxels/MetavoxelServer.h | 23 ++++++++ libraries/metavoxels/src/Bitstream.cpp | 6 ++ libraries/metavoxels/src/Bitstream.h | 5 ++ 4 files changed, 93 insertions(+) diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index 14765e2ddc..928a693527 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -10,6 +10,9 @@ // #include +#include +#include +#include #include @@ -44,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 the 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() { @@ -67,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()); @@ -193,3 +214,41 @@ void MetavoxelSession::sendPacketGroup(int alreadySent) { _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; + } + QDebug debug = qDebug() << "Reading from" << SAVE_FILE << "..."; + file.open(QIODevice::ReadOnly); + QDataStream inStream(&file); + Bitstream in(inStream); + MetavoxelData data; + try { + in >> data; + } catch (const BitstreamException& e) { + debug << "failed, " << e.getDescription(); + return; + } + QMetaObject::invokeMethod(_server, "setData", Q_ARG(const MetavoxelData&, data)); + debug << "done."; +} + +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 6df769227c..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; @@ -88,4 +95,20 @@ private: 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/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index bc662aa890..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); diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 70fde94b79..dacfeb2ee9 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -290,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 From f9036dbaecbff5d7a4eceb018d40e51902febb80 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 9 Jul 2014 14:22:33 -0700 Subject: [PATCH 075/135] Lock the attribute mapping, since it can be accessed from multiple threads. --- libraries/metavoxels/src/AttributeRegistry.cpp | 9 +++++++++ libraries/metavoxels/src/AttributeRegistry.h | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 33ce298859..b43759fa4f 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); 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; From f463099d16a5504f66426456e4a95010529a5d08 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 9 Jul 2014 14:31:47 -0700 Subject: [PATCH 076/135] add "simulate" stat name to Avatar::simulate() --- interface/src/avatar/Avatar.cpp | 1 + interface/src/avatar/MyAvatar.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 26c8e3f2fc..db36300b55 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -99,6 +99,7 @@ float Avatar::getLODDistance() const { } void Avatar::simulate(float deltaTime) { + PerformanceTimer perfTimer("simulate"); if (_scale != _targetScale) { setScale(_targetScale); } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 13c11dfefc..04e7b628c9 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -130,7 +130,6 @@ void MyAvatar::update(float deltaTime) { void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); - if (_scale != _targetScale) { float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale; setScale(scale); From 63bcee02297299f51785585890eb2ee7ba0530d5 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 9 Jul 2014 14:33:09 -0700 Subject: [PATCH 077/135] fix whitespace formatting --- libraries/shared/src/PerfStat.cpp | 4 ++-- libraries/shared/src/PerfStat.h | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/libraries/shared/src/PerfStat.cpp b/libraries/shared/src/PerfStat.cpp index 908dca61e7..b811a719bc 100644 --- a/libraries/shared/src/PerfStat.cpp +++ b/libraries/shared/src/PerfStat.cpp @@ -90,8 +90,8 @@ PerformanceTimer::~PerformanceTimer() { // static void PerformanceTimer::tallyAllTimerRecords() { - QMap::iterator recordsItr = _records.begin(); - QMap::const_iterator recordsEnd = _records.end(); + QMap::iterator recordsItr = _records.begin(); + QMap::const_iterator recordsEnd = _records.end(); quint64 now = usecTimestampNow(); while (recordsItr != recordsEnd) { recordsItr.value().tallyResult(now); diff --git a/libraries/shared/src/PerfStat.h b/libraries/shared/src/PerfStat.h index 69bda0af5e..3cbf188c83 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, @@ -62,13 +62,13 @@ public: quint64 getCount() const { return _numTallies; } private: - quint64 _runningTotal; + quint64 _runningTotal; quint64 _lastTotal; quint64 _numAccumulations; - quint64 _numTallies; + quint64 _numTallies; quint64 _expiry; - SimpleMovingAverage _movingAverage; -}; + SimpleMovingAverage _movingAverage; + class PerformanceTimer { public: @@ -89,10 +89,10 @@ public: static void dumpAllTimerRecords(); private: - quint64 _start; - QString _name; + quint64 _start; + QString _name; static QString _fullName; - static QMap _records; + static QMap _records; }; From 9ce6844e9d50549192b0155f185f8cc6e0f47a7f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 9 Jul 2014 14:39:08 -0700 Subject: [PATCH 078/135] fix accidental broken class definition --- libraries/shared/src/PerfStat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/PerfStat.h b/libraries/shared/src/PerfStat.h index 3cbf188c83..4f94be73b1 100644 --- a/libraries/shared/src/PerfStat.h +++ b/libraries/shared/src/PerfStat.h @@ -68,7 +68,7 @@ private: quint64 _numTallies; quint64 _expiry; SimpleMovingAverage _movingAverage; - +}; class PerformanceTimer { public: From f2d49fa6a1b911118d84a416deaaaa2fa0f5dedf Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 9 Jul 2014 15:12:29 -0700 Subject: [PATCH 079/135] Rename focus indicators menu item and default to off --- interface/src/Menu.cpp | 2 +- interface/src/Menu.h | 2 +- interface/src/avatar/Avatar.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 019ce1df9e..beb6e18e5c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -406,7 +406,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::GlowWhenSpeaking, 0, true); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::HideFocusIndicators, 0, false); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::FocusIndicators, 0, false); QMenu* oculusOptionsMenu = developerMenu->addMenu("Oculus Options"); addCheckableActionToQMenuAndActionHash(oculusOptionsMenu, MenuOption::DisplayOculusOverlays, 0, true); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 32458e1883..b66d3f1107 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -369,6 +369,7 @@ namespace MenuOption { const QString Faceshift = "Faceshift"; const QString FilterSixense = "Smooth Sixense Movement"; const QString FirstPerson = "First Person"; + const QString FocusIndicators = "Focus Indicators"; const QString FrameTimer = "Show Timer"; const QString FrustumRenderMode = "Render Mode"; const QString Fullscreen = "Fullscreen"; @@ -382,7 +383,6 @@ namespace MenuOption { const QString ObeyEnvironmentalGravity = "Obey Environmental Gravity"; const QString HandsCollideWithSelf = "Collide With Self"; const QString HeadMouse = "Head Mouse"; - const QString HideFocusIndicators = "Hide Focus Indicators"; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IncreaseVoxelSize = "Increase Voxel Size"; const QString LoadScript = "Open and Run Script File..."; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index e2e111c210..c7d01ba996 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -257,7 +257,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 && !Menu::getInstance()->isOptionChecked(MenuOption::HideFocusIndicators)) { + 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 }; From 81e168f6578246859a9501794616281b055c6ece Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 15:38:03 -0700 Subject: [PATCH 080/135] added RingBufferHistory template class, used it in SentPacketHistory and MovingMinMaxAvg --- .../networking/src/SentPacketHistory.cpp | 23 +-- libraries/networking/src/SentPacketHistory.h | 6 +- libraries/shared/src/MovingMinMaxAvg.h | 190 ++++++++---------- libraries/shared/src/RingBufferHistory.h | 146 ++++++++++++++ tests/shared/src/MovingMinMaxAvgTests.cpp | 4 +- 5 files changed, 234 insertions(+), 135 deletions(-) create mode 100644 libraries/shared/src/RingBufferHistory.h 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/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 5263e2ab6c..abe43cdfac 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -12,9 +12,48 @@ #ifndef hifi_MovingMinMaxAvg_h #define hifi_MovingMinMaxAvg_h +#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. @@ -25,104 +64,51 @@ public: // new sample, instantiate this class with MovingMinMaxAvg(1, 100). MovingMinMaxAvg(int intervalLength, int windowIntervals) - : _min(std::numeric_limits::max()), - _max(std::numeric_limits::min()), - _average(0.0), - _samplesCollected(0), - _intervalLength(intervalLength), + : _intervalLength(intervalLength), _windowIntervals(windowIntervals), + _overallStats(), + _samplesCollected(0), + _windowStats(), _existingSamplesInCurrentInterval(0), - _existingIntervals(0), - _windowMin(std::numeric_limits::max()), - _windowMax(std::numeric_limits::min()), - _windowAverage(0.0), - _currentIntervalMin(std::numeric_limits::max()), - _currentIntervalMax(std::numeric_limits::min()), - _currentIntervalAverage(0.0), - _newestIntervalStatsAt(0), + _currentIntervalStats(), + _intervalStats(windowIntervals), _newStatsAvailable(false) - { - _intervalMins = new T[_windowIntervals]; - _intervalMaxes = new T[_windowIntervals]; - _intervalAverages = new double[_windowIntervals]; - } - - ~MovingMinMaxAvg() { - delete[] _intervalMins; - delete[] _intervalMaxes; - delete[] _intervalAverages; - } + {} void reset() { - _min = std::numeric_limits::max(); - _max = std::numeric_limits::min(); - _average = 0.0; + _overallStats = Stats(); _samplesCollected = 0; + _windowStats = Stats(); _existingSamplesInCurrentInterval = 0; - _existingIntervals = 0; - _windowMin = std::numeric_limits::max(); - _windowMax = std::numeric_limits::min(); - _windowAverage = 0.0; - _currentIntervalMin = std::numeric_limits::max(); - _currentIntervalMax = std::numeric_limits::min(); - _currentIntervalAverage = 0.0; - _newStatsAvailableFlag = false; + _currentIntervalStats = Stats(); + _intervalStats.clear(); + _newStatsAvailable = false; } void update(T newSample) { // update overall stats - if (newSample < _min) { - _min = newSample; - } - if (newSample > _max) { - _max = newSample; - } - updateAverage(_average, _samplesCollected, (double)newSample); + _overallStats.updateWithSample(newSample, _samplesCollected); // update the current interval stats - if (newSample < _currentIntervalMin) { - _currentIntervalMin = newSample; - } - if (newSample > _currentIntervalMax) { - _currentIntervalMax = newSample; - } - updateAverage(_currentIntervalAverage, _existingSamplesInCurrentInterval, (double)newSample); + _currentIntervalStats.updateWithSample(newSample, _existingSamplesInCurrentInterval); // if the current interval of samples is now full, record its stats into our past intervals' stats if (_existingSamplesInCurrentInterval == _intervalLength) { - // increment index of the newest interval's stats cyclically - _newestIntervalStatsAt = _newestIntervalStatsAt == _windowIntervals - 1 ? 0 : _newestIntervalStatsAt + 1; - // record current interval's stats, then reset them - _intervalMins[_newestIntervalStatsAt] = _currentIntervalMin; - _intervalMaxes[_newestIntervalStatsAt] = _currentIntervalMax; - _intervalAverages[_newestIntervalStatsAt] = _currentIntervalAverage; - _currentIntervalMin = std::numeric_limits::max(); - _currentIntervalMax = std::numeric_limits::min(); - _currentIntervalAverage = 0.0; + _intervalStats.insert(_currentIntervalStats); + _currentIntervalStats = Stats(); _existingSamplesInCurrentInterval = 0; - if (_existingIntervals < _windowIntervals) { - _existingIntervals++; - } - - // update the window's stats - int k = _newestIntervalStatsAt; - _windowMin = _intervalMins[k]; - _windowMax = _intervalMaxes[k]; - _windowAverage = _intervalAverages[k]; - int intervalsIncludedInWindowStats = 1; - while (intervalsIncludedInWindowStats < _existingIntervals) { - k = k == 0 ? _windowIntervals - 1 : k - 1; - if (_intervalMins[k] < _windowMin) { - _windowMin = _intervalMins[k]; - } - if (_intervalMaxes[k] > _windowMax) { - _windowMax = _intervalMaxes[k]; - } - updateAverage(_windowAverage, intervalsIncludedInWindowStats, _intervalAverages[k]); + // update the window's stats by combining the intervals' stats + RingBufferHistory::Iterator i = _intervalStats.begin(); + RingBufferHistory::Iterator end = _intervalStats.end(); + _windowStats = Stats(); + int intervalsIncludedInWindowStats = 0; + while (i != end) { + _windowStats.updateWithOtherStats(*i, intervalsIncludedInWindowStats); + i++; } _newStatsAvailable = true; @@ -133,48 +119,34 @@ public: bool getNewStatsAvailableFlag() const { return _newStatsAvailable; } void clearNewStatsAvailableFlag() { _newStatsAvailable = false; } - T getMin() const { return _min; } - T getMax() const { return _max; } - double getAverage() const { return _average; } - T getWindowMin() const { return _windowMin; } - T getWindowMax() const { return _windowMax; } - double getWindowAverage() const { return _windowAverage; } + 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: - void updateAverage(double& average, int& numSamples, double newSample) { - // update some running average without overflowing it - average = average * ((double)numSamples / (numSamples + 1)) + newSample / (numSamples + 1); - numSamples++; - } - -private: - // these are min/max/avg stats for all samples collected. - T _min; - T _max; - double _average; - int _samplesCollected; - int _intervalLength; int _windowIntervals; - int _existingSamplesInCurrentInterval; - int _existingIntervals; + // 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 - T _windowMin; - T _windowMax; - double _windowAverage; + Stats _windowStats; + int _existingSamplesInCurrentInterval; - T _currentIntervalMin; - T _currentIntervalMax; - double _currentIntervalAverage; + // these are the min/max/avg stats for the current interval + Stats _currentIntervalStats; - T* _intervalMins; - T* _intervalMaxes; - double* _intervalAverages; - int _newestIntervalStatsAt; + // these are stored stats for the past intervals in the window + RingBufferHistory _intervalStats; bool _newStatsAvailable; }; -#endif // hifi_OctalCode_h +#endif // hifi_MovingMinMaxAvg_h diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h new file mode 100644 index 0000000000..094963ddc6 --- /dev/null +++ b/libraries/shared/src/RingBufferHistory.h @@ -0,0 +1,146 @@ +// +// 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 + +template +class RingBufferHistory { + +public: + + RingBufferHistory(int capacity) + : _size(capacity + 1), + _capacity(capacity), + _newestEntryAt(0), + _numEntries(0) + { + _buffer = new T[_size]; + } + + RingBufferHistory(const RingBufferHistory& other) + : _size(other._size), + _capacity(other._capacity), + _newestEntryAt(other._newestEntryAt), + _numEntries(other._numEntries) + { + _buffer = new T[_size]; + memcpy(_buffer, other._buffer, _size*sizeof(T)); + } + + RingBufferHistory& operator= (const RingBufferHistory& rhs) { + _size = rhs._size; + _capacity = rhs._capacity; + _newestEntryAt = rhs._newestEntryAt; + _numEntries = rhs._numEntries; + delete[] _buffer; + _buffer = new T[_size]; + memcpy(_buffer, rhs._buffer, _size*sizeof(T)); + } + + ~RingBufferHistory() { + delete[] _buffer; + } + + void clear() { + _numEntries = 0; + } + + void insert(const T& entry) { + // increment newest entry index cyclically + _newestEntryAt = (_newestEntryAt == _size - 1) ? 0 : _newestEntryAt + 1; + + // insert new entry + _buffer[_newestEntryAt] = 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 = _newestEntryAt - 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 &_buffer[_newestEntryAt]; + } + + T* getNewestEntry() { + return &_buffer[_newestEntryAt]; + } + + int getCapacity() const { return _capacity; } + int getNumEntries() const { return _numEntries; } + +private: + T* _buffer; + int _size; + int _capacity; + int _newestEntryAt; + int _numEntries; + + + +public: + class Iterator : public std::iterator < std::forward_iterator_tag, T > { + public: + Iterator(T* buffer, int size, T* at) : _buffer(buffer), _bufferEnd(buffer+size), _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 == _buffer) ? _bufferEnd - 1 : _at - 1; + return *this; + } + + Iterator& operator++(int) { + Iterator tmp(*this); + ++(*this); + return tmp; + } + + private: + T* const _buffer; + T* const _bufferEnd; + T* _at; + }; + + Iterator begin() { return Iterator(_buffer, _size, &_buffer[_newestEntryAt]); } + + Iterator end() { + int endAt = _newestEntryAt - _numEntries; + if (endAt < 0) { + endAt += _size; + } + return Iterator(_buffer, _size, &_buffer[endAt]); + } +}; + +#endif // hifi_RingBufferHistory_h diff --git a/tests/shared/src/MovingMinMaxAvgTests.cpp b/tests/shared/src/MovingMinMaxAvgTests.cpp index ae23248e5f..108db82e35 100644 --- a/tests/shared/src/MovingMinMaxAvgTests.cpp +++ b/tests/shared/src/MovingMinMaxAvgTests.cpp @@ -59,7 +59,7 @@ void MovingMinMaxAvgTests::runAllTests() { assert(stats.getMin() == min); assert(stats.getMax() == max); - assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001 || abs(stats.getAverage() - average) < 0.000001); if ((i + 1) % INTERVAL_LENGTH == 0) { @@ -78,7 +78,7 @@ void MovingMinMaxAvgTests::runAllTests() { assert(stats.getWindowMin() == windowMin); assert(stats.getWindowMax() == windowMax); - assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001 || abs(stats.getAverage() - average) < 0.000001); } else { assert(!stats.getNewStatsAvailableFlag()); From d412e21008ddceff111179e3cdcf0ad371dec8e7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 9 Jul 2014 15:51:37 -0700 Subject: [PATCH 081/135] add two new concert cameras to examples --- examples/concertCamera_kims.js | 72 ++++++++++++++++++++++++++++++++++ examples/concertCamera_kyrs.js | 72 ++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 examples/concertCamera_kims.js create mode 100644 examples/concertCamera_kyrs.js 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); From 3b7945fc14a83c9bf87a2484651be44d66175a50 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 9 Jul 2014 15:52:23 -0700 Subject: [PATCH 082/135] Gather and print out some stats after loading the data. --- .../src/metavoxels/MetavoxelServer.cpp | 25 ++++++++------- libraries/metavoxels/src/MetavoxelData.cpp | 31 +++++++++++++++++++ libraries/metavoxels/src/MetavoxelData.h | 4 +++ 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index 928a693527..956458404a 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -226,19 +226,22 @@ void MetavoxelPersister::load() { if (!file.exists()) { return; } - QDebug debug = qDebug() << "Reading from" << SAVE_FILE << "..."; - file.open(QIODevice::ReadOnly); - QDataStream inStream(&file); - Bitstream in(inStream); MetavoxelData data; - try { - in >> data; - } catch (const BitstreamException& e) { - debug << "failed, " << e.getDescription(); - return; + { + 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."; } - QMetaObject::invokeMethod(_server, "setData", Q_ARG(const MetavoxelData&, data)); - debug << "done."; + data.dumpStats(); } void MetavoxelPersister::save(const MetavoxelData& data) { diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 2d61ede796..4b142d0f5e 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include @@ -627,6 +628,25 @@ bool MetavoxelData::deepEquals(const MetavoxelData& other, const MetavoxelLOD& l return true; } +void MetavoxelData::dumpStats(QDebug debug) const { + QDebugStateSaver saver(debug); + debug.nospace() << "[size=" << _size << ", roots=["; + int totalInternal = 0, totalLeaves = 0; + 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()->countDescendants(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; } @@ -1061,6 +1081,17 @@ void MetavoxelNode::getSpanners(const AttributePointer& attribute, const glm::ve } } +void MetavoxelNode::countDescendants(int& internalNodes, int& leaves) const { + if (isLeaf()) { + leaves++; + return; + } + internalNodes++; + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i]->countDescendants(internalNodes, leaves); + } +} + int MetavoxelVisitor::encodeOrder(int first, int second, int third, int fourth, int fifth, int sixth, int seventh, int eighth) { return first | (second << 3) | (third << 6) | (fourth << 9) | diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 6a7ba33eb5..347d5a1f0b 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -125,6 +125,8 @@ public: /// shallow comparison). bool deepEquals(const MetavoxelData& other, 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 +223,8 @@ public: void getSpanners(const AttributePointer& attribute, const glm::vec3& minimum, float size, const MetavoxelLOD& lod, SharedObjectSet& results) const; + void countDescendants(int& internalNodes, int& leaves) const; + private: Q_DISABLE_COPY(MetavoxelNode) From 090f1fd8521389912d0cca030f5ce25b7cb895de Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Wed, 9 Jul 2014 16:09:06 -0700 Subject: [PATCH 083/135] Fixed bug with 3rd person UI ray picking --- interface/src/ui/ApplicationOverlay.cpp | 18 +++++++++++------- interface/src/ui/ApplicationOverlay.h | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 15dc18e74e..0d05de76c5 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -165,7 +165,8 @@ 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(); + glm::quat rot = myAvatar->getOrientation(); //invert y direction y = 1.0 - y; @@ -177,8 +178,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(rot * 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 @@ -275,7 +279,7 @@ QPoint ApplicationOverlay::getOculusPalmClickLocation(PalmData *palm) const { 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(eyePos - tip); //direction of ray goes towards camera + glm::vec3 dir = orientation * glm::normalize(application->getCamera()->getPosition() - tip); //direction of ray goes towards camera glm::vec3 tipPos = orientation * (tip - eyePos); QPoint rv; @@ -299,6 +303,8 @@ QPoint ApplicationOverlay::getOculusPalmClickLocation(PalmData *palm) const { rv.setX(u * glWidget->width()); rv.setY(v * glWidget->height()); + + printf("%d %d\n", rv.x(), rv.y()); } } else { //if they did not click on the overlay, just set the coords to INT_MAX @@ -374,7 +380,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { renderTexturedHemisphere(); - renderPointersOculus(); + renderPointersOculus(whichCamera.getPosition()); glDepthMask(GL_TRUE); glBindTexture(GL_TEXTURE_2D, 0); @@ -652,7 +658,7 @@ void ApplicationOverlay::renderControllerPointers() { } } -void ApplicationOverlay::renderPointersOculus() { +void ApplicationOverlay::renderPointersOculus(const glm::vec3& eyePos) { Application* application = Application::getInstance(); QGLWidget* glWidget = application->getGLWidget(); @@ -668,8 +674,6 @@ void ApplicationOverlay::renderPointersOculus() { glMatrixMode(GL_MODELVIEW); MyAvatar* myAvatar = application->getAvatar(); - glm::vec3 eyePos = myAvatar->getHead()->calculateAverageEyePosition(); - //Controller Pointers for (int i = 0; i < myAvatar->getHand()->getNumPalms(); i++) { diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 117dd02f3b..726d0a3769 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -49,7 +49,7 @@ private: void renderPointers(); void renderControllerPointers(); - void renderPointersOculus(); + void renderPointersOculus(const glm::vec3& eyePos); void renderMagnifier(int mouseX, int mouseY, float sizeMult, bool showBorder) const; void renderAudioMeter(); void renderStatsAndLogs(); From 8147f2cd91b5471e5565406bab94c2664ffbb5b0 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Wed, 9 Jul 2014 16:30:00 -0700 Subject: [PATCH 084/135] Fixed some magnification window bugs --- interface/src/ui/ApplicationOverlay.cpp | 44 ++++++++++++------------- interface/src/ui/ApplicationOverlay.h | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 0d05de76c5..c064bb040a 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -191,7 +191,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++) { @@ -267,7 +267,7 @@ bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, } -QPoint ApplicationOverlay::getOculusPalmClickLocation(PalmData *palm) const { +QPoint ApplicationOverlay::getOculusPalmClickLocation(const PalmData *palm) const { Application* application = Application::getInstance(); QGLWidget* glWidget = application->getGLWidget(); @@ -303,8 +303,6 @@ QPoint ApplicationOverlay::getOculusPalmClickLocation(PalmData *palm) const { rv.setX(u * glWidget->width()); rv.setY(v * glWidget->height()); - - printf("%d %d\n", rv.x(), rv.y()); } } else { //if they did not click on the overlay, just set the coords to INT_MAX @@ -600,13 +598,32 @@ void ApplicationOverlay::renderControllerPointers() { } 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 @@ -621,24 +638,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; diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 726d0a3769..fc282c2903 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -32,7 +32,7 @@ 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(PalmData *palm) const; + QPoint getOculusPalmClickLocation(const PalmData *palm) const; // Getters QOpenGLFramebufferObject* getFramebufferObject(); From 56ff74dd56334b9300fba948c239843a6098b263 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Wed, 9 Jul 2014 16:39:05 -0700 Subject: [PATCH 085/135] Removed ApplicationOverlay::resize() --- interface/src/Application.cpp | 1 - interface/src/ui/ApplicationOverlay.cpp | 14 +++----------- interface/src/ui/ApplicationOverlay.h | 5 +---- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 42184bbdda..06ba3e442e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -723,7 +723,6 @@ void Application::resizeGL(int width, int height) { resetCamerasOnResizeGL(_myCamera, width, height); glViewport(0, 0, width, height); // shouldn't this account for the menu??? - _applicationOverlay.resize(); updateProjectionMatrix(); glLoadIdentity(); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index caf103c00c..9ec87483dc 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -1189,20 +1189,12 @@ void ApplicationOverlay::renderTexturedHemisphere() { } -void ApplicationOverlay::resize() { - if (_framebufferObject != NULL) { - delete _framebufferObject; - _framebufferObject = NULL; - } - // _framebufferObject is recreated at the correct size the next time it is accessed via getFramebufferObject(). -} - QOpenGLFramebufferObject* ApplicationOverlay::getFramebufferObject() { QSize size = Application::getInstance()->getGLWidget()->size(); if (!_framebufferObject || _framebufferObject->size() != size) { - if (_framebufferObject){ - delete _framebufferObject; - } + + delete _framebufferObject; + _framebufferObject = new QOpenGLFramebufferObject(size); glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index a956858051..a64e26ed67 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -32,11 +32,8 @@ 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; -<<<<<<< HEAD QPoint getOculusPalmClickLocation(const PalmData *palm) const; -======= - void resize(); ->>>>>>> af6704a83cec1d69b3dc3e1145238919fcb82933 + // Getters QOpenGLFramebufferObject* getFramebufferObject(); From 8fa7bf4fa091714b7010b49f16d44f2f794d2adc Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 9 Jul 2014 16:43:46 -0700 Subject: [PATCH 086/135] rename: ParentFrame --> ConstrainedFrame --- interface/src/avatar/FaceModel.cpp | 8 ++--- interface/src/avatar/SkeletonModel.cpp | 6 ++-- interface/src/renderer/JointState.cpp | 48 +++++++++++++------------- interface/src/renderer/JointState.h | 12 +++---- interface/src/renderer/Model.cpp | 8 ++--- 5 files changed, 41 insertions(+), 41 deletions(-) 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/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/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 8b8557709c..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); @@ -1787,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; } } From 004db004a6318d3762ffe8d394529ab65e0eb304 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Wed, 9 Jul 2014 17:03:09 -0700 Subject: [PATCH 087/135] Dynamically resize lasers so they always end on UI --- interface/src/devices/OculusManager.cpp | 12 ++++++++++-- interface/src/ui/ApplicationOverlay.cpp | 18 ++++++++++++++++++ interface/src/ui/ApplicationOverlay.h | 1 + 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 2e6d4cb9a2..b3c478e5e9 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -490,8 +490,16 @@ void OculusManager::renderLaserPointer() { glm::vec3 OculusManager::getLaserPointerTipPosition(const PalmData* palm) { #ifdef HAVE_LIBOVR - const float PALM_TIP_ROD_LENGTH_MULT = 4.0f; - return palm->getPosition() + (palm->getTipPosition() - palm->getPosition()) * PALM_TIP_ROD_LENGTH_MULT; + 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 result; + if (applicationOverlay.calculateRayUICollisionPoint(palm->getPosition(), direction, result)) { + return result; + } + + return palm->getPosition(); #endif return glm::vec3(0.0f); } diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 9ec87483dc..6d8af6641f 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -311,6 +311,24 @@ QPoint ApplicationOverlay::getOculusPalmClickLocation(const PalmData *palm) cons return rv; } +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, 1, &t)){ + result = position + direction * t; + return true; + } + + return false; +} + // Draws the FBO texture for Oculus rift. void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index a64e26ed67..e355665b95 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -33,6 +33,7 @@ public: 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 From dbd7ec18b0355fb3c157e0e7c4812830c781e025 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 9 Jul 2014 17:15:05 -0700 Subject: [PATCH 088/135] Show some stats on the client (node counts under LOD, reliable delta download progress). --- interface/src/ui/Stats.cpp | 36 ++++++++++++++++++- .../metavoxels/src/DatagramSequencer.cpp | 11 ++++++ libraries/metavoxels/src/DatagramSequencer.h | 4 +++ .../metavoxels/src/MetavoxelClientManager.cpp | 4 +++ .../metavoxels/src/MetavoxelClientManager.h | 2 ++ libraries/metavoxels/src/MetavoxelData.cpp | 23 ++++++++---- libraries/metavoxels/src/MetavoxelData.h | 6 +++- 7 files changed, 77 insertions(+), 9 deletions(-) diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 3de21b449b..b0cbea14a9 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -377,7 +377,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 +419,40 @@ 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 received = 0, total = 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()); + int clientReceived = 0, clientTotal = 0; + if (client->getReliableDeltaProgress(clientReceived, clientTotal)) { + received += clientReceived; + total += clientTotal; + } + } + } + } + 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 (total > 0) { + stringstream reliableDelta; + reliableDelta << "Reliable Delta: " << (received * 100 / total) << "%"; + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, reliableDelta.str().c_str(), color); + } } verticalOffset = 0; diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index 536cfc9dfb..8c58233eb9 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -692,6 +692,17 @@ void ReliableChannel::sendMessage(const QVariant& message) { endMessage(); } +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) { ClearSharedObjectMessage message = { id }; sendMessage(QVariant::fromValue(message)); diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index b6dce464f7..998e196a05 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -376,6 +376,10 @@ public: /// writes the message to the bitstream, then calls endMessage). void sendMessage(const QVariant& message); + /// 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. diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp index f3ea1ae8c5..94d9116794 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.cpp +++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp @@ -94,6 +94,10 @@ MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelClientM SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&))); } +bool MetavoxelClient::getReliableDeltaProgress(int& received, int& total) const { + return _reliableDeltaChannel && _reliableDeltaChannel->getMessageReceiveProgress(received, total); +} + void MetavoxelClient::guide(MetavoxelVisitor& visitor) { visitor.setLOD(_manager->getLOD()); _data.guide(visitor); diff --git a/libraries/metavoxels/src/MetavoxelClientManager.h b/libraries/metavoxels/src/MetavoxelClientManager.h index ad6c86c8fc..809718aa01 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.h +++ b/libraries/metavoxels/src/MetavoxelClientManager.h @@ -53,6 +53,8 @@ public: MetavoxelData& getData() { return _data; } + bool getReliableDeltaProgress(int& received, int& total) const; + void guide(MetavoxelVisitor& visitor); void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false); diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 4b142d0f5e..1362731a8a 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -628,17 +628,25 @@ 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()->countDescendants(internal, leaves); + it.value()->countNodes(it.key(), minimum, _size, MetavoxelLOD(), internal, leaves); debug << internal << " internal, " << leaves << " leaves, " << (internal + leaves) << " total"; totalInternal += internal; totalLeaves += leaves; @@ -1076,19 +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::countDescendants(int& internalNodes, int& leaves) const { - if (isLeaf()) { +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; } - internalNodes++; + internal++; + float nextSize = size * 0.5f; for (int i = 0; i < CHILD_COUNT; i++) { - _children[i]->countDescendants(internalNodes, leaves); + _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 347d5a1f0b..f558bf8e80 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -125,6 +125,9 @@ 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; @@ -223,7 +226,8 @@ public: void getSpanners(const AttributePointer& attribute, const glm::vec3& minimum, float size, const MetavoxelLOD& lod, SharedObjectSet& results) const; - void countDescendants(int& internalNodes, int& leaves) 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) From a785ceb3da8726236875df11ed10320845c06fee Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Wed, 9 Jul 2014 17:33:41 -0700 Subject: [PATCH 089/135] Restored UI fade animation --- interface/src/Application.cpp | 4 ---- interface/src/devices/OculusManager.cpp | 11 +++++------ interface/src/devices/OculusManager.h | 2 +- interface/src/ui/ApplicationOverlay.cpp | 3 +++ 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 06ba3e442e..9842144bff 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2786,10 +2786,6 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { { PerformanceTimer perfTimer("avatars"); - if (OculusManager::isConnected()) { - OculusManager::renderLaserPointer(); - } - _avatarManager.renderAvatars(mirrorMode ? Avatar::MIRROR_RENDER_MODE : Avatar::NORMAL_RENDER_MODE, selfAvatarOnly); } diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index b3c478e5e9..574aa3c6f6 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::UserInterface); - + //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 @@ -467,7 +464,8 @@ QSize OculusManager::getRenderTargetSize() { #endif } -void OculusManager::renderLaserPointer() { +//Renders sixense laser pointers for UI selection in the oculus +void OculusManager::renderLaserPointers() { #ifdef HAVE_LIBOVR const float PALM_TIP_ROD_RADIUS = 0.002f; @@ -488,6 +486,7 @@ void OculusManager::renderLaserPointer() { #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(); diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h index 73ce6ef2d3..871e5d649a 100644 --- a/interface/src/devices/OculusManager.h +++ b/interface/src/devices/OculusManager.h @@ -44,7 +44,7 @@ public: static QSize getRenderTargetSize(); /// Renders a laser pointer for UI picking - static void renderLaserPointer(); + static void renderLaserPointers(); static glm::vec3 getLaserPointerTipPosition(const PalmData* palm); private: diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 6d8af6641f..6324ee03b0 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -340,6 +340,9 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { MyAvatar* myAvatar = application->getAvatar(); + //Render the sixense lasers + OculusManager::renderLaserPointers(); + glActiveTexture(GL_TEXTURE0); glEnable(GL_BLEND); From 01f10024aed637881a17e517181cfe58862b764c Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 18:13:33 -0700 Subject: [PATCH 090/135] all stats added, needs testing; created PacketStreamStats struct --- .../src/audio/AudioMixerClientData.cpp | 26 +++--- interface/src/Audio.cpp | 76 +++++++++++++-- interface/src/Audio.h | 34 ++++++- interface/src/ui/Stats.cpp | 92 +++++++++++++------ libraries/audio/src/AudioRingBuffer.h | 2 + libraries/audio/src/AudioStreamStats.h | 17 +--- .../networking/src/SequenceNumberStats.cpp | 34 +++---- .../networking/src/SequenceNumberStats.h | 45 ++++++--- libraries/shared/src/RingBufferHistory.h | 5 +- 9 files changed, 223 insertions(+), 108 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 9e27103bd1..f8d97dc44e 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -188,13 +188,13 @@ AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const Positio streamStats._ringBufferOverflowCount = ringBuffer->getOverflowCount(); streamStats._ringBufferSilentFramesDropped = ringBuffer->getSilentFramesDropped(); - 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(); + streamStats._packetStreamStats._numReceived = streamSequenceNumberStats->getNumReceived(); + streamStats._packetStreamStats._numUnreasonable = streamSequenceNumberStats->getNumUnreasonable(); + streamStats._packetStreamStats._numEarly = streamSequenceNumberStats->getNumEarly(); + streamStats._packetStreamStats._numLate = streamSequenceNumberStats->getNumLate(); + streamStats._packetStreamStats._numLost = streamSequenceNumberStats->getNumLost(); + streamStats._packetStreamStats._numRecovered = streamSequenceNumberStats->getNumRecovered(); + streamStats._packetStreamStats._numDuplicate = streamSequenceNumberStats->getNumDuplicate(); return streamStats; } @@ -261,9 +261,9 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " not mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + " silents dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " early:" + QString::number(streamStats._packetsEarly) - + " late:" + QString::number(streamStats._packetsLate) - + " lost:" + QString::number(streamStats._packetsLost) + + " early:" + QString::number(streamStats._packetStreamStats._numEarly) + + " late:" + QString::number(streamStats._packetStreamStats._numLate) + + " lost:" + QString::number(streamStats._packetStreamStats._numLost) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) @@ -284,9 +284,9 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " not mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + " silents dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " early:" + QString::number(streamStats._packetsEarly) - + " late:" + QString::number(streamStats._packetsLate) - + " lost:" + QString::number(streamStats._packetsLost) + + " early:" + QString::number(streamStats._packetStreamStats._numEarly) + + " late:" + QString::number(streamStats._packetStreamStats._numLate) + + " lost:" + QString::number(streamStats._packetStreamStats._numLost) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index cc455c5544..2073659e89 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -48,6 +48,11 @@ static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_ static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300; +static const int AUDIO_STREAM_STATS_HISTORY_SIZE = 30; + +const int TIME_GAPS_STATS_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS; +const int TIME_GAP_STATS_WINDOW_INTERVALS = 30; + // Mute icon configration static const int MUTE_ICON_SIZE = 24; @@ -103,8 +108,13 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) : _scopeInput(0), _scopeOutputLeft(0), _scopeOutputRight(0), - _audioMixerAvatarStreamStats(), - _outgoingAvatarAudioSequenceNumber(0) + _audioMixerAvatarStreamAudioStats(), + _audioMixerAvatarStreamPacketStatsHistory(AUDIO_STREAM_STATS_HISTORY_SIZE), + _outgoingAvatarAudioSequenceNumber(0), + _incomingStreamPacketStatsHistory(AUDIO_STREAM_STATS_HISTORY_SIZE), + _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,9 +130,20 @@ void Audio::init(QGLWidget *parent) { void Audio::reset() { _ringBuffer.reset(); + + _starveCount = 0; + _consecutiveNotMixedCount = 0; + + _audioMixerAvatarStreamAudioStats = AudioStreamStats(); + _audioMixerInjectedStreamAudioStatsMap.clear(); + + _audioMixerAvatarStreamPacketStatsHistory.clear(); + _audioMixerInjectedStreamPacketStatsHistoryMap.clear(); + _outgoingAvatarAudioSequenceNumber = 0; - _audioMixerInjectedStreamStatsMap.clear(); _incomingMixedAudioSequenceNumberStats.reset(); + + _incomingStreamPacketStatsHistory.clear(); } QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) { @@ -672,7 +693,7 @@ void Audio::handleAudioInput() { // memcpy our orientation memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); currentPacketPtr += sizeof(headOrientation); - +if (randFloat() < 0.95f) nodeList->writeDatagram(audioDataPacket, numAudioBytes + leadingBytes, audioMixer); _outgoingAvatarAudioSequenceNumber++; @@ -689,7 +710,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 +749,8 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { quint8 appendFlag = *(reinterpret_cast(dataAt)); dataAt += sizeof(quint8); if (!appendFlag) { - _audioMixerInjectedStreamStatsMap.clear(); + _audioMixerInjectedStreamAudioStatsMap.clear(); + _audioMixerInjectedStreamPacketStatsHistoryMap.clear(); } // parse the number of stream stats structs to follow @@ -740,11 +764,21 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { dataAt += sizeof(AudioStreamStats); if (streamStats._streamType == PositionalAudioRingBuffer::Microphone) { - _audioMixerAvatarStreamStats = streamStats; + _audioMixerAvatarStreamAudioStats = streamStats; + _audioMixerAvatarStreamPacketStatsHistory.insert(streamStats._packetStreamStats); } else { - _audioMixerInjectedStreamStatsMap[streamStats._streamIdentifier] = streamStats; + if (!_audioMixerInjectedStreamAudioStatsMap.contains(streamStats._streamIdentifier)) { + _audioMixerInjectedStreamPacketStatsHistoryMap.insert(streamStats._streamIdentifier, + RingBufferHistory(AUDIO_STREAM_STATS_HISTORY_SIZE)); + } + _audioMixerInjectedStreamAudioStatsMap[streamStats._streamIdentifier] = streamStats; + _audioMixerInjectedStreamPacketStatsHistoryMap[streamStats._streamIdentifier].insert(streamStats._packetStreamStats); } } + + // when an audio stream stats packet is received, also record the current packets received and lost + // in the packet loss stats history + _incomingStreamPacketStatsHistory.insert(_incomingMixedAudioSequenceNumberStats.getStats()); } // NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo @@ -867,6 +901,9 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) { //qDebug() << "Audio output just starved."; _ringBuffer.setIsStarved(true); _numFramesDisplayStarve = 10; + + _starveCount++; + _consecutiveNotMixedCount = 0; } int numNetworkOutputSamples; @@ -886,6 +923,7 @@ 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; @@ -1515,3 +1553,25 @@ int Audio::calculateNumberOfFrameSamples(int numBytes) { int frameSamples = (int)(numBytes * CALLBACK_ACCELERATOR_RATIO + 0.5f) / sizeof(int16_t); return frameSamples; } + +void Audio::calculatePacketLossRate(const RingBufferHistory& statsHistory, + float& overallLossRate, float& windowLossRate) const { + + int numHistoryEntries = statsHistory.getNumEntries(); + if (numHistoryEntries == 0) { + overallLossRate = 0.0f; + windowLossRate = 0.0f; + } else { + const PacketStreamStats& newestStats = *statsHistory.getNewestEntry(); + overallLossRate = (float)newestStats._numLost / newestStats._numReceived; + + if (numHistoryEntries == 1) { + windowLossRate = overallLossRate; + } else { + int age = std::min(numHistoryEntries-1, AUDIO_STREAM_STATS_HISTORY_SIZE-1); + const PacketStreamStats& oldestStats = *statsHistory.get(age); + windowLossRate = (float)(newestStats._numLost - oldestStats._numLost) + / (newestStats._numReceived - oldestStats._numReceived); + } + } +} diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 9f04e5cb03..e6e06838d3 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 @@ -107,8 +109,22 @@ 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 RingBufferHistory& getAudioMixerAvatarStreamPacketStatsHistory() const { return _audioMixerAvatarStreamPacketStatsHistory; } + const QHash >& getAudioMixerInjectedStreamPacketStatsHistoryMap() const {return _audioMixerInjectedStreamPacketStatsHistoryMap; } + const RingBufferHistory& getIncomingStreamPacketStatsHistory() const { return _incomingStreamPacketStatsHistory; } + const MovingMinMaxAvg& getInterframeTimeGapStats() const { return _interframeTimeGapStats; } + + void calculatePacketLossRate(const RingBufferHistory& statsHistory, + float& overallLossRate, float& windowLossRate) const; signals: bool muteToggled(); @@ -241,11 +257,21 @@ private: QByteArray* _scopeOutputLeft; QByteArray* _scopeOutputRight; - AudioStreamStats _audioMixerAvatarStreamStats; - QHash _audioMixerInjectedStreamStatsMap; + int _starveCount; + int _consecutiveNotMixedCount; + + AudioStreamStats _audioMixerAvatarStreamAudioStats; + QHash _audioMixerInjectedStreamAudioStatsMap; + + RingBufferHistory _audioMixerAvatarStreamPacketStatsHistory; + QHash > _audioMixerInjectedStreamPacketStatsHistoryMap; quint16 _outgoingAvatarAudioSequenceNumber; SequenceNumberStats _incomingMixedAudioSequenceNumberStats; + + RingBufferHistory _incomingStreamPacketStatsHistory; + + MovingMinMaxAvg _interframeTimeGapStats; }; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index e86e656f2e..01032aebc8 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -288,15 +288,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 ? 12 + (audioMixerInjectedStreamStatsMap.size() + 1) * 3: 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,7 +325,7 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color); char audioMixerStatsLabelString[] = "AudioMixer stats:"; - char streamStatsFormatLabelString[] = "early/late/lost"; + 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"; @@ -349,61 +346,98 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color); - const SequenceNumberStats& downstreamAudioSequenceNumberStats = audio->getIncomingMixedAudioSequenceNumberStats(); - char downstreamAudioStatsString[30]; + /* const SequenceNumberStats& downstreamAudioSequenceNumberStats = audio->getIncomingMixedAudioSequenceNumberStats(); + sprintf(downstreamAudioStatsString, " mix: %d/%d/%d, %d", downstreamAudioSequenceNumberStats.getNumEarly(), downstreamAudioSequenceNumberStats.getNumLate(), downstreamAudioSequenceNumberStats.getNumLost(), audio->getJitterBufferSamples() / NETWORK_BUFFER_LENGTH_SAMPLES_STEREO); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color);*/ + + float packetLossRate, packetLossRate30s; + + char downstreamAudioStatsString[30]; + + audio->calculatePacketLossRate(audio->getIncomingStreamPacketStatsHistory(), packetLossRate, packetLossRate30s); + + sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %d/?/%d", packetLossRate*100.0f, packetLossRate30s*100.0f, + audio->getDownstreamRingBuffer().framesAvailable(), audio->getDesiredJitterBufferFrames()); + verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); + + const MovingMinMaxAvg& timeGapStats = audio->getInterframeTimeGapStats(); + + sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %d/%d", timeGapStats.getMin(), timeGapStats.getMax(), + timeGapStats.getAverage(), audio->getStarveCount(), audio->getDownstreamRingBuffer().getOverflowCount()); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); + + sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %d/?", timeGapStats.getWindowMin(), timeGapStats.getWindowMax(), + timeGapStats.getWindowAverage(), audio->getConsecutiveNotMixedCount()); + + 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/%d/%d", audioMixerAvatarStreamStats._packetsEarly, - audioMixerAvatarStreamStats._packetsLate, audioMixerAvatarStreamStats._packetsLost, - audioMixerAvatarStreamStats._ringBufferFramesAvailable, audioMixerAvatarStreamStats._ringBufferCurrentJitterBufferFrames, - audioMixerAvatarStreamStats._ringBufferDesiredJitterBufferFrames); + + const AudioStreamStats& audioMixerAvatarAudioStreamStats = audio->getAudioMixerAvatarStreamAudioStats(); + + audio->calculatePacketLossRate(audio->getAudioMixerAvatarStreamPacketStatsHistory(), packetLossRate, packetLossRate30s); + + sprintf(upstreamAudioStatsString, " mic: %.1f%%/%.1f%%, %u/%u/%u", packetLossRate*100.0f, packetLossRate30s*100.0f, + audioMixerAvatarAudioStreamStats._ringBufferFramesAvailable, audioMixerAvatarAudioStreamStats._ringBufferCurrentJitterBufferFrames, + audioMixerAvatarAudioStreamStats._ringBufferDesiredJitterBufferFrames); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarStreamStats._timeGapMin, - audioMixerAvatarStreamStats._timeGapMax, audioMixerAvatarStreamStats._timeGapAverage, - audioMixerAvatarStreamStats._ringBufferStarveCount, audioMixerAvatarStreamStats._ringBufferOverflowCount); + 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", audioMixerAvatarStreamStats._timeGapMovingMin, - audioMixerAvatarStreamStats._timeGapMovingMax, audioMixerAvatarStreamStats._timeGapMovingAverage, - audioMixerAvatarStreamStats._ringBufferConsecutiveNotMixedCount, audioMixerAvatarStreamStats._ringBufferSilentFramesDropped); + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapMovingMin, + audioMixerAvatarAudioStreamStats._timeGapMovingMax, audioMixerAvatarAudioStreamStats._timeGapMovingAverage, + audioMixerAvatarAudioStreamStats._ringBufferConsecutiveNotMixedCount, audioMixerAvatarAudioStreamStats._ringBufferSilentFramesDropped); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - foreach(AudioStreamStats injectedStreamStats, audioMixerInjectedStreamStatsMap) { - sprintf(upstreamAudioStatsString, " inj: %d/%d/%d, %d/%d/%d", injectedStreamStats._packetsEarly, - injectedStreamStats._packetsLate, injectedStreamStats._packetsLost, - injectedStreamStats._ringBufferFramesAvailable, injectedStreamStats._ringBufferCurrentJitterBufferFrames, - injectedStreamStats._ringBufferDesiredJitterBufferFrames); + QHash > audioMixerInjectedStreamPacketStatsHistoryMap + = audio->getAudioMixerInjectedStreamPacketStatsHistoryMap(); + + foreach(const AudioStreamStats& injectedStreamAudioStats, audioMixerInjectedStreamAudioStatsMap) { + + audio->calculatePacketLossRate(audioMixerInjectedStreamPacketStatsHistoryMap[injectedStreamAudioStats._streamIdentifier], + packetLossRate, packetLossRate30s); + + sprintf(upstreamAudioStatsString, " inj: %.1f%%/%.1f%%, %u/%u/%u", packetLossRate*100.0f, packetLossRate30s*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", injectedStreamStats._timeGapMin, - injectedStreamStats._timeGapMax, injectedStreamStats._timeGapAverage, - injectedStreamStats._ringBufferStarveCount, injectedStreamStats._ringBufferOverflowCount); + 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", injectedStreamStats._timeGapMovingMin, - injectedStreamStats._timeGapMovingMax, injectedStreamStats._timeGapMovingAverage, - injectedStreamStats._ringBufferConsecutiveNotMixedCount, injectedStreamStats._ringBufferSilentFramesDropped); + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapMovingMin, + injectedStreamAudioStats._timeGapMovingMax, injectedStreamAudioStats._timeGapMovingAverage, + injectedStreamAudioStats._ringBufferConsecutiveNotMixedCount, injectedStreamAudioStats._ringBufferSilentFramesDropped); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 38f1adec21..9f049fc5e8 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -67,6 +67,8 @@ public: 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 2c66187309..191e10ba8e 100644 --- a/libraries/audio/src/AudioStreamStats.h +++ b/libraries/audio/src/AudioStreamStats.h @@ -13,6 +13,7 @@ #define hifi_AudioStreamStats_h #include "PositionalAudioRingBuffer.h" +#include "SequenceNumberStats.h" class AudioStreamStats { public: @@ -32,13 +33,7 @@ public: _ringBufferConsecutiveNotMixedCount(0), _ringBufferOverflowCount(0), _ringBufferSilentFramesDropped(0), - _packetsReceived(0), - _packetsUnreasonable(0), - _packetsEarly(0), - _packetsLate(0), - _packetsLost(0), - _packetsRecovered(0), - _packetsDuplicate(0) + _packetStreamStats() {} PositionalAudioRingBuffer::Type _streamType; @@ -59,13 +54,7 @@ public: quint32 _ringBufferOverflowCount; quint32 _ringBufferSilentFramesDropped; - quint32 _packetsReceived; - quint32 _packetsUnreasonable; - quint32 _packetsEarly; - quint32 _packetsLate; - quint32 _packetsLost; - quint32 _packetsRecovered; - quint32 _packetsDuplicate; + PacketStreamStats _packetStreamStats; }; #endif // hifi_AudioStreamStats_h diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index 3f696a522b..6a26e613b6 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -16,26 +16,14 @@ SequenceNumberStats::SequenceNumberStats() : _lastReceived(std::numeric_limits::max()), _missingSet(), - _numReceived(0), - _numUnreasonable(0), - _numEarly(0), - _numLate(0), - _numLost(0), - _numRecovered(0), - _numDuplicate(0), + _stats(), _lastSenderUUID() { } void SequenceNumberStats::reset() { _missingSet.clear(); - _numReceived = 0; - _numUnreasonable = 0; - _numEarly = 0; - _numLate = 0; - _numLost = 0; - _numRecovered = 0; - _numDuplicate = 0; + _stats = PacketStreamStats(); } static const int UINT16_RANGE = std::numeric_limits::max() + 1; @@ -51,9 +39,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 +68,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 +80,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 +98,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 +107,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++; } } } diff --git a/libraries/networking/src/SequenceNumberStats.h b/libraries/networking/src/SequenceNumberStats.h index 88c8748b03..b399c23d2b 100644 --- a/libraries/networking/src/SequenceNumberStats.h +++ b/libraries/networking/src/SequenceNumberStats.h @@ -17,6 +17,26 @@ const int MAX_REASONABLE_SEQUENCE_GAP = 1000; +class PacketStreamStats { +public: + PacketStreamStats() + : _numReceived(0), + _numUnreasonable(0), + _numEarly(0), + _numLate(0), + _numLost(0), + _numRecovered(0), + _numDuplicate(0) + {} + quint32 _numReceived; + quint32 _numUnreasonable; + quint32 _numEarly; + quint32 _numLate; + quint32 _numLost; + quint32 _numRecovered; + quint32 _numDuplicate; +}; + class SequenceNumberStats { public: SequenceNumberStats(); @@ -25,27 +45,22 @@ public: 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; } + 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; } const QSet& getMissingSet() const { return _missingSet; } private: quint16 _lastReceived; QSet _missingSet; - quint32 _numReceived; - quint32 _numUnreasonable; - quint32 _numEarly; - quint32 _numLate; - quint32 _numLost; - quint32 _numRecovered; - quint32 _numDuplicate; + PacketStreamStats _stats; QUuid _lastSenderUUID; }; diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index 094963ddc6..e33723b2b1 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -20,7 +20,7 @@ class RingBufferHistory { public: - RingBufferHistory(int capacity) + RingBufferHistory(int capacity = 10) : _size(capacity + 1), _capacity(capacity), _newestEntryAt(0), @@ -47,6 +47,7 @@ public: delete[] _buffer; _buffer = new T[_size]; memcpy(_buffer, rhs._buffer, _size*sizeof(T)); + return *this; } ~RingBufferHistory() { @@ -120,7 +121,7 @@ public: return *this; } - Iterator& operator++(int) { + Iterator operator++(int) { Iterator tmp(*this); ++(*this); return tmp; From 7fcf149f708f4b6b2448b26db8e6914739f93fc2 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 18:14:58 -0700 Subject: [PATCH 091/135] removed if randFloat --- interface/src/Audio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 2073659e89..e81683b213 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -693,7 +693,7 @@ void Audio::handleAudioInput() { // memcpy our orientation memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); currentPacketPtr += sizeof(headOrientation); -if (randFloat() < 0.95f) + nodeList->writeDatagram(audioDataPacket, numAudioBytes + leadingBytes, audioMixer); _outgoingAvatarAudioSequenceNumber++; From 0d83b9a6f6aa02bd0c5d109fdf8dbf0ed05d0951 Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 09:51:01 -0700 Subject: [PATCH 092/135] tidied up code --- .../src/audio/AudioMixerClientData.cpp | 18 ++++++------ .../src/audio/AvatarAudioRingBuffer.cpp | 2 +- interface/src/Audio.cpp | 5 ++-- interface/src/ui/Stats.cpp | 20 ++++--------- libraries/audio/src/AudioStreamStats.h | 12 ++++---- .../audio/src/InjectedAudioRingBuffer.cpp | 2 +- .../audio/src/PositionalAudioRingBuffer.cpp | 2 +- .../audio/src/PositionalAudioRingBuffer.h | 2 +- libraries/shared/src/MovingMinMaxAvg.h | 4 --- libraries/shared/src/RingBufferHistory.h | 28 +++++++++---------- 10 files changed, 41 insertions(+), 54 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index f8d97dc44e..c624c1dc01 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -176,9 +176,9 @@ AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const Positio streamStats._timeGapMin = timeGapStats.getMin(); streamStats._timeGapMax = timeGapStats.getMax(); streamStats._timeGapAverage = timeGapStats.getAverage(); - streamStats._timeGapMovingMin = timeGapStats.getWindowMin(); - streamStats._timeGapMovingMax = timeGapStats.getWindowMax(); - streamStats._timeGapMovingAverage = timeGapStats.getWindowAverage(); + streamStats._timeGapWindowMin = timeGapStats.getWindowMin(); + streamStats._timeGapWindowMax = timeGapStats.getWindowMax(); + streamStats._timeGapWindowAverage = timeGapStats.getWindowAverage(); streamStats._ringBufferFramesAvailable = ringBuffer->framesAvailable(); streamStats._ringBufferCurrentJitterBufferFrames = ringBuffer->getCurrentJitterBufferFrames(); @@ -267,9 +267,9 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) - + " min 30s gap:" + QString::number(streamStats._timeGapMovingMin) - + " max 30s gap:" + QString::number(streamStats._timeGapMovingMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapMovingAverage, 'g', 2); + + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) + + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) + + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'g', 2); } else { result = "mic unknown"; } @@ -290,9 +290,9 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) - + " min 30s gap:" + QString::number(streamStats._timeGapMovingMin) - + " max 30s gap:" + QString::number(streamStats._timeGapMovingMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapMovingAverage, 'g', 2); + + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) + + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) + + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'g', 2); } } return result; diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp index 0177bc48ea..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) { - frameReceived(); + timeGapStatsFrameReceived(); updateDesiredJitterBufferFrames(); _shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index e81683b213..17e9054568 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -50,6 +50,7 @@ static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300; static const int AUDIO_STREAM_STATS_HISTORY_SIZE = 30; +// audio frames time gap stats (min/max/avg) for last ~30 seconds are recalculated every ~1 second const int TIME_GAPS_STATS_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS; const int TIME_GAP_STATS_WINDOW_INTERVALS = 30; @@ -776,8 +777,8 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { } } - // when an audio stream stats packet is received, also record the current packets received and lost - // in the packet loss stats history + // when an audio stream stats packet is received, also record the downstream packets stats in the history + // for calculating packet loss rates _incomingStreamPacketStatsHistory.insert(_incomingMixedAudioSequenceNumberStats.getStats()); } diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 01032aebc8..25d5ee44bc 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -341,22 +341,12 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString4, color); + float packetLossRate, packetLossRate30s; char downstreamLabelString[] = " Downstream:"; verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color); - /* const SequenceNumberStats& downstreamAudioSequenceNumberStats = audio->getIncomingMixedAudioSequenceNumberStats(); - - sprintf(downstreamAudioStatsString, " mix: %d/%d/%d, %d", downstreamAudioSequenceNumberStats.getNumEarly(), - downstreamAudioSequenceNumberStats.getNumLate(), downstreamAudioSequenceNumberStats.getNumLost(), - audio->getJitterBufferSamples() / NETWORK_BUFFER_LENGTH_SAMPLES_STEREO); - - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color);*/ - - float packetLossRate, packetLossRate30s; - char downstreamAudioStatsString[30]; audio->calculatePacketLossRate(audio->getIncomingStreamPacketStatsHistory(), packetLossRate, packetLossRate30s); @@ -406,8 +396,8 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapMovingMin, - audioMixerAvatarAudioStreamStats._timeGapMovingMax, audioMixerAvatarAudioStreamStats._timeGapMovingAverage, + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapWindowMin, + audioMixerAvatarAudioStreamStats._timeGapWindowMax, audioMixerAvatarAudioStreamStats._timeGapWindowAverage, audioMixerAvatarAudioStreamStats._ringBufferConsecutiveNotMixedCount, audioMixerAvatarAudioStreamStats._ringBufferSilentFramesDropped); verticalOffset += STATS_PELS_PER_LINE; @@ -435,8 +425,8 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapMovingMin, - injectedStreamAudioStats._timeGapMovingMax, injectedStreamAudioStats._timeGapMovingAverage, + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapWindowMin, + injectedStreamAudioStats._timeGapWindowMax, injectedStreamAudioStats._timeGapWindowAverage, injectedStreamAudioStats._ringBufferConsecutiveNotMixedCount, injectedStreamAudioStats._ringBufferSilentFramesDropped); verticalOffset += STATS_PELS_PER_LINE; diff --git a/libraries/audio/src/AudioStreamStats.h b/libraries/audio/src/AudioStreamStats.h index 191e10ba8e..2cd0bca880 100644 --- a/libraries/audio/src/AudioStreamStats.h +++ b/libraries/audio/src/AudioStreamStats.h @@ -23,9 +23,9 @@ public: _timeGapMin(0), _timeGapMax(0), _timeGapAverage(0.0f), - _timeGapMovingMin(0), - _timeGapMovingMax(0), - _timeGapMovingAverage(0.0f), + _timeGapWindowMin(0), + _timeGapWindowMax(0), + _timeGapWindowAverage(0.0f), _ringBufferFramesAvailable(0), _ringBufferCurrentJitterBufferFrames(0), _ringBufferDesiredJitterBufferFrames(0), @@ -42,9 +42,9 @@ public: quint64 _timeGapMin; quint64 _timeGapMax; float _timeGapAverage; - quint64 _timeGapMovingMin; - quint64 _timeGapMovingMax; - float _timeGapMovingAverage; + quint64 _timeGapWindowMin; + quint64 _timeGapWindowMax; + float _timeGapWindowAverage; quint32 _ringBufferFramesAvailable; quint16 _ringBufferCurrentJitterBufferFrames; diff --git a/libraries/audio/src/InjectedAudioRingBuffer.cpp b/libraries/audio/src/InjectedAudioRingBuffer.cpp index d3d0cdfb8d..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) { - 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 666b89e568..411b02400d 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -198,7 +198,7 @@ int PositionalAudioRingBuffer::getCalculatedDesiredJitterBufferFrames() const { return calculatedDesiredJitterBufferFrames; } -void PositionalAudioRingBuffer::frameReceived() { +void PositionalAudioRingBuffer::timeGapStatsFrameReceived() { quint64 now = usecTimestampNow(); if (_lastFrameReceivedTime != 0) { quint64 gap = now - _lastFrameReceivedTime; diff --git a/libraries/audio/src/PositionalAudioRingBuffer.h b/libraries/audio/src/PositionalAudioRingBuffer.h index a3adec0117..3a9e7ed124 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.h +++ b/libraries/audio/src/PositionalAudioRingBuffer.h @@ -80,7 +80,7 @@ protected: PositionalAudioRingBuffer(const PositionalAudioRingBuffer&); PositionalAudioRingBuffer& operator= (const PositionalAudioRingBuffer&); - void frameReceived(); + void timeGapStatsFrameReceived(); void updateDesiredJitterBufferFrames(); PositionalAudioRingBuffer::Type _type; diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index abe43cdfac..00aa35dd2c 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -86,7 +86,6 @@ public: } void update(T newSample) { - // update overall stats _overallStats.updateWithSample(newSample, _samplesCollected); @@ -115,7 +114,6 @@ public: } } - bool getNewStatsAvailableFlag() const { return _newStatsAvailable; } void clearNewStatsAvailableFlag() { _newStatsAvailable = false; } @@ -126,8 +124,6 @@ public: T getWindowMax() const { return _windowStats._max; } double getWindowAverage() const { return _windowStats._average; } - - private: int _intervalLength; int _windowIntervals; diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index e33723b2b1..fbb34ed9be 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -23,7 +23,7 @@ public: RingBufferHistory(int capacity = 10) : _size(capacity + 1), _capacity(capacity), - _newestEntryAt(0), + _newestEntryAtIndex(0), _numEntries(0) { _buffer = new T[_size]; @@ -32,7 +32,7 @@ public: RingBufferHistory(const RingBufferHistory& other) : _size(other._size), _capacity(other._capacity), - _newestEntryAt(other._newestEntryAt), + _newestEntryAtIndex(other._newestEntryAtIndex), _numEntries(other._numEntries) { _buffer = new T[_size]; @@ -42,7 +42,7 @@ public: RingBufferHistory& operator= (const RingBufferHistory& rhs) { _size = rhs._size; _capacity = rhs._capacity; - _newestEntryAt = rhs._newestEntryAt; + _newestEntryAtIndex = rhs._newestEntryAtIndex; _numEntries = rhs._numEntries; delete[] _buffer; _buffer = new T[_size]; @@ -60,10 +60,10 @@ public: void insert(const T& entry) { // increment newest entry index cyclically - _newestEntryAt = (_newestEntryAt == _size - 1) ? 0 : _newestEntryAt + 1; + _newestEntryAtIndex = (_newestEntryAtIndex == _size - 1) ? 0 : _newestEntryAtIndex + 1; // insert new entry - _buffer[_newestEntryAt] = entry; + _buffer[_newestEntryAtIndex] = entry; if (_numEntries < _capacity) { _numEntries++; } @@ -75,7 +75,7 @@ public: if (!(entryAge >= 0 && entryAge < _numEntries)) { return NULL; } - int entryAt = _newestEntryAt - entryAge; + int entryAt = _newestEntryAtIndex - entryAge; if (entryAt < 0) { entryAt += _size; } @@ -87,11 +87,11 @@ public: } const T* getNewestEntry() const { - return &_buffer[_newestEntryAt]; + return &_buffer[_newestEntryAtIndex]; } T* getNewestEntry() { - return &_buffer[_newestEntryAt]; + return &_buffer[_newestEntryAtIndex]; } int getCapacity() const { return _capacity; } @@ -101,7 +101,7 @@ private: T* _buffer; int _size; int _capacity; - int _newestEntryAt; + int _newestEntryAtIndex; int _numEntries; @@ -133,14 +133,14 @@ public: T* _at; }; - Iterator begin() { return Iterator(_buffer, _size, &_buffer[_newestEntryAt]); } + Iterator begin() { return Iterator(_buffer, _size, &_buffer[_newestEntryAtIndex]); } Iterator end() { - int endAt = _newestEntryAt - _numEntries; - if (endAt < 0) { - endAt += _size; + int endAtIndex = _newestEntryAtIndex - _numEntries; + if (endAtIndex < 0) { + endAtIndex += _size; } - return Iterator(_buffer, _size, &_buffer[endAt]); + return Iterator(_buffer, _size, &_buffer[endAtIndex]); } }; From d1b96110409db6c8e1cf515de25b52b70975c876 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Thu, 10 Jul 2014 18:52:38 +0200 Subject: [PATCH 093/135] Remove debug output lines. --- interface/src/Application.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 95cfaab4cf..ace265ad4f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -803,7 +803,6 @@ bool Application::event(QEvent* event) { } void Application::keyPressEvent(QKeyEvent* event) { - qDebug("Application::keyPressEvent(%x)", event->key()); _keysPressed.insert(event->key()); @@ -1054,7 +1053,6 @@ void Application::keyPressEvent(QKeyEvent* event) { } void Application::keyReleaseEvent(QKeyEvent* event) { - qDebug("Application::keyReleaseEvent(%x)", event->key()); _keysPressed.remove(event->key()); From 3389b6440cc5aeac1fb2dea661f0b70e2d297fb6 Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 09:53:37 -0700 Subject: [PATCH 094/135] undo .gitignore changes --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index d3af3c4535..4176dcc652 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,3 @@ interface/external/rtmidi/* # Ignore interfaceCache for Linux users interface/interfaceCache/ -tests/shared/src/MovingMinMaxAvgTests.cpp -examples/dancer.js -examples/happyBirthday.js From 37b60a63b2908d2332b4af6f77256b7962d6af5b Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 10:01:32 -0700 Subject: [PATCH 095/135] removed spaces --- libraries/shared/src/RingBufferHistory.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index fbb34ed9be..03cbce80df 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -104,8 +104,6 @@ private: int _newestEntryAtIndex; int _numEntries; - - public: class Iterator : public std::iterator < std::forward_iterator_tag, T > { public: From 763cc26fa96dd50c7669260941f9afc651ba826c Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 10:21:03 -0700 Subject: [PATCH 096/135] added operator= to RingBufferHistory::Iterator --- libraries/shared/src/RingBufferHistory.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index 03cbce80df..7ec5bbd6ff 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -109,6 +109,12 @@ public: public: Iterator(T* buffer, int size, T* at) : _buffer(buffer), _bufferEnd(buffer+size), _at(at) {} + Iterator& operator=(const Iterator& other) { + _buffer = other._buffer; + _bufferEnd = other._bufferEnd; + _at = other._at; + } + bool operator==(const Iterator& rhs) { return _at == rhs._at; } bool operator!=(const Iterator& rhs) { return _at != rhs._at; } T& operator*() { return *_at; } From d555017fb4c62690b5447a3033e3ca413835d015 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Thu, 10 Jul 2014 19:25:00 +0200 Subject: [PATCH 097/135] Coding style fix. --- interface/src/GLCanvas.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 87c1016c5e..cde1890e6d 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -184,12 +184,13 @@ bool GLCanvas::eventFilter(QObject*, QEvent* event) { { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta) { - if (event->type() == QEvent::KeyPress) + if (event->type() == QEvent::KeyPress) { keyPressEvent(keyEvent); - else if (event->type() == QEvent::KeyRelease) + } else if (event->type() == QEvent::KeyRelease) { keyReleaseEvent(keyEvent); - else + } else { QGLWidget::event(event); + } return true; } } From baf825789e3b51194d607af30cf063bde455ab39 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 10 Jul 2014 10:28:56 -0700 Subject: [PATCH 098/135] Made Oculus UI radius not a magic number --- interface/src/ui/ApplicationOverlay.cpp | 14 ++++++++------ interface/src/ui/ApplicationOverlay.h | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 6324ee03b0..005858fd4d 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -40,7 +40,8 @@ ApplicationOverlay::ApplicationOverlay() : _framebufferObject(NULL), _textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE), _alpha(1.0f), - _crosshairTexture(0) { + _crosshairTexture(0), + _oculusuiRadius(1.0f) { memset(_reticleActive, 0, sizeof(_reticleActive)); memset(_magActive, 0, sizeof(_reticleActive)); @@ -288,9 +289,10 @@ QPoint ApplicationOverlay::getOculusPalmClickLocation(const PalmData *palm) cons //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, 1, &t)){ + if (raySphereIntersect(dir, adjustedPos, _oculusuiRadius, &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); @@ -321,7 +323,7 @@ bool ApplicationOverlay::calculateRayUICollisionPoint(const glm::vec3& position, glm::vec3 relativeDirection = orientation * direction; float t; - if (raySphereIntersect(relativeDirection, relativePosition, 1, &t)){ + if (raySphereIntersect(relativeDirection, relativePosition, _oculusuiRadius, &t)){ result = position + direction * t; return true; } @@ -1196,8 +1198,8 @@ void ApplicationOverlay::renderTexturedHemisphere() { glTranslatef(position.x, position.y, position.z); glMultMatrixf(&rotation[0][0]); - - + glScalef(_oculusuiRadius, _oculusuiRadius, _oculusuiRadius); + glDrawRangeElements(GL_TRIANGLES, 0, vertices - 1, indices, GL_UNSIGNED_SHORT, 0); glPopMatrix(); diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index e355665b95..33ba818f40 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -71,6 +71,7 @@ private: float _magSizeMult[NUMBER_OF_MAGNIFIERS]; float _alpha; + float _oculusuiRadius; GLuint _crosshairTexture; }; From 35e4b253c2be7623e015a74f87f83f57ac7d018c Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 10:35:29 -0700 Subject: [PATCH 099/135] added typename keyword in MovingMinMaxAvg.h --- libraries/shared/src/MovingMinMaxAvg.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 00aa35dd2c..79f4b0e3cb 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -101,8 +101,8 @@ public: _existingSamplesInCurrentInterval = 0; // update the window's stats by combining the intervals' stats - RingBufferHistory::Iterator i = _intervalStats.begin(); - RingBufferHistory::Iterator end = _intervalStats.end(); + typename RingBufferHistory::Iterator i = _intervalStats.begin(); + typename RingBufferHistory::Iterator end = _intervalStats.end(); _windowStats = Stats(); int intervalsIncludedInWindowStats = 0; while (i != end) { From 0b213f96168ccaf7f1550cd30f8ab344c51f391e Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 10:44:33 -0700 Subject: [PATCH 100/135] streamlined AudioStreamStats packing; added #include --- .../src/audio/AudioMixerClientData.cpp | 13 ++----------- libraries/shared/src/MovingMinMaxAvg.h | 2 ++ 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index c624c1dc01..ae4a0269cc 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -162,14 +162,13 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() { AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const { AudioStreamStats streamStats; - const SequenceNumberStats* streamSequenceNumberStats; streamStats._streamType = ringBuffer->getType(); if (streamStats._streamType == PositionalAudioRingBuffer::Injector) { streamStats._streamIdentifier = ((InjectedAudioRingBuffer*)ringBuffer)->getStreamIdentifier(); - streamSequenceNumberStats = &_incomingInjectedAudioSequenceNumberStatsMap[streamStats._streamIdentifier]; + streamStats._packetStreamStats = _incomingInjectedAudioSequenceNumberStatsMap[streamStats._streamIdentifier].getStats(); } else { - streamSequenceNumberStats = &_incomingAvatarAudioSequenceNumberStats; + streamStats._packetStreamStats = _incomingAvatarAudioSequenceNumberStats.getStats(); } const MovingMinMaxAvg& timeGapStats = ringBuffer->getInterframeTimeGapStatsForStatsPacket(); @@ -187,14 +186,6 @@ AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const Positio streamStats._ringBufferConsecutiveNotMixedCount = ringBuffer->getConsecutiveNotMixedCount(); streamStats._ringBufferOverflowCount = ringBuffer->getOverflowCount(); streamStats._ringBufferSilentFramesDropped = ringBuffer->getSilentFramesDropped(); - - streamStats._packetStreamStats._numReceived = streamSequenceNumberStats->getNumReceived(); - streamStats._packetStreamStats._numUnreasonable = streamSequenceNumberStats->getNumUnreasonable(); - streamStats._packetStreamStats._numEarly = streamSequenceNumberStats->getNumEarly(); - streamStats._packetStreamStats._numLate = streamSequenceNumberStats->getNumLate(); - streamStats._packetStreamStats._numLost = streamSequenceNumberStats->getNumLost(); - streamStats._packetStreamStats._numRecovered = streamSequenceNumberStats->getNumRecovered(); - streamStats._packetStreamStats._numDuplicate = streamSequenceNumberStats->getNumDuplicate(); return streamStats; } diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 79f4b0e3cb..7c645dbd93 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -12,6 +12,8 @@ #ifndef hifi_MovingMinMaxAvg_h #define hifi_MovingMinMaxAvg_h +#include + #include "RingBufferHistory.h" template From 4d591de76276614bb3dd40b50f123081f4281640 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 10 Jul 2014 10:52:15 -0700 Subject: [PATCH 101/135] Applied low velocity filter to palm tip position --- interface/src/devices/SixenseManager.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index b9a736887e..f145b8d5be 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -173,8 +173,10 @@ void SixenseManager::update(float deltaTime) { // 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)); - palm->setRawRotation(safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter)); + 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); From fdd9acb47768df14819175f9bb802e39995535f0 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 10 Jul 2014 10:55:41 -0700 Subject: [PATCH 102/135] bug fix: bad logic for early exit --- interface/src/avatar/AvatarManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 86ec7c2680..debe6489ea 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -41,7 +41,7 @@ void AvatarManager::init() { } void AvatarManager::updateOtherAvatars(float deltaTime) { - if (_avatarHash.size() > 1) { + if (_avatarHash.size() < 2) { return; } bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); From ad6a219a60b0b4abbaecca44663ea60c8da47c62 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 10 Jul 2014 11:08:15 -0700 Subject: [PATCH 103/135] fix bug: remove redundant _skeleton.simulate() --- interface/src/avatar/Avatar.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 4efb03b3f5..db4f44c82c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -145,20 +145,20 @@ void Avatar::simulate(float deltaTime) { _skeletonModel.setLODDistance(getLODDistance()); if (!_shouldRenderBillboard && inViewFrustum) { - if (_hasNewJointRotations) { - PerformanceTimer perfTimer("skeleton"); - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData.at(i); - _skeletonModel.setJointState(i, data.valid, data.rotation); - } - _skeletonModel.simulate(deltaTime); - } { - PerformanceTimer perfTimer("head"); + 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, _hasNewJointRotations); simulateAttachments(deltaTime); _hasNewJointRotations = false; - + } + { + PerformanceTimer perfTimer("head"); glm::vec3 headPosition = _position; _skeletonModel.getHeadPosition(headPosition); Head* head = getHead(); From 0be70063c5236bec901e6e6c63b80ed7a922cf0a Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 10 Jul 2014 11:39:49 -0700 Subject: [PATCH 104/135] only set hasNewJointRotations true for valid rotation --- libraries/avatars/src/AvatarData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; } From a955a17472999b32f5e29e84770554ee479aac6b Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 11:55:19 -0700 Subject: [PATCH 105/135] changed RingBufferHistory to use qvector instead of raw array --- libraries/shared/src/RingBufferHistory.h | 51 +++++------------------- 1 file changed, 11 insertions(+), 40 deletions(-) diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index 7ec5bbd6ff..a9d24e44b6 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -15,6 +15,8 @@ #include #include +#include + template class RingBufferHistory { @@ -24,34 +26,9 @@ public: : _size(capacity + 1), _capacity(capacity), _newestEntryAtIndex(0), - _numEntries(0) + _numEntries(0), + _buffer(capacity + 1) { - _buffer = new T[_size]; - } - - RingBufferHistory(const RingBufferHistory& other) - : _size(other._size), - _capacity(other._capacity), - _newestEntryAtIndex(other._newestEntryAtIndex), - _numEntries(other._numEntries) - { - _buffer = new T[_size]; - memcpy(_buffer, other._buffer, _size*sizeof(T)); - } - - RingBufferHistory& operator= (const RingBufferHistory& rhs) { - _size = rhs._size; - _capacity = rhs._capacity; - _newestEntryAtIndex = rhs._newestEntryAtIndex; - _numEntries = rhs._numEntries; - delete[] _buffer; - _buffer = new T[_size]; - memcpy(_buffer, rhs._buffer, _size*sizeof(T)); - return *this; - } - - ~RingBufferHistory() { - delete[] _buffer; } void clear() { @@ -98,22 +75,16 @@ public: int getNumEntries() const { return _numEntries; } private: - T* _buffer; int _size; int _capacity; int _newestEntryAtIndex; int _numEntries; + QVector _buffer; public: class Iterator : public std::iterator < std::forward_iterator_tag, T > { public: - Iterator(T* buffer, int size, T* at) : _buffer(buffer), _bufferEnd(buffer+size), _at(at) {} - - Iterator& operator=(const Iterator& other) { - _buffer = other._buffer; - _bufferEnd = other._bufferEnd; - _at = other._at; - } + 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; } @@ -121,7 +92,7 @@ public: T* operator->() { return _at; } Iterator& operator++() { - _at = (_at == _buffer) ? _bufferEnd - 1 : _at - 1; + _at = (_at == _bufferFirst) ? _bufferLast : _at - 1; return *this; } @@ -132,19 +103,19 @@ public: } private: - T* const _buffer; - T* const _bufferEnd; + T* const _bufferFirst; + T* const _bufferLast; T* _at; }; - Iterator begin() { return Iterator(_buffer, _size, &_buffer[_newestEntryAtIndex]); } + Iterator begin() { return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[_newestEntryAtIndex]); } Iterator end() { int endAtIndex = _newestEntryAtIndex - _numEntries; if (endAtIndex < 0) { endAtIndex += _size; } - return Iterator(_buffer, _size, &_buffer[endAtIndex]); + return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[endAtIndex]); } }; From 45421ffb0e435d0ad61b6d913dd024f0913eb497 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 10 Jul 2014 12:06:36 -0700 Subject: [PATCH 106/135] Made palm ball and disk properly use avatar scale --- interface/src/avatar/Hand.cpp | 16 ++++++++++++---- interface/src/devices/OculusManager.cpp | 1 - 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 94f734ba06..7faf1aaadc 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 @@ -153,10 +156,10 @@ 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) { @@ -165,6 +168,11 @@ void Hand::renderHandTargets(bool isMine) { glColor4f(handColor.r, handColor.g, handColor.b, alpha); glm::vec3 tip = palm.getFingerTipPosition(); glm::vec3 root = palm.getPosition(); + + //Scale the positions based on avatar scale + tip = myAvatar->getPosition() + avatarScale * (tip - myAvatar->getPosition()); + root = myAvatar->getPosition() + avatarScale * (root - myAvatar->getPosition()); + 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/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 574aa3c6f6..0ad606a320 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -473,7 +473,6 @@ void OculusManager::renderLaserPointers() { //If the Oculus is enabled, we will draw a blue cursor ray - // Draw the palm ball and disk for (size_t i = 0; i < myAvatar->getHand()->getNumPalms(); ++i) { PalmData& palm = myAvatar->getHand()->getPalms()[i]; if (palm.isActive()) { From b4c9e51011420ac7bfeb11e2facc0a83e2e2974a Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 14:14:29 -0700 Subject: [PATCH 107/135] Audio now periodically sends downstream audio stats to audiomixer --- assignment-client/src/audio/AudioMixer.cpp | 3 +- .../src/audio/AudioMixerClientData.cpp | 11 +++- .../src/audio/AudioMixerClientData.h | 2 + interface/src/Application.cpp | 17 ++++-- interface/src/Application.h | 1 + interface/src/Audio.cpp | 54 +++++++++++++++++++ interface/src/Audio.h | 3 ++ interface/src/ui/Stats.cpp | 18 ++++--- 8 files changed, 96 insertions(+), 13 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 6cdcaef133..2ba3809729 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) { diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index ae4a0269cc..915199b443 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -83,7 +83,7 @@ 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 @@ -107,6 +107,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; diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 65fd4b3da3..526071832e 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -46,6 +46,8 @@ private: quint16 _outgoingMixedAudioSequenceNumber; SequenceNumberStats _incomingAvatarAudioSequenceNumberStats; QHash _incomingInjectedAudioSequenceNumberStatsMap; + + AudioStreamStats _downstreamAudioStreamStats; }; #endif // hifi_AudioMixerClientData_h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d7464f57a1..8edc788833 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -172,7 +172,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); @@ -2125,10 +2126,11 @@ void Application::updateMyAvatar(float deltaTime) { loadViewFrustum(_myCamera, _viewFrustum); } + quint64 now = usecTimestampNow(); + // Update my voxel servers with my current voxel query... { PerformanceTimer perfTimer("queryOctree"); - quint64 now = usecTimestampNow(); quint64 sinceLastQuery = now - _lastQueriedTime; const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; @@ -2146,7 +2148,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) { @@ -2154,6 +2155,16 @@ void Application::updateMyAvatar(float deltaTime) { sendNackPackets(); } } + + { + quint64 sinceLastNack = now - _lastSendDownstreamAudioStats; + const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND; + if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) { + _lastSendDownstreamAudioStats = now; + + QMetaObject::invokeMethod(&_audio, "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); + } + } } int Application::sendNackPackets() { diff --git a/interface/src/Application.h b/interface/src/Application.h index 321a43d548..b55a830e3e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -586,6 +586,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 17e9054568..7a445bf816 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -782,6 +782,60 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { _incomingStreamPacketStatsHistory.insert(_incomingMixedAudioSequenceNumberStats.getStats()); } +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(); + + return stats; +} + +void Audio::sendDownstreamAudioStatsPacket() { + + 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) { diff --git a/interface/src/Audio.h b/interface/src/Audio.h index e6e06838d3..e8e92db1a0 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -99,6 +99,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) ? diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 0cd9e3fa05..add0754fe1 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -349,24 +349,26 @@ void Stats::display( char downstreamAudioStatsString[30]; + AudioStreamStats downstreamAudioStreamStats = audio->getDownstreamAudioStreamStats(); + audio->calculatePacketLossRate(audio->getIncomingStreamPacketStatsHistory(), packetLossRate, packetLossRate30s); - sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %d/?/%d", packetLossRate*100.0f, packetLossRate30s*100.0f, - audio->getDownstreamRingBuffer().framesAvailable(), audio->getDesiredJitterBufferFrames()); + sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %u/?/%u", packetLossRate*100.0f, packetLossRate30s*100.0f, + downstreamAudioStreamStats._ringBufferFramesAvailable, downstreamAudioStreamStats._ringBufferDesiredJitterBufferFrames); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); - const MovingMinMaxAvg& timeGapStats = audio->getInterframeTimeGapStats(); - - sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %d/%d", timeGapStats.getMin(), timeGapStats.getMax(), - timeGapStats.getAverage(), audio->getStarveCount(), audio->getDownstreamRingBuffer().getOverflowCount()); + 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, %d/?", timeGapStats.getWindowMin(), timeGapStats.getWindowMax(), - timeGapStats.getWindowAverage(), audio->getConsecutiveNotMixedCount()); + 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); From fc21f7bbacbcce8009df663aaed2a48287f72c8b Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 10 Jul 2014 14:21:17 -0700 Subject: [PATCH 108/135] Removed outdated and incorrect getFingerTipPosition() --- interface/src/BuckyBalls.cpp | 2 +- interface/src/avatar/Hand.cpp | 4 ++-- libraries/avatars/src/HandData.cpp | 6 ------ libraries/avatars/src/HandData.h | 1 - 4 files changed, 3 insertions(+), 10 deletions(-) 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/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 7faf1aaadc..aae9cb0a29 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -145,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); @@ -166,7 +166,7 @@ void Hand::renderHandTargets(bool isMine) { 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 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; From 8bc92144453e486b614662daea5df829aa8cf416 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 10 Jul 2014 14:37:04 -0700 Subject: [PATCH 109/135] Added helper scaling function to Avatar. --- interface/src/avatar/Avatar.cpp | 5 +++++ interface/src/avatar/Avatar.h | 4 ++++ interface/src/avatar/Hand.cpp | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 3221e75e56..2ebbd55c85 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -970,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"); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 8c14fc9ed0..6fb7991c3c 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -152,6 +152,10 @@ 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(); diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index aae9cb0a29..08e1cf83df 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -170,8 +170,8 @@ void Hand::renderHandTargets(bool isMine) { glm::vec3 root = palm.getPosition(); //Scale the positions based on avatar scale - tip = myAvatar->getPosition() + avatarScale * (tip - myAvatar->getPosition()); - root = myAvatar->getPosition() + avatarScale * (root - myAvatar->getPosition()); + myAvatar->scaleVectorRelativeToPosition(tip); + myAvatar->scaleVectorRelativeToPosition(root); Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS); // Render sphere at palm/finger root From ce0101aa2a95825ea0cbfced16d7bb72c254fdeb Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 14:53:29 -0700 Subject: [PATCH 110/135] fixed octreeEditPacketSender seq numbers errors; untested --- .../octree/src/OctreeEditPacketSender.cpp | 24 ++++++++++--------- libraries/octree/src/OctreeEditPacketSender.h | 6 ++--- 2 files changed, 16 insertions(+), 14 deletions(-) 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 From 817b50c7b8fd9a068a9ef841cb752ff3840f29c7 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 10 Jul 2014 15:34:45 -0700 Subject: [PATCH 111/135] Show upload/download stats for all reliable messages, not just reliable delta downloads. --- interface/src/ui/Stats.cpp | 22 +++++++------ .../metavoxels/src/DatagramSequencer.cpp | 32 ++++++++++++++++++- libraries/metavoxels/src/DatagramSequencer.h | 9 ++++++ libraries/metavoxels/src/Endpoint.h | 2 ++ .../metavoxels/src/MetavoxelClientManager.cpp | 4 --- .../metavoxels/src/MetavoxelClientManager.h | 2 -- 6 files changed, 54 insertions(+), 17 deletions(-) diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index b0cbea14a9..0d2762f923 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -422,18 +422,15 @@ void Stats::display( int internal = 0, leaves = 0; - int received = 0, total = 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()); - int clientReceived = 0, clientTotal = 0; - if (client->getReliableDeltaProgress(clientReceived, clientTotal)) { - received += clientReceived; - total += clientTotal; - } + client->getSequencer().addReliableChannelStats(sendProgress, sendTotal, receiveProgress, receiveTotal); } } } @@ -447,11 +444,16 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, nodeTypes.str().c_str(), color); - if (total > 0) { - stringstream reliableDelta; - reliableDelta << "Reliable Delta: " << (received * 100 / total) << "%"; + 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, reliableDelta.str().c_str(), color); + drawText(horizontalOffset, verticalOffset, scale, rotation, font, reliableStats.str().c_str(), color); } } diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index 8c58233eb9..c757e131bb 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -79,6 +79,24 @@ ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) { return channel; } +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; @@ -684,6 +702,8 @@ void ReliableChannel::endMessage() { quint32 length = _buffer.pos() - _messageLengthPlaceholder; _buffer.writeBytes(_messageLengthPlaceholder, sizeof(quint32), (const char*)&length); + _messageReceivedOffset = getBytesWritten(); + _messageSize = length; } void ReliableChannel::sendMessage(const QVariant& message) { @@ -692,6 +712,15 @@ void ReliableChannel::sendMessage(const QVariant& message) { endMessage(); } +bool ReliableChannel::getMessageSendProgress(int& sent, int& total) const { + if (!_messagesEnabled || _offset >= _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; @@ -728,7 +757,8 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o _offset(0), _writePosition(0), _writePositionResetPacketNumber(0), - _messagesEnabled(true) { + _messagesEnabled(true), + _messageReceivedOffset(0) { _buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly); _dataStream.setByteOrder(QDataStream::LittleEndian); diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index 998e196a05..4a01679c68 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -108,6 +108,9 @@ public: /// Returns the intput channel at the specified index, creating it if necessary. ReliableChannel* getReliableInputChannel(int index = 0); + /// 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 @@ -376,6 +379,10 @@ public: /// 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; @@ -420,6 +427,8 @@ private: SpanList _acknowledged; bool _messagesEnabled; int _messageLengthPlaceholder; + int _messageReceivedOffset; + int _messageSize; }; #endif // hifi_DatagramSequencer_h diff --git a/libraries/metavoxels/src/Endpoint.h b/libraries/metavoxels/src/Endpoint.h index 3c681a7b98..d6999196d8 100644 --- a/libraries/metavoxels/src/Endpoint.h +++ b/libraries/metavoxels/src/Endpoint.h @@ -32,6 +32,8 @@ public: PacketRecord* baselineReceiveRecord = NULL); virtual ~Endpoint(); + const DatagramSequencer& getSequencer() const { return _sequencer; } + virtual void update(); virtual int parseData(const QByteArray& packet); diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp index 94d9116794..f3ea1ae8c5 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.cpp +++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp @@ -94,10 +94,6 @@ MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelClientM SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&))); } -bool MetavoxelClient::getReliableDeltaProgress(int& received, int& total) const { - return _reliableDeltaChannel && _reliableDeltaChannel->getMessageReceiveProgress(received, total); -} - void MetavoxelClient::guide(MetavoxelVisitor& visitor) { visitor.setLOD(_manager->getLOD()); _data.guide(visitor); diff --git a/libraries/metavoxels/src/MetavoxelClientManager.h b/libraries/metavoxels/src/MetavoxelClientManager.h index 809718aa01..ad6c86c8fc 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.h +++ b/libraries/metavoxels/src/MetavoxelClientManager.h @@ -53,8 +53,6 @@ public: MetavoxelData& getData() { return _data; } - bool getReliableDeltaProgress(int& received, int& total) const; - void guide(MetavoxelVisitor& visitor); void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false); From d858e20af25b2a72d595c2b23c2eaddb7a826fbf Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 10 Jul 2014 16:02:53 -0700 Subject: [PATCH 112/135] For now, tie the metavoxel LOD threshold to the avatar LOD parameter. --- interface/src/MetavoxelSystem.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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) { From 4bfd393d028674face241aca774bd379bb8f565a Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 10 Jul 2014 16:42:00 -0700 Subject: [PATCH 113/135] Proper scaling for oculus UI using avatar scale --- interface/src/devices/OculusManager.cpp | 12 +++++++++- interface/src/ui/ApplicationOverlay.cpp | 31 +++++++++++++++---------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 0ad606a320..beb18bfc72 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -479,6 +479,10 @@ void OculusManager::renderLaserPointers() { 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); } } @@ -492,8 +496,14 @@ glm::vec3 OculusManager::getLaserPointerTipPosition(const PalmData* palm) { 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(palm->getPosition(), direction, result)) { + if (applicationOverlay.calculateRayUICollisionPoint(position, direction, result)) { return result; } diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 005858fd4d..6d1e45df20 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -266,7 +266,7 @@ bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, } } - +//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(); @@ -289,7 +289,7 @@ QPoint ApplicationOverlay::getOculusPalmClickLocation(const PalmData *palm) cons //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, &t)){ + 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); @@ -313,6 +313,7 @@ QPoint ApplicationOverlay::getOculusPalmClickLocation(const PalmData *palm) cons 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(); @@ -323,7 +324,7 @@ bool ApplicationOverlay::calculateRayUICollisionPoint(const glm::vec3& position, glm::vec3 relativeDirection = orientation * direction; float t; - if (raySphereIntersect(relativeDirection, relativePosition, _oculusuiRadius, &t)){ + if (raySphereIntersect(relativeDirection, relativePosition, _oculusuiRadius * myAvatar->getScale(), &t)){ result = position + direction * t; return true; } @@ -862,18 +863,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 { @@ -881,7 +886,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 { @@ -889,7 +894,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 { @@ -897,7 +902,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 { @@ -1198,7 +1203,9 @@ void ApplicationOverlay::renderTexturedHemisphere() { glTranslatef(position.x, position.y, position.z); glMultMatrixf(&rotation[0][0]); - glScalef(_oculusuiRadius, _oculusuiRadius, _oculusuiRadius); + + const float scale = _oculusuiRadius * myAvatar->getScale(); + glScalef(scale, scale, scale); glDrawRangeElements(GL_TRIANGLES, 0, vertices - 1, indices, GL_UNSIGNED_SHORT, 0); From 25f4f63a1e24a14fa7292e18ea4c46edeff7f827 Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 16:42:23 -0700 Subject: [PATCH 114/135] added window PacketStreamStats to AudioStreamStats --- assignment-client/src/audio/AudioMixer.cpp | 3 - assignment-client/src/audio/AudioMixer.h | 2 + .../src/audio/AudioMixerClientData.cpp | 56 +++++++++++++++---- .../src/audio/AudioMixerClientData.h | 5 +- libraries/audio/src/AudioStreamStats.h | 4 +- .../networking/src/SequenceNumberStats.cpp | 29 +++++++++- .../networking/src/SequenceNumberStats.h | 15 ++++- libraries/shared/src/RingBufferHistory.h | 4 +- 8 files changed, 97 insertions(+), 21 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 2ba3809729..459f8a4b59 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -641,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 915199b443..d0b5104dbd 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -20,10 +20,13 @@ #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) { } @@ -89,6 +92,9 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { // 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; @@ -175,11 +181,14 @@ AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const Positio streamStats._streamType = ringBuffer->getType(); if (streamStats._streamType == PositionalAudioRingBuffer::Injector) { streamStats._streamIdentifier = ((InjectedAudioRingBuffer*)ringBuffer)->getStreamIdentifier(); - streamStats._packetStreamStats = _incomingInjectedAudioSequenceNumberStatsMap[streamStats._streamIdentifier].getStats(); + const SequenceNumberStats& sequenceNumberStats = _incomingInjectedAudioSequenceNumberStatsMap[streamStats._streamIdentifier]; + streamStats._packetStreamStats = sequenceNumberStats.getStats(); + streamStats._packetStreamWindowStats = sequenceNumberStats.getStatsForHistoryWindow(); } else { streamStats._packetStreamStats = _incomingAvatarAudioSequenceNumberStats.getStats(); + streamStats._packetStreamWindowStats = _incomingAvatarAudioSequenceNumberStats.getStatsForHistoryWindow(); } - + const MovingMinMaxAvg& timeGapStats = ringBuffer->getInterframeTimeGapStatsForStatsPacket(); streamStats._timeGapMin = timeGapStats.getMin(); streamStats._timeGapMax = timeGapStats.getMax(); @@ -199,8 +208,18 @@ AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const Positio 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(); @@ -251,6 +270,23 @@ 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(), 'g', 2) + + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'g', 2) + + " min gap:" + QString::number(streamStats._timeGapMin) + + " max gap:" + QString::number(streamStats._timeGapMax) + + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) + + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) + + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) + + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'g', 2); + AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer(); if (avatarRingBuffer) { AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer); @@ -261,9 +297,8 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " not mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + " silents dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " early:" + QString::number(streamStats._packetStreamStats._numEarly) - + " late:" + QString::number(streamStats._packetStreamStats._numLate) - + " lost:" + QString::number(streamStats._packetStreamStats._numLost) + + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'g', 2) + + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'g', 2) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) @@ -277,16 +312,15 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { for (int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) { AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]); - result += "mic.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + result += "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) - + " early:" + QString::number(streamStats._packetStreamStats._numEarly) - + " late:" + QString::number(streamStats._packetStreamStats._numLate) - + " lost:" + QString::number(streamStats._packetStreamStats._numLost) + + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'g', 2) + + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'g', 2) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 526071832e..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; } diff --git a/libraries/audio/src/AudioStreamStats.h b/libraries/audio/src/AudioStreamStats.h index 2cd0bca880..f26a3847c7 100644 --- a/libraries/audio/src/AudioStreamStats.h +++ b/libraries/audio/src/AudioStreamStats.h @@ -33,7 +33,8 @@ public: _ringBufferConsecutiveNotMixedCount(0), _ringBufferOverflowCount(0), _ringBufferSilentFramesDropped(0), - _packetStreamStats() + _packetStreamStats(), + _packetStreamWindowStats() {} PositionalAudioRingBuffer::Type _streamType; @@ -55,6 +56,7 @@ public: quint32 _ringBufferSilentFramesDropped; PacketStreamStats _packetStreamStats; + PacketStreamStats _packetStreamWindowStats; }; #endif // hifi_AudioStreamStats_h diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index 6a26e613b6..28dea7de63 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -13,17 +13,19 @@ #include -SequenceNumberStats::SequenceNumberStats() +SequenceNumberStats::SequenceNumberStats(int statsHistoryLength) : _lastReceived(std::numeric_limits::max()), _missingSet(), _stats(), - _lastSenderUUID() + _lastSenderUUID(), + _statsHistory(statsHistoryLength) { } void SequenceNumberStats::reset() { _missingSet.clear(); _stats = PacketStreamStats(); + _statsHistory.clear(); } static const int UINT16_RANGE = std::numeric_limits::max() + 1; @@ -168,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 b399c23d2b..8c16345aaf 100644 --- a/libraries/networking/src/SequenceNumberStats.h +++ b/libraries/networking/src/SequenceNumberStats.h @@ -13,6 +13,7 @@ #define hifi_SequenceNumberStats_h #include "SharedUtil.h" +#include "RingBufferHistory.h" #include const int MAX_REASONABLE_SEQUENCE_GAP = 1000; @@ -28,6 +29,14 @@ public: _numRecovered(0), _numDuplicate(0) {} + + 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; quint32 _numEarly; @@ -39,11 +48,12 @@ public: class SequenceNumberStats { public: - SequenceNumberStats(); + 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; } @@ -54,6 +64,7 @@ public: 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: @@ -63,6 +74,8 @@ private: PacketStreamStats _stats; QUuid _lastSenderUUID; + + RingBufferHistory _statsHistory; }; #endif // hifi_SequenceNumberStats_h diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index a9d24e44b6..e9875ec38a 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -64,11 +64,11 @@ public: } const T* getNewestEntry() const { - return &_buffer[_newestEntryAtIndex]; + return _numEntries == 0 ? NULL : &_buffer[_newestEntryAtIndex]; } T* getNewestEntry() { - return &_buffer[_newestEntryAtIndex]; + return _numEntries == 0 ? NULL : &_buffer[_newestEntryAtIndex]; } int getCapacity() const { return _capacity; } From a7ef7647edebb4256816dcf824bd44c86a3fb724 Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 16:54:26 -0700 Subject: [PATCH 115/135] application stats tracking and packing updated; --- interface/src/Application.cpp | 1 - interface/src/Application.h | 2 ++ interface/src/Audio.cpp | 57 ++++++++--------------------------- interface/src/Audio.h | 13 ++------ 4 files changed, 17 insertions(+), 56 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8edc788833..689250b318 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2158,7 +2158,6 @@ void Application::updateMyAvatar(float deltaTime) { { quint64 sinceLastNack = now - _lastSendDownstreamAudioStats; - const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND; if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) { _lastSendDownstreamAudioStats = now; diff --git a/interface/src/Application.h b/interface/src/Application.h index b55a830e3e..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 diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 7a445bf816..3689ff0143 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -48,15 +48,18 @@ static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_ static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300; -static const int AUDIO_STREAM_STATS_HISTORY_SIZE = 30; - // audio frames time gap stats (min/max/avg) for last ~30 seconds are recalculated every ~1 second -const int TIME_GAPS_STATS_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS; -const int TIME_GAP_STATS_WINDOW_INTERVALS = 30; +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), @@ -110,9 +113,8 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) : _scopeOutputLeft(0), _scopeOutputRight(0), _audioMixerAvatarStreamAudioStats(), - _audioMixerAvatarStreamPacketStatsHistory(AUDIO_STREAM_STATS_HISTORY_SIZE), _outgoingAvatarAudioSequenceNumber(0), - _incomingStreamPacketStatsHistory(AUDIO_STREAM_STATS_HISTORY_SIZE), + _incomingMixedAudioSequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH), _interframeTimeGapStats(TIME_GAPS_STATS_INTERVAL_SAMPLES, TIME_GAP_STATS_WINDOW_INTERVALS), _starveCount(0), _consecutiveNotMixedCount(0) @@ -138,13 +140,8 @@ void Audio::reset() { _audioMixerAvatarStreamAudioStats = AudioStreamStats(); _audioMixerInjectedStreamAudioStatsMap.clear(); - _audioMixerAvatarStreamPacketStatsHistory.clear(); - _audioMixerInjectedStreamPacketStatsHistoryMap.clear(); - _outgoingAvatarAudioSequenceNumber = 0; _incomingMixedAudioSequenceNumberStats.reset(); - - _incomingStreamPacketStatsHistory.clear(); } QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) { @@ -751,7 +748,6 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { dataAt += sizeof(quint8); if (!appendFlag) { _audioMixerInjectedStreamAudioStatsMap.clear(); - _audioMixerInjectedStreamPacketStatsHistoryMap.clear(); } // parse the number of stream stats structs to follow @@ -766,20 +762,10 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { if (streamStats._streamType == PositionalAudioRingBuffer::Microphone) { _audioMixerAvatarStreamAudioStats = streamStats; - _audioMixerAvatarStreamPacketStatsHistory.insert(streamStats._packetStreamStats); } else { - if (!_audioMixerInjectedStreamAudioStatsMap.contains(streamStats._streamIdentifier)) { - _audioMixerInjectedStreamPacketStatsHistoryMap.insert(streamStats._streamIdentifier, - RingBufferHistory(AUDIO_STREAM_STATS_HISTORY_SIZE)); - } _audioMixerInjectedStreamAudioStatsMap[streamStats._streamIdentifier] = streamStats; - _audioMixerInjectedStreamPacketStatsHistoryMap[streamStats._streamIdentifier].insert(streamStats._packetStreamStats); } } - - // when an audio stream stats packet is received, also record the downstream packets stats in the history - // for calculating packet loss rates - _incomingStreamPacketStatsHistory.insert(_incomingMixedAudioSequenceNumberStats.getStats()); } AudioStreamStats Audio::getDownstreamAudioStreamStats() const { @@ -803,12 +789,17 @@ AudioStreamStats Audio::getDownstreamAudioStreamStats() const { 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 @@ -1608,25 +1599,3 @@ int Audio::calculateNumberOfFrameSamples(int numBytes) { int frameSamples = (int)(numBytes * CALLBACK_ACCELERATOR_RATIO + 0.5f) / sizeof(int16_t); return frameSamples; } - -void Audio::calculatePacketLossRate(const RingBufferHistory& statsHistory, - float& overallLossRate, float& windowLossRate) const { - - int numHistoryEntries = statsHistory.getNumEntries(); - if (numHistoryEntries == 0) { - overallLossRate = 0.0f; - windowLossRate = 0.0f; - } else { - const PacketStreamStats& newestStats = *statsHistory.getNewestEntry(); - overallLossRate = (float)newestStats._numLost / newestStats._numReceived; - - if (numHistoryEntries == 1) { - windowLossRate = overallLossRate; - } else { - int age = std::min(numHistoryEntries-1, AUDIO_STREAM_STATS_HISTORY_SIZE-1); - const PacketStreamStats& oldestStats = *statsHistory.get(age); - windowLossRate = (float)(newestStats._numLost - oldestStats._numLost) - / (newestStats._numReceived - oldestStats._numReceived); - } - } -} diff --git a/interface/src/Audio.h b/interface/src/Audio.h index e8e92db1a0..b40355e714 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -36,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; @@ -121,14 +123,8 @@ public slots: const AudioStreamStats& getAudioMixerAvatarStreamAudioStats() const { return _audioMixerAvatarStreamAudioStats; } const QHash& getAudioMixerInjectedStreamAudioStatsMap() const { return _audioMixerInjectedStreamAudioStatsMap; } - const RingBufferHistory& getAudioMixerAvatarStreamPacketStatsHistory() const { return _audioMixerAvatarStreamPacketStatsHistory; } - const QHash >& getAudioMixerInjectedStreamPacketStatsHistoryMap() const {return _audioMixerInjectedStreamPacketStatsHistoryMap; } - const RingBufferHistory& getIncomingStreamPacketStatsHistory() const { return _incomingStreamPacketStatsHistory; } const MovingMinMaxAvg& getInterframeTimeGapStats() const { return _interframeTimeGapStats; } - void calculatePacketLossRate(const RingBufferHistory& statsHistory, - float& overallLossRate, float& windowLossRate) const; - signals: bool muteToggled(); void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format); @@ -266,14 +262,9 @@ private: AudioStreamStats _audioMixerAvatarStreamAudioStats; QHash _audioMixerInjectedStreamAudioStatsMap; - RingBufferHistory _audioMixerAvatarStreamPacketStatsHistory; - QHash > _audioMixerInjectedStreamPacketStatsHistoryMap; - quint16 _outgoingAvatarAudioSequenceNumber; SequenceNumberStats _incomingMixedAudioSequenceNumberStats; - RingBufferHistory _incomingStreamPacketStatsHistory; - MovingMinMaxAvg _interframeTimeGapStats; }; From 71634b4c8dd7fc9d7bb4498ca371805ccab5ddde Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 10 Jul 2014 16:56:04 -0700 Subject: [PATCH 116/135] Fixed windows compiler warning --- interface/src/ui/ApplicationOverlay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 6d1e45df20..15cb10ec84 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -700,7 +700,7 @@ void ApplicationOverlay::renderPointersOculus(const glm::vec3& eyePos) { MyAvatar* myAvatar = application->getAvatar(); //Controller Pointers - for (int i = 0; i < myAvatar->getHand()->getNumPalms(); i++) { + for (size_t i = 0; i < myAvatar->getHand()->getNumPalms(); i++) { PalmData& palm = myAvatar->getHand()->getPalms()[i]; if (palm.isActive()) { From e658ce8fb939995d99579d3f016baa4982c313db Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 10 Jul 2014 16:57:46 -0700 Subject: [PATCH 117/135] Fixed ubuntu compiler warning --- interface/src/ui/ApplicationOverlay.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 15cb10ec84..3cc9cf4250 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -40,8 +40,8 @@ ApplicationOverlay::ApplicationOverlay() : _framebufferObject(NULL), _textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE), _alpha(1.0f), - _crosshairTexture(0), - _oculusuiRadius(1.0f) { + _oculusuiRadius(1.0f), + _crosshairTexture(0) { memset(_reticleActive, 0, sizeof(_reticleActive)); memset(_magActive, 0, sizeof(_reticleActive)); From 4bf74c1b65c5c7bc2bad97c8db7031ae13d5ee43 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 10 Jul 2014 16:59:57 -0700 Subject: [PATCH 118/135] Fixed more ubuntu compiler warnings --- interface/src/ui/ApplicationOverlay.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 3cc9cf4250..fad4a64835 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -272,9 +272,6 @@ QPoint ApplicationOverlay::getOculusPalmClickLocation(const PalmData *palm) cons Application* application = Application::getInstance(); QGLWidget* glWidget = application->getGLWidget(); MyAvatar* myAvatar = application->getAvatar(); - - const int widgetWidth = glWidget->width(); - const int widgetHeight = glWidget->height(); glm::vec3 tip = OculusManager::getLaserPointerTipPosition(palm); glm::vec3 eyePos = myAvatar->getHead()->calculateAverageEyePosition(); @@ -700,13 +697,12 @@ void ApplicationOverlay::renderPointersOculus(const glm::vec3& eyePos) { MyAvatar* myAvatar = application->getAvatar(); //Controller Pointers - for (size_t i = 0; i < myAvatar->getHand()->getNumPalms(); i++) { + for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { PalmData& palm = myAvatar->getHand()->getPalms()[i]; if (palm.isActive()) { glm::vec3 tip = OculusManager::getLaserPointerTipPosition(&palm); glm::quat orientation = glm::inverse(myAvatar->getOrientation()); - glm::vec3 dir = orientation * glm::normalize(eyePos - tip); //direction of ray goes towards camera glm::vec3 tipPos = (tip - eyePos); float length = glm::length(eyePos - tip); From 6c85caaa54415bbb272f9398deb50ca1f9f66b9c Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 17:03:53 -0700 Subject: [PATCH 119/135] updated Stats for new AudioStreamStats format --- .../src/audio/AudioMixerClientData.cpp | 28 +++++++++---------- interface/src/ui/Stats.cpp | 22 +++++---------- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index d0b5104dbd..789e73eb86 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -278,33 +278,33 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " not mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + " silents dropped: ?" - + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'g', 2) - + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'g', 2) + + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) + + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) - + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) + + " avg gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'g', 2); + + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer(); if (avatarRingBuffer) { AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer); - result += "mic.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + result += " 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(), 'g', 2) - + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'g', 2) + + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) + + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) - + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) + + " avg gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'g', 2); + + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); } else { result = "mic unknown"; } @@ -312,21 +312,21 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { for (int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) { AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]); - result += "inj.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + result += " 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(), 'g', 2) - + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'g', 2) + + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) + + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) - + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) + + " avg gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'g', 2); + + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); } } return result; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index add0754fe1..15a54e42a6 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; @@ -341,8 +342,6 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString4, color); - float packetLossRate, packetLossRate30s; - char downstreamLabelString[] = " Downstream:"; verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color); @@ -351,9 +350,8 @@ void Stats::display( AudioStreamStats downstreamAudioStreamStats = audio->getDownstreamAudioStreamStats(); - audio->calculatePacketLossRate(audio->getIncomingStreamPacketStatsHistory(), packetLossRate, packetLossRate30s); - - sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %u/?/%u", packetLossRate*100.0f, packetLossRate30s*100.0f, + 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; @@ -381,10 +379,9 @@ void Stats::display( char upstreamAudioStatsString[30]; const AudioStreamStats& audioMixerAvatarAudioStreamStats = audio->getAudioMixerAvatarStreamAudioStats(); - - audio->calculatePacketLossRate(audio->getAudioMixerAvatarStreamPacketStatsHistory(), packetLossRate, packetLossRate30s); - sprintf(upstreamAudioStatsString, " mic: %.1f%%/%.1f%%, %u/%u/%u", packetLossRate*100.0f, packetLossRate30s*100.0f, + sprintf(upstreamAudioStatsString, " mic: %.1f%%/%.1f%%, %u/%u/%u", audioMixerAvatarAudioStreamStats._packetStreamStats.getLostRate()*100.0f, + audioMixerAvatarAudioStreamStats._packetStreamWindowStats.getLostRate() * 100.0f, audioMixerAvatarAudioStreamStats._ringBufferFramesAvailable, audioMixerAvatarAudioStreamStats._ringBufferCurrentJitterBufferFrames, audioMixerAvatarAudioStreamStats._ringBufferDesiredJitterBufferFrames); @@ -405,15 +402,10 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - QHash > audioMixerInjectedStreamPacketStatsHistoryMap - = audio->getAudioMixerInjectedStreamPacketStatsHistoryMap(); - foreach(const AudioStreamStats& injectedStreamAudioStats, audioMixerInjectedStreamAudioStatsMap) { - audio->calculatePacketLossRate(audioMixerInjectedStreamPacketStatsHistoryMap[injectedStreamAudioStats._streamIdentifier], - packetLossRate, packetLossRate30s); - - sprintf(upstreamAudioStatsString, " inj: %.1f%%/%.1f%%, %u/%u/%u", packetLossRate*100.0f, packetLossRate30s*100.0f, + sprintf(upstreamAudioStatsString, " inj: %.1f%%/%.1f%%, %u/%u/%u", injectedStreamAudioStats._packetStreamStats.getLostRate()*100.0f, + injectedStreamAudioStats._packetStreamWindowStats.getLostRate() * 100.0f, injectedStreamAudioStats._ringBufferFramesAvailable, injectedStreamAudioStats._ringBufferCurrentJitterBufferFrames, injectedStreamAudioStats._ringBufferDesiredJitterBufferFrames); From ae1d91b21d81be29195b6bdc5ddd140d607ce50d Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 17:11:15 -0700 Subject: [PATCH 120/135] improved domain page stats string a bit --- .../src/audio/AudioMixerClientData.cpp | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 789e73eb86..bde4521d33 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -271,40 +271,40 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& QString AudioMixerClientData::getAudioStreamStatsString() const { QString result; AudioStreamStats streamStats = _downstreamAudioStreamStats; - result += "downstream.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + result += "DOWNSTREAM.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + " current: ?" + " available:" + QString::number(streamStats._ringBufferFramesAvailable) + " starves:" + QString::number(streamStats._ringBufferStarveCount) - + " not mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) - + " silents dropped: ?" - + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) - + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) - + " min gap:" + QString::number(streamStats._timeGapMin) - + " max gap:" + QString::number(streamStats._timeGapMax) - + " avg gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) - + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) - + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); + + " silents_dropped: ?" + + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) + + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), '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) { AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer); - result += " mic.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + 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) + + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) - + " silents dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) - + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) - + " min gap:" + QString::number(streamStats._timeGapMin) - + " max gap:" + QString::number(streamStats._timeGapMax) - + " avg gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) - + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) - + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); + + " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) + + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) + + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), '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"; } @@ -312,21 +312,21 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { for (int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) { AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]); - result += " inj.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + 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) + + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) - + " silents dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) - + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) - + " min gap:" + QString::number(streamStats._timeGapMin) - + " max gap:" + QString::number(streamStats._timeGapMax) - + " avg gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) - + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) - + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); + + " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) + + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) + + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), '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; From df7f3073dc3efd4ce49f05598e2d1430f16e3459 Mon Sep 17 00:00:00 2001 From: TonyPeng Date: Thu, 10 Jul 2014 17:18:49 -0700 Subject: [PATCH 121/135] 2 local lights as default --- interface/src/avatar/Avatar.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 83105c9022..febbba7536 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -60,9 +60,9 @@ Avatar::Avatar() : _mouseRayDirection(0.0f, 0.0f, 0.0f), _moving(false), _collisionGroups(0), + _numLocalLights(2), _initialized(false), - _shouldRenderBillboard(true), - _numLocalLights(1) + _shouldRenderBillboard(true) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); @@ -89,14 +89,14 @@ void Avatar::init() { _localLightDirections[i] = glm::vec3(0.0f, 0.0f, 0.0f); } - glm::vec3 darkGrayColor(0.3f, 0.3f, 0.3f); + 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] = greenColor; + _localLightColors[1] = darkGrayColor; _localLightDirections[0] = directionX; _localLightDirections[1] = directionY; From 69005242b9acc84aa2fa0a044d72bb50e32760fe Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 17:18:54 -0700 Subject: [PATCH 122/135] forgot to multiply rates by 100% for domain stats page --- assignment-client/src/audio/AudioMixerClientData.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index bde4521d33..94bbdc6a6b 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -278,8 +278,8 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + " silents_dropped: ?" - + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) - + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + + " 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) @@ -297,8 +297,8 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) - + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + + " 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) @@ -319,8 +319,8 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) - + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + + " 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) From d121c8e07af33811b5ac636a00a2b4635c38c5de Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 10 Jul 2014 17:21:05 -0700 Subject: [PATCH 123/135] Fix for starting with empty spanners (due to LOD), then adding. --- libraries/metavoxels/src/AttributeRegistry.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index b43759fa4f..64b2646261 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -568,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) { @@ -586,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, From 1781a2d1253692666b03754274222bbd2a73b89b Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 10 Jul 2014 17:30:43 -0700 Subject: [PATCH 124/135] fixed unused variable warning --- interface/src/ui/ApplicationOverlay.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index fad4a64835..3ccb487716 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -702,7 +702,6 @@ void ApplicationOverlay::renderPointersOculus(const glm::vec3& eyePos) { PalmData& palm = myAvatar->getHand()->getPalms()[i]; if (palm.isActive()) { glm::vec3 tip = OculusManager::getLaserPointerTipPosition(&palm); - glm::quat orientation = glm::inverse(myAvatar->getOrientation()); glm::vec3 tipPos = (tip - eyePos); float length = glm::length(eyePos - tip); From 97165cadcc45443c3f81da10ed92f4c3de8b156b Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 10 Jul 2014 17:34:10 -0700 Subject: [PATCH 125/135] Removed unneeded variable --- interface/src/ui/ApplicationOverlay.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 3ccb487716..7b3db26d8d 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -166,7 +166,6 @@ void ApplicationOverlay::displayOverlayTexture() { void ApplicationOverlay::computeOculusPickRay(float x, float y, glm::vec3& direction) const { MyAvatar* myAvatar = Application::getInstance()->getAvatar(); - glm::quat rot = myAvatar->getOrientation(); //invert y direction y = 1.0 - y; @@ -179,7 +178,7 @@ void ApplicationOverlay::computeOculusPickRay(float x, float y, glm::vec3& direc float z = -sqrt(1.0f - dist * dist); glm::vec3 relativePosition = myAvatar->getHead()->calculateAverageEyePosition() + - glm::normalize(rot * glm::vec3(x, y, z)); + glm::normalize(myAvatar->getOrientation() * glm::vec3(x, y, z)); //Rotate the UI pick ray by the avatar orientation direction = glm::normalize(relativePosition - Application::getInstance()->getCamera()->getPosition()); From af3af15084ea77f3dfe90509220b2a3d3ce75c58 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 10 Jul 2014 18:11:51 -0700 Subject: [PATCH 126/135] Comment, formatting fix. --- assignment-client/src/metavoxels/MetavoxelServer.cpp | 2 +- interface/src/ui/Stats.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index 956458404a..4d9c45ed6c 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -51,7 +51,7 @@ void MetavoxelServer::run() { // initialize Bitstream before using it in multiple threads Bitstream::preThreadingInit(); - // create the persister and start the it in its own thread + // create the persister and start it in its own thread _persister = new MetavoxelPersister(this); QThread* persistenceThread = new QThread(this); _persister->moveToThread(persistenceThread); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 0d2762f923..eeff76395b 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -420,7 +420,6 @@ 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; From 2347bd7eed458fd80b9fe81836723b69214f4a63 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 11 Jul 2014 10:47:48 -0700 Subject: [PATCH 127/135] Sitting position persists between sessions --- examples/sit.js | 54 +++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/examples/sit.js b/examples/sit.js index 056a65fbf1..f7be681d25 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{ @@ -218,33 +225,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,6 +237,22 @@ 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 && From bd7b17a7878368dcdab23bb3b91c9289f8681a57 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 11 Jul 2014 10:50:11 -0700 Subject: [PATCH 128/135] Added subimage to Billboards overlays --- .../src/ui/overlays/BillboardOverlay.cpp | 51 ++++++++++++++++--- interface/src/ui/overlays/BillboardOverlay.h | 5 +- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 8742f19c3d..d88ee6abd2 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -14,7 +14,8 @@ #include "BillboardOverlay.h" BillboardOverlay::BillboardOverlay() -: _scale(1.0f), +: _fromImage(-1,-1,-1,-1), + _scale(1.0f), _isFacingAvatar(true) { } @@ -28,6 +29,9 @@ void BillboardOverlay::render() { 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, @@ -58,19 +62,23 @@ 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); + 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(0.0f, 0.0f); + glTexCoord2f((float)_fromImage.x() / (float)_size.width(), + (float)_fromImage.y() / (float)_size.height()); glVertex2f(-x, -y); - glTexCoord2f(1.0f, 0.0f); + glTexCoord2f(((float)_fromImage.x() + (float)_fromImage.width()) / (float)_size.width(), + (float)_fromImage.y() / (float)_size.height()); glVertex2f(x, -y); - glTexCoord2f(1.0f, 1.0f); + glTexCoord2f(((float)_fromImage.x() + (float)_fromImage.width()) / (float)_size.width(), + ((float)_fromImage.y() + (float)_fromImage.height()) / _size.height()); glVertex2f(x, y); - glTexCoord2f(0.0f, 1.0f); + glTexCoord2f((float)_fromImage.x() / (float)_size.width(), + ((float)_fromImage.y() + (float)_fromImage.height()) / (float)_size.height()); glVertex2f(-x, y); } glEnd(); @@ -93,6 +101,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; From a9ceec46270eec7e3b4c56f68bc6442bf969a96a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 11 Jul 2014 10:50:46 -0700 Subject: [PATCH 129/135] Switched sit.js spheres to billboards --- examples/sit.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/sit.js b/examples/sit.js index f7be681d25..b51d59759a 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -166,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 }); From 22bcb7d7e7040e052f0a386d6b8d5ca9c8384768 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 11 Jul 2014 11:22:06 -0700 Subject: [PATCH 130/135] Billboard overlays disply grey square while image is loading --- .../src/ui/overlays/BillboardOverlay.cpp | 85 ++++++++++--------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index d88ee6abd2..4bd854114d 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -20,26 +20,25 @@ BillboardOverlay::BillboardOverlay() } void BillboardOverlay::render() { - if (_billboard.isEmpty()) { - 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(); - 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()); } glEnable(GL_ALPHA_TEST); @@ -62,25 +61,35 @@ void BillboardOverlay::render() { } glScalef(_scale, _scale, _scale); - 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(); + 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(); From e067a013a0ee7643f1c2a5a5247a5c0be35aa83f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 11 Jul 2014 11:22:38 -0700 Subject: [PATCH 131/135] Made sitting point more consistant --- examples/sit.js | 2 +- libraries/models/src/ModelsScriptingInterface.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/sit.js b/examples/sit.js index b51d59759a..c157d4854d 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -261,7 +261,7 @@ function update(deltaTime){ 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/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(); From 4c3a1d192e8b4504be67e2575e5756f86c698693 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 11 Jul 2014 11:29:32 -0700 Subject: [PATCH 132/135] fix crash in particle and model server from attempting to edit items that dont exist in the server --- libraries/models/src/ModelItem.cpp | 12 ++++++------ libraries/particles/src/Particle.cpp | 14 ++++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) 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/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index 59265c00dc..4dfe4b588e 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -385,6 +385,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,20 +394,20 @@ 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 memcpy(&newParticle._lastEdited, dataAt, sizeof(newParticle._lastEdited)); dataAt += sizeof(newParticle._lastEdited); From 7ad90ac7bbdb627cecf36565750814c704bfa327 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 11 Jul 2014 11:28:08 -0700 Subject: [PATCH 133/135] When visible is false, don't render billboards/models overlays --- interface/src/ui/overlays/BillboardOverlay.cpp | 4 ++++ interface/src/ui/overlays/ModelOverlay.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 4bd854114d..e7d5cef3be 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -20,6 +20,10 @@ BillboardOverlay::BillboardOverlay() } void BillboardOverlay::render() { + if (!_visible) { + return; + } + if (!_billboard.isEmpty()) { if (!_billboardTexture) { QImage image = QImage::fromData(_billboard); 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()) { From d832f5bec8f7e63aee3167a8299e0d9b697386b9 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 11 Jul 2014 16:08:37 -0700 Subject: [PATCH 134/135] fix menu config of visible stats --- interface/src/Menu.cpp | 10 ++++----- interface/src/Menu.h | 12 +++++------ interface/src/ui/Stats.cpp | 43 ++++++++++++++------------------------ 3 files changed, 25 insertions(+), 40 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f2ec217656..f18dde7cbf 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -441,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); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 4ca50f71b8..651cf3288a 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -357,13 +357,11 @@ 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"; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 15a54e42a6..00f9450eda 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -162,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 From 9939c00a40fad20c57db1b63cab664b67e74403c Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 11 Jul 2014 16:57:08 -0700 Subject: [PATCH 135/135] more guards to prevent crashing in bad particle edit packets --- libraries/particles/src/Particle.cpp | 114 ++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index 4dfe4b588e..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); @@ -409,6 +425,12 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr } // 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); @@ -417,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); @@ -425,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); @@ -432,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); @@ -439,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); @@ -446,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); @@ -453,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); @@ -460,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); @@ -467,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); @@ -475,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); @@ -482,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); @@ -490,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; @@ -502,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; @@ -513,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); @@ -520,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); @@ -527,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;