diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index a5c086fc44..89d4094856 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -12,7 +12,6 @@ Script.load("progress.js"); Script.load("edit.js"); Script.load("selectAudioDevice.js"); Script.load("controllers/hydra/hydraMove.js"); -Script.load("headMove.js"); Script.load("inspect.js"); Script.load("lobby.js"); Script.load("notifications.js"); diff --git a/examples/dice.js b/examples/dice.js index 553a233a25..ee48d59617 100644 --- a/examples/dice.js +++ b/examples/dice.js @@ -13,7 +13,8 @@ // var isDice = false; -var NUMBER_OF_DICE = 2; +var NUMBER_OF_DICE = 4; +var LIFETIME = 10000; // Dice will live for about 3 hours var dice = []; var DIE_SIZE = 0.20; @@ -50,7 +51,7 @@ var diceButton = Overlays.addOverlay("image", { }); var GRAVITY = -3.5; -var LIFETIME = 300; + // NOTE: angularVelocity is in radians/sec var MAX_ANGULAR_SPEED = Math.PI; @@ -105,6 +106,7 @@ function mousePressEvent(event) { var clickedText = false; var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); if (clickedOverlay == offButton) { + deleteDice(); Script.stop(); } else if (clickedOverlay == diceButton) { var HOW_HARD = 2.0; @@ -116,10 +118,8 @@ function mousePressEvent(event) { } function scriptEnding() { - deleteDice(); Overlays.deleteOverlay(offButton); Overlays.deleteOverlay(diceButton); - } Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); diff --git a/examples/harmonicOscillator.js b/examples/harmonicOscillator.js new file mode 100644 index 0000000000..0ffbce8beb --- /dev/null +++ b/examples/harmonicOscillator.js @@ -0,0 +1,65 @@ +// harmonicOscillator.js +// +// Created by Philip Rosedale on May 5, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// An object moves around the edge of a disc while +// changing color. The script is continuously updating +// position, velocity, rotation, and color. The movement +// should appear perfectly smooth to someone else, +// provided their network connection is good. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var ball, disc; +var time = 0.0; +var range = 1.0; +var speed = 0.5; + + +var basePosition = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); + +ball = Entities.addEntity( + { type: "Box", + position: basePosition, + dimensions: { x: 0.1, y: 0.1, z: 0.1 }, + color: { red: 255, green: 0, blue: 255 } + }); + +disc = Entities.addEntity( + { type: "Sphere", + position: basePosition, + dimensions: { x: range, y: range / 20.0, z: range }, + color: { red: 128, green: 128, blue: 128 } + }); + +function update(deltaTime) { + time += deltaTime * speed; + if (!ball.isKnownID) { + ball = Entities.identifyEntity(ball); + } + rotation = Quat.angleAxis(time/Math.PI * 180.0, { x: 0, y: 1, z: 0 }); + Entities.editEntity(ball, + { + color: { red: 255 * (Math.sin(time)/2.0 + 0.5), + green: 255 - 255 * (Math.sin(time)/2.0 + 0.5), + blue: 0 }, + position: { x: basePosition.x + Math.sin(time) / 2.0 * range, + y: basePosition.y, + z: basePosition.z + Math.cos(time) / 2.0 * range }, + velocity: { x: Math.cos(time)/2.0 * range, + y: 0.0, + z: -Math.sin(time)/2.0 * range }, + rotation: rotation + }); +} + +function scriptEnding() { + Entities.deleteEntity(ball); + Entities.deleteEntity(disc); +} + +Script.scriptEnding.connect(scriptEnding); +Script.update.connect(update); diff --git a/interface/resources/images/face-mute.svg b/interface/resources/images/face-mute.svg new file mode 100644 index 0000000000..b16b7383c5 --- /dev/null +++ b/interface/resources/images/face-mute.svg @@ -0,0 +1,32 @@ + + + + Slice 1 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/interface/resources/images/face.svg b/interface/resources/images/face.svg new file mode 100644 index 0000000000..62ce0deb25 --- /dev/null +++ b/interface/resources/images/face.svg @@ -0,0 +1,31 @@ + + + + Slice 1 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e20ee73877..069eedd9e4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -103,6 +103,7 @@ #include "audio/AudioIOStatsRenderer.h" #include "audio/AudioScope.h" +#include "devices/CameraToolBox.h" #include "devices/DdeFaceTracker.h" #include "devices/Faceshift.h" #include "devices/Leapmotion.h" @@ -266,6 +267,7 @@ bool setupEssentials(int& argc, char** argv) { auto ddeFaceTracker = DependencyManager::set(); auto modelBlender = DependencyManager::set(); auto audioToolBox = DependencyManager::set(); + auto cameraToolBox = DependencyManager::set(); auto avatarManager = DependencyManager::set(); auto lodManager = DependencyManager::set(); auto jsConsole = DependencyManager::set(); @@ -590,6 +592,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // The offscreen UI needs to intercept the mouse and keyboard // events coming from the onscreen window _glWidget->installEventFilter(DependencyManager::get().data()); + + // initialize our face trackers after loading the menu settings + auto faceshiftTracker = DependencyManager::get(); + faceshiftTracker->init(); + connect(faceshiftTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled); + auto ddeTracker = DependencyManager::get(); + ddeTracker->init(); + connect(ddeTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled); } @@ -921,6 +931,14 @@ void Application::audioMuteToggled() { muteAction->setChecked(DependencyManager::get()->isMuted()); } +void Application::faceTrackerMuteToggled() { + QAction* muteAction = Menu::getInstance()->getActionForOption(MenuOption::MuteFaceTracking); + Q_CHECK_PTR(muteAction); + bool isMuted = getSelectedFaceTracker()->isMuted(); + muteAction->setChecked(isMuted); + getSelectedFaceTracker()->setEnabled(!isMuted); +} + void Application::aboutApp() { InfoView::forcedShow(INFO_HELP_PATH); } @@ -1011,6 +1029,9 @@ bool Application::event(QEvent* event) { case QEvent::MouseButtonPress: mousePressEvent((QMouseEvent*)event); return true; + case QEvent::MouseButtonDblClick: + mouseDoublePressEvent((QMouseEvent*)event); + return true; case QEvent::MouseButtonRelease: mouseReleaseEvent((QMouseEvent*)event); return true; @@ -1432,7 +1453,12 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { // stop propagation return; } - + + if (DependencyManager::get()->mousePressEvent(getMouseX(), getMouseY())) { + // stop propagation + return; + } + if (_rearMirrorTools->mousePressEvent(getMouseX(), getMouseY())) { // stop propagation return; @@ -1450,6 +1476,24 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { } } +void Application::mouseDoublePressEvent(QMouseEvent* event, unsigned int deviceID) { + // if one of our scripts have asked to capture this event, then stop processing it + if (_controllerScriptingInterface.isMouseCaptured()) { + return; + } + + if (activeWindow() == _window) { + if (event->button() == Qt::LeftButton) { + if (mouseOnScreen()) { + if (DependencyManager::get()->mouseDoublePressEvent(getMouseX(), getMouseY())) { + // stop propagation + return; + } + } + } + } +} + void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { if (!_aboutToQuit) { @@ -1845,18 +1889,45 @@ FaceTracker* Application::getActiveFaceTracker() { (faceshift->isActive() ? static_cast(faceshift.data()) : NULL)); } -void Application::setActiveFaceTracker() { +FaceTracker* Application::getSelectedFaceTracker() { + FaceTracker* faceTracker = NULL; #ifdef HAVE_FACESHIFT - DependencyManager::get()->setTCPEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift)); + if (Menu::getInstance()->isOptionChecked(MenuOption::Faceshift)) { + faceTracker = DependencyManager::get().data(); + } +#endif +#ifdef HAVE_DDE + if (Menu::getInstance()->isOptionChecked(MenuOption::UseCamera)) { + faceTracker = DependencyManager::get().data(); + } +#endif + return faceTracker; +} + +void Application::setActiveFaceTracker() { + bool isMuted = Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking); +#ifdef HAVE_FACESHIFT + auto faceshiftTracker = DependencyManager::get(); + faceshiftTracker->setIsMuted(isMuted); + faceshiftTracker->setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) && !isMuted); #endif #ifdef HAVE_DDE bool isUsingDDE = Menu::getInstance()->isOptionChecked(MenuOption::UseCamera); Menu::getInstance()->getActionForOption(MenuOption::UseAudioForMouth)->setVisible(isUsingDDE); Menu::getInstance()->getActionForOption(MenuOption::VelocityFilter)->setVisible(isUsingDDE); - DependencyManager::get()->setEnabled(isUsingDDE); + auto ddeTracker = DependencyManager::get(); + ddeTracker->setIsMuted(isMuted); + ddeTracker->setEnabled(isUsingDDE && !isMuted); #endif } +void Application::toggleFaceTrackerMute() { + FaceTracker* faceTracker = getSelectedFaceTracker(); + if (faceTracker) { + faceTracker->toggleMute(); + } +} + bool Application::exportEntities(const QString& filename, const QVector& entityIDs) { QVector entities; @@ -2025,10 +2096,6 @@ void Application::init() { SixenseManager::getInstance().toggleSixense(true); #endif - // initialize our face trackers after loading the menu settings - DependencyManager::get()->init(); - DependencyManager::get()->init(); - Leapmotion::init(); RealSense::init(); @@ -2166,7 +2233,7 @@ void Application::updateMyAvatarLookAtPosition() { isLookingAtSomeone = true; // If I am looking at someone else, look directly at one of their eyes - if (tracker) { + if (tracker && !tracker->isMuted()) { // If a face tracker is active, look at the eye for the side my gaze is biased toward if (tracker->getEstimatedEyeYaw() > _myAvatar->getHead()->getFinalYaw()) { // Look at their right eye @@ -2192,7 +2259,7 @@ void Application::updateMyAvatarLookAtPosition() { // // Deflect the eyes a bit to match the detected Gaze from 3D camera if active // - if (tracker) { + if (tracker && !tracker->isMuted()) { float eyePitch = tracker->getEstimatedEyePitch(); float eyeYaw = tracker->getEstimatedEyeYaw(); const float GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT = 0.1f; @@ -2247,7 +2314,7 @@ void Application::updateCamera(float deltaTime) { if (!OculusManager::isConnected() && !TV3DManager::isConnected() && Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) { FaceTracker* tracker = getActiveFaceTracker(); - if (tracker) { + if (tracker && !tracker->isMuted()) { const float EYE_OFFSET_SCALE = 0.025f; glm::vec3 position = tracker->getHeadTranslation() * EYE_OFFSET_SCALE; float xSign = (_myCamera.getMode() == CAMERA_MODE_MIRROR) ? 1.0f : -1.0f; @@ -2311,7 +2378,7 @@ void Application::update(float deltaTime) { PerformanceTimer perfTimer("devices"); DeviceTracker::updateAll(); FaceTracker* tracker = getActiveFaceTracker(); - if (tracker) { + if (tracker && !tracker->isMuted()) { tracker->update(deltaTime); } SixenseManager::getInstance().update(deltaTime); @@ -3241,6 +3308,7 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, RenderArgs { PerformanceTimer perfTimer("3dOverlaysFront"); glClear(GL_DEPTH_BUFFER_BIT); + Glower glower; // Sets alpha to 1.0 _overlays.renderWorld(true); } activeRenderingThread = nullptr; diff --git a/interface/src/Application.h b/interface/src/Application.h index b432c581e4..58e49159a2 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -169,6 +169,7 @@ public: void mouseMoveEvent(QMouseEvent* event, unsigned int deviceID = 0); void mousePressEvent(QMouseEvent* event, unsigned int deviceID = 0); + void mouseDoublePressEvent(QMouseEvent* event, unsigned int deviceID = 0); void mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID = 0); void touchBeginEvent(QTouchEvent* event); @@ -218,6 +219,7 @@ public: bool getLastMouseMoveWasSimulated() const { return _lastMouseMoveWasSimulated; } FaceTracker* getActiveFaceTracker(); + FaceTracker* getSelectedFaceTracker(); QSystemTrayIcon* getTrayIcon() { return _trayIcon; } ApplicationOverlay& getApplicationOverlay() { return _applicationOverlay; } @@ -396,6 +398,7 @@ public slots: void resetSensors(); void setActiveFaceTracker(); + void toggleFaceTrackerMute(); void aboutApp(); void showEditEntitiesHelp(); @@ -437,6 +440,7 @@ private slots: void runTests(); void audioMuteToggled(); + void faceTrackerMuteToggled(); void setCursorVisible(bool visible); diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 9a9512a0b0..e169c96fe1 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -131,6 +131,7 @@ bool GLCanvas::event(QEvent* event) { case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: case QEvent::KeyPress: case QEvent::KeyRelease: case QEvent::FocusIn: diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8070bd5555..b6940566ad 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -394,6 +394,12 @@ Menu::Menu() { QAction* ddeFiltering = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::VelocityFilter, 0, true); ddeFiltering->setVisible(false); #endif +#if defined(HAVE_FACESHIFT) || defined(HAVE_DDE) + faceTrackingMenu->addSeparator(); + addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::MuteFaceTracking, + 0, false, + qApp, SLOT(toggleFaceTrackerMute())); +#endif auto avatarManager = DependencyManager::get(); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 7d105687ab..a622668742 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -211,6 +211,7 @@ namespace MenuOption { const QString Mirror = "Mirror"; const QString MuteAudio = "Mute Microphone"; const QString MuteEnvironment = "Mute Environment"; + const QString MuteFaceTracking = "Mute Face Tracking"; const QString NoFaceTracking = "None"; const QString OctreeStats = "Entity Statistics"; const QString OffAxisProjection = "Off-Axis Projection"; diff --git a/interface/src/audio/AudioToolBox.cpp b/interface/src/audio/AudioToolBox.cpp index 330e7bc194..e37c154429 100644 --- a/interface/src/audio/AudioToolBox.cpp +++ b/interface/src/audio/AudioToolBox.cpp @@ -35,7 +35,7 @@ bool AudioToolBox::mousePressEvent(int x, int y) { return false; } -void AudioToolBox::render(int x, int y, bool boxed) { +void AudioToolBox::render(int x, int y, int padding, bool boxed) { glEnable(GL_TEXTURE_2D); auto glCanvas = Application::getInstance()->getGLWidget(); @@ -79,7 +79,7 @@ void AudioToolBox::render(int x, int y, bool boxed) { float iconColor = 1.0f; - _iconBounds = QRect(x, y, MUTE_ICON_SIZE, MUTE_ICON_SIZE); + _iconBounds = QRect(x + padding, y, MUTE_ICON_SIZE, MUTE_ICON_SIZE); if (!audioIO->isMuted()) { glBindTexture(GL_TEXTURE_2D, _micTextureId); iconColor = 1.0f; diff --git a/interface/src/audio/AudioToolBox.h b/interface/src/audio/AudioToolBox.h index 526de89b9c..7fbbab8fad 100644 --- a/interface/src/audio/AudioToolBox.h +++ b/interface/src/audio/AudioToolBox.h @@ -18,7 +18,7 @@ class AudioToolBox : public Dependency { SINGLETON_DEPENDENCY public: - void render(int x, int y, bool boxed); + void render(int x, int y, int padding, bool boxed); bool mousePressEvent(int x, int y); protected: diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 41c2e9b54c..16cd906133 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -90,7 +90,7 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { // Only use face trackers when not playing back a recording. if (!myAvatar->isPlaying()) { FaceTracker* faceTracker = Application::getInstance()->getActiveFaceTracker(); - _isFaceTrackerConnected = faceTracker != NULL; + _isFaceTrackerConnected = faceTracker != NULL && !faceTracker->isMuted(); if (_isFaceTrackerConnected) { _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 557d630ebf..73c2e6a6e8 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -243,7 +243,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) { estimatedPosition /= OCULUS_LEAN_SCALE; } else { FaceTracker* tracker = Application::getInstance()->getActiveFaceTracker(); - if (tracker) { + if (tracker && !tracker->isMuted()) { estimatedPosition = tracker->getHeadTranslation(); _trackedHeadPosition = estimatedPosition; estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation())); diff --git a/interface/src/devices/CameraToolBox.cpp b/interface/src/devices/CameraToolBox.cpp new file mode 100644 index 0000000000..26aff4bf9a --- /dev/null +++ b/interface/src/devices/CameraToolBox.cpp @@ -0,0 +1,121 @@ +// +// CameraToolBox.cpp +// interface/src/devices +// +// Created by David Rowe on 30 Apr 2015. +// Copyright 2015 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 "InterfaceConfig.h" + +#include +#include + +#include "Application.h" +#include "CameraToolBox.h" +#include "FaceTracker.h" + + +CameraToolBox::CameraToolBox() : + _iconPulseTimeReference(usecTimestampNow()), + _doubleClickTimer(NULL) +{ +} + +CameraToolBox::~CameraToolBox() { + if (_doubleClickTimer) { + _doubleClickTimer->stop(); + delete _doubleClickTimer; + } +} + +bool CameraToolBox::mousePressEvent(int x, int y) { + if (_iconBounds.contains(x, y)) { + if (!_doubleClickTimer) { + // Toggle mute after waiting to check that it's not a double-click. + const int DOUBLE_CLICK_WAIT = 200; // ms + _doubleClickTimer = new QTimer(this); + connect(_doubleClickTimer, SIGNAL(timeout()), this, SLOT(toggleMute())); + _doubleClickTimer->setSingleShot(true); + _doubleClickTimer->setInterval(DOUBLE_CLICK_WAIT); + _doubleClickTimer->start(); + } + return true; + } + return false; +} + +bool CameraToolBox::mouseDoublePressEvent(int x, int y) { + if (_iconBounds.contains(x, y)) { + if (_doubleClickTimer) { + _doubleClickTimer->stop(); + delete _doubleClickTimer; + _doubleClickTimer = NULL; + } + Application::getInstance()->resetSensors(); + return true; + } + return false; +} + +void CameraToolBox::toggleMute() { + delete _doubleClickTimer; + _doubleClickTimer = NULL; + + FaceTracker* faceTracker = Application::getInstance()->getSelectedFaceTracker(); + if (faceTracker) { + faceTracker->toggleMute(); + } +} + +void CameraToolBox::render(int x, int y, bool boxed) { + glEnable(GL_TEXTURE_2D); + + auto glCanvas = Application::getInstance()->getGLWidget(); + if (_enabledTextureId == 0) { + _enabledTextureId = glCanvas->bindTexture(QImage(PathUtils::resourcesPath() + "images/face.svg")); + } + if (_mutedTextureId == 0) { + _mutedTextureId = glCanvas->bindTexture(QImage(PathUtils::resourcesPath() + "images/face-mute.svg")); + } + + const int MUTE_ICON_SIZE = 24; + _iconBounds = QRect(x, y, MUTE_ICON_SIZE, MUTE_ICON_SIZE); + float iconColor = 1.0f; + if (!Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking)) { + glBindTexture(GL_TEXTURE_2D, _enabledTextureId); + } else { + glBindTexture(GL_TEXTURE_2D, _mutedTextureId); + + // Make muted icon pulsate + static const float PULSE_MIN = 0.4f; + static const float PULSE_MAX = 1.0f; + static const float PULSE_FREQUENCY = 1.0f; // in Hz + qint64 now = usecTimestampNow(); + if (now - _iconPulseTimeReference > (qint64)USECS_PER_SECOND) { + // Prevents t from getting too big, which would diminish glm::cos precision + _iconPulseTimeReference = now - ((now - _iconPulseTimeReference) % USECS_PER_SECOND); + } + float t = (float)(now - _iconPulseTimeReference) / (float)USECS_PER_SECOND; + float pulseFactor = (glm::cos(t * PULSE_FREQUENCY * 2.0f * PI) + 1.0f) / 2.0f; + iconColor = PULSE_MIN + (PULSE_MAX - PULSE_MIN) * pulseFactor; + } + + glm::vec4 quadColor(iconColor, iconColor, iconColor, 1.0f); + + glm::vec2 topLeft(_iconBounds.left(), _iconBounds.top()); + glm::vec2 bottomRight(_iconBounds.right(), _iconBounds.bottom()); + glm::vec2 texCoordTopLeft(1,1); + glm::vec2 texCoordBottomRight(0,0); + + if (_boxQuadID == GeometryCache::UNKNOWN_ID) { + _boxQuadID = DependencyManager::get()->allocateID(); + } + + DependencyManager::get()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, quadColor, _boxQuadID); + + glDisable(GL_TEXTURE_2D); +} \ No newline at end of file diff --git a/interface/src/devices/CameraToolBox.h b/interface/src/devices/CameraToolBox.h new file mode 100644 index 0000000000..5f9241c81d --- /dev/null +++ b/interface/src/devices/CameraToolBox.h @@ -0,0 +1,45 @@ +// +// CameraToolBox.h +// interface/src/devices +// +// Created by David Rowe on 30 Apr 2015. +// Copyright 2015 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_CameraToolBox_h +#define hifi_CameraToolBox_h + +#include + +#include +#include + +class CameraToolBox : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + void render(int x, int y, bool boxed); + bool mousePressEvent(int x, int y); + bool mouseDoublePressEvent(int x, int y); + +protected: + CameraToolBox(); + ~CameraToolBox(); + +private slots: + void toggleMute(); + +private: + GLuint _enabledTextureId = 0; + GLuint _mutedTextureId = 0; + int _boxQuadID = GeometryCache::UNKNOWN_ID; + QRect _iconBounds; + qint64 _iconPulseTimeReference = 0; + QTimer* _doubleClickTimer; +}; + +#endif // hifi_CameraToolBox_h \ No newline at end of file diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index 98f1636a32..59d2b42a51 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -178,9 +178,7 @@ DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 serverPort, qui _filteredBrowUp(0.0f), _lastEyeBlinks(), _filteredEyeBlinks(), - _lastEyeCoefficients(), - _isCalculatingFPS(false), - _frameCount(0) + _lastEyeCoefficients() { _coefficients.resize(NUM_FACESHIFT_BLENDSHAPES); @@ -203,7 +201,16 @@ DdeFaceTracker::~DdeFaceTracker() { #pragma warning(default:4351) #endif +void DdeFaceTracker::init() { + FaceTracker::init(); + setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::UseCamera) && !_isMuted); +} + void DdeFaceTracker::setEnabled(bool enabled) { + if (!_isInitialized) { + // Don't enable until have explicitly initialized + return; + } #ifdef HAVE_DDE // isOpen() does not work as one might expect on QUdpSocket; don't test isOpen() before closing socket. _udpSocket.close(); @@ -314,7 +321,9 @@ float DdeFaceTracker::getBlendshapeCoefficient(int index) const { } void DdeFaceTracker::decodePacket(const QByteArray& buffer) { - if(buffer.size() > MIN_PACKET_SIZE) { + _lastReceiveTimestamp = usecTimestampNow(); + + if (buffer.size() > MIN_PACKET_SIZE) { bool isFiltering = Menu::getInstance()->isOptionChecked(MenuOption::VelocityFilter); Packet packet; @@ -326,7 +335,7 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) { memcpy(&translation, packet.translation, sizeof(packet.translation)); glm::quat rotation; memcpy(&rotation, &packet.rotation, sizeof(packet.rotation)); - if (_reset || (_lastReceiveTimestamp == 0)) { + if (_reset || (_lastMessageReceived == 0)) { memcpy(&_referenceTranslation, &translation, sizeof(glm::vec3)); memcpy(&_referenceRotation, &rotation, sizeof(glm::quat)); _reset = false; @@ -501,5 +510,4 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) { } else { qCWarning(interfaceapp) << "DDE Face Tracker: Decode error"; } - _lastReceiveTimestamp = usecTimestampNow(); } diff --git a/interface/src/devices/DdeFaceTracker.h b/interface/src/devices/DdeFaceTracker.h index 9fb0e943f2..9edbd4df58 100644 --- a/interface/src/devices/DdeFaceTracker.h +++ b/interface/src/devices/DdeFaceTracker.h @@ -28,6 +28,7 @@ class DdeFaceTracker : public FaceTracker, public Dependency { SINGLETON_DEPENDENCY public: + virtual void init(); virtual void reset(); virtual bool isActive() const; @@ -120,9 +121,6 @@ private: float _lastEyeBlinks[2]; float _filteredEyeBlinks[2]; float _lastEyeCoefficients[2]; - - bool _isCalculatingFPS; - int _frameCount; }; #endif // hifi_DdeFaceTracker_h diff --git a/interface/src/devices/FaceTracker.cpp b/interface/src/devices/FaceTracker.cpp index 0d40249c26..25a76ff2b1 100644 --- a/interface/src/devices/FaceTracker.cpp +++ b/interface/src/devices/FaceTracker.cpp @@ -15,14 +15,14 @@ #include "FaceTracker.h" #include "InterfaceLogging.h" +#include "Menu.h" const int FPS_TIMER_DELAY = 2000; // ms const int FPS_TIMER_DURATION = 2000; // ms -FaceTracker::FaceTracker() : - _isCalculatingFPS(false), - _frameCount(0) -{ +void FaceTracker::init() { + _isMuted = Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking); + _isInitialized = true; // FaceTracker can be used now } inline float FaceTracker::getBlendshapeCoefficient(int index) const { @@ -101,3 +101,8 @@ void FaceTracker::finishFPSTimer() { qCDebug(interfaceapp) << "Face tracker FPS =" << (float)_frameCount / ((float)FPS_TIMER_DURATION / 1000.0f); _isCalculatingFPS = false; } + +void FaceTracker::toggleMute() { + _isMuted = !_isMuted; + emit muteToggled(); +} diff --git a/interface/src/devices/FaceTracker.h b/interface/src/devices/FaceTracker.h index a0a434ee9e..2a0c4438a4 100644 --- a/interface/src/devices/FaceTracker.h +++ b/interface/src/devices/FaceTracker.h @@ -26,7 +26,7 @@ public: virtual bool isActive() const { return false; } virtual bool isTracking() const { return false; } - virtual void init() {} + virtual void init(); virtual void update(float deltaTime); virtual void reset(); @@ -42,11 +42,23 @@ public: bool isValidBlendshapeIndex(int index) const { return index >= 0 && index < getNumBlendshapes(); } const QVector& getBlendshapeCoefficients() const; float getBlendshapeCoefficient(int index) const; - + + bool isMuted() const { return _isMuted; } + void setIsMuted(bool isMuted) { _isMuted = isMuted; } + void toggleMute(); + +signals: + void muteToggled(); + +public slots: + virtual void setEnabled(bool enabled) = 0; + protected: - FaceTracker(); virtual ~FaceTracker() {}; + bool _isInitialized = false; + bool _isMuted = true; + glm::vec3 _headTranslation = glm::vec3(0.0f); glm::quat _headRotation = glm::quat(); float _estimatedEyePitch = 0.0f; @@ -63,8 +75,8 @@ private slots: void finishFPSTimer(); private: - bool _isCalculatingFPS; - int _frameCount; + bool _isCalculatingFPS = false; + int _frameCount = 0; }; #endif // hifi_FaceTracker_h diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index 183c7695af..409f359afa 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -49,7 +49,8 @@ Faceshift::Faceshift() : #ifdef HAVE_FACESHIFT void Faceshift::init() { - setTCPEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift)); + FaceTracker::init(); + setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) && !_isMuted); } void Faceshift::update(float deltaTime) { @@ -92,7 +93,7 @@ void Faceshift::reset() { bool Faceshift::isActive() const { const quint64 ACTIVE_TIMEOUT_USECS = 1000000; - return (usecTimestampNow() - _lastTrackingStateReceived) < ACTIVE_TIMEOUT_USECS; + return (usecTimestampNow() - _lastReceiveTimestamp) < ACTIVE_TIMEOUT_USECS; } bool Faceshift::isTracking() const { @@ -127,7 +128,11 @@ void Faceshift::updateFakeCoefficients(float leftBlink, float rightBlink, float coefficients[FUNNEL_BLENDSHAPE] = mouth3; } -void Faceshift::setTCPEnabled(bool enabled) { +void Faceshift::setEnabled(bool enabled) { + // Don't enable until have explicitly initialized + if (!_isInitialized) { + return; + } #ifdef HAVE_FACESHIFT if ((_tcpEnabled = enabled)) { connectSocket(); @@ -196,6 +201,8 @@ void Faceshift::send(const std::string& message) { void Faceshift::receive(const QByteArray& buffer) { #ifdef HAVE_FACESHIFT + _lastReceiveTimestamp = usecTimestampNow(); + _stream.received(buffer.size(), buffer.constData()); fsMsgPtr msg; for (fsMsgPtr msg; (msg = _stream.get_message()); ) { @@ -240,11 +247,11 @@ void Faceshift::receive(const QByteArray& buffer) { const float FRAME_AVERAGING_FACTOR = 0.99f; quint64 usecsNow = usecTimestampNow(); - if (_lastTrackingStateReceived != 0) { + if (_lastMessageReceived != 0) { _averageFrameTime = FRAME_AVERAGING_FACTOR * _averageFrameTime + - (1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastTrackingStateReceived) / 1000000.0f; + (1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastMessageReceived) / 1000000.0f; } - _lastTrackingStateReceived = usecsNow; + _lastMessageReceived = usecsNow; } break; } diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h index 3d38af5654..9be1766170 100644 --- a/interface/src/devices/Faceshift.h +++ b/interface/src/devices/Faceshift.h @@ -87,7 +87,7 @@ signals: void connectionStateChanged(); public slots: - void setTCPEnabled(bool enabled); + void setEnabled(bool enabled); private slots: void connectSocket(); @@ -114,7 +114,8 @@ private: bool _tcpEnabled = true; int _tcpRetryCount = 0; bool _tracking = false; - quint64 _lastTrackingStateReceived = 0; + quint64 _lastReceiveTimestamp = 0; + quint64 _lastMessageReceived = 0; float _averageFrameTime = STARTING_FACESHIFT_FRAME_TIME; glm::vec3 _headAngularVelocity = glm::vec3(0.0f); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 8704a61261..c044bb0674 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -25,6 +25,7 @@ #include "audio/AudioToolBox.h" #include "Application.h" #include "ApplicationOverlay.h" +#include "devices/CameraToolBox.h" #include "devices/OculusManager.h" #include "Util.h" @@ -211,6 +212,7 @@ void ApplicationOverlay::renderOverlay() { glMatrixMode(GL_MODELVIEW); renderAudioMeter(); + renderCameraToggle(); renderStatsAndLogs(); @@ -808,18 +810,46 @@ void ApplicationOverlay::renderMagnifier(glm::vec2 magPos, float sizeMult, bool } glPopMatrix(); } +const int AUDIO_METER_GAP = 5; +const int MUTE_ICON_PADDING = 10; + +void ApplicationOverlay::renderCameraToggle() { + if (Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking)) { + return; + } + + int audioMeterY; + bool smallMirrorVisible = Menu::getInstance()->isOptionChecked(MenuOption::Mirror) && !OculusManager::isConnected(); + bool boxed = smallMirrorVisible && + !Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror); + if (boxed) { + audioMeterY = MIRROR_VIEW_HEIGHT + AUDIO_METER_GAP + MUTE_ICON_PADDING; + } else { + audioMeterY = AUDIO_METER_GAP + MUTE_ICON_PADDING; + } + + DependencyManager::get()->render(MIRROR_VIEW_LEFT_PADDING + AUDIO_METER_GAP, audioMeterY, boxed); +} + void ApplicationOverlay::renderAudioMeter() { auto glCanvas = Application::getInstance()->getGLWidget(); auto audio = DependencyManager::get(); // Audio VU Meter and Mute Icon const int MUTE_ICON_SIZE = 24; - const int MUTE_ICON_PADDING = 10; - const int AUDIO_METER_WIDTH = MIRROR_VIEW_WIDTH - MUTE_ICON_SIZE - MUTE_ICON_PADDING; - const int AUDIO_METER_SCALE_WIDTH = AUDIO_METER_WIDTH - 2 ; const int AUDIO_METER_HEIGHT = 8; - const int AUDIO_METER_GAP = 5; - const int AUDIO_METER_X = MIRROR_VIEW_LEFT_PADDING + MUTE_ICON_SIZE + AUDIO_METER_GAP; + const int INTER_ICON_GAP = 2; + + int cameraSpace = 0; + int audioMeterWidth = MIRROR_VIEW_WIDTH - MUTE_ICON_SIZE - MUTE_ICON_PADDING; + int audioMeterScaleWidth = audioMeterWidth - 2; + int audioMeterX = MIRROR_VIEW_LEFT_PADDING + MUTE_ICON_SIZE + AUDIO_METER_GAP; + if (!Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking)) { + cameraSpace = MUTE_ICON_SIZE + INTER_ICON_GAP; + audioMeterWidth -= cameraSpace; + audioMeterScaleWidth -= cameraSpace; + audioMeterX += cameraSpace; + } int audioMeterY; bool smallMirrorVisible = Menu::getInstance()->isOptionChecked(MenuOption::Mirror) && !OculusManager::isConnected(); @@ -834,13 +864,13 @@ void ApplicationOverlay::renderAudioMeter() { const glm::vec4 AUDIO_METER_BLUE = { 0.0, 0.0, 1.0, 1.0 }; const glm::vec4 AUDIO_METER_GREEN = { 0.0, 1.0, 0.0, 1.0 }; const glm::vec4 AUDIO_METER_RED = { 1.0, 0.0, 0.0, 1.0 }; - const float AUDIO_GREEN_START = 0.25 * AUDIO_METER_SCALE_WIDTH; - const float AUDIO_RED_START = 0.80 * AUDIO_METER_SCALE_WIDTH; const float CLIPPING_INDICATOR_TIME = 1.0f; const float AUDIO_METER_AVERAGING = 0.5; const float LOG2 = log(2.0f); const float METER_LOUDNESS_SCALE = 2.8f / 5.0f; const float LOG2_LOUDNESS_FLOOR = 11.0f; + float audioGreenStart = 0.25f * audioMeterScaleWidth; + float audioRedStart = 0.8f * audioMeterScaleWidth; float audioLevel = 0.0f; float loudness = audio->getLastInputLoudness() + 1.0f; @@ -848,12 +878,12 @@ void ApplicationOverlay::renderAudioMeter() { float log2loudness = log(_trailingAudioLoudness) / LOG2; if (log2loudness <= LOG2_LOUDNESS_FLOOR) { - audioLevel = (log2loudness / LOG2_LOUDNESS_FLOOR) * METER_LOUDNESS_SCALE * AUDIO_METER_SCALE_WIDTH; + audioLevel = (log2loudness / LOG2_LOUDNESS_FLOOR) * METER_LOUDNESS_SCALE * audioMeterScaleWidth; } else { - audioLevel = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.0f)) * METER_LOUDNESS_SCALE * AUDIO_METER_SCALE_WIDTH; + audioLevel = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.0f)) * METER_LOUDNESS_SCALE * audioMeterScaleWidth; } - if (audioLevel > AUDIO_METER_SCALE_WIDTH) { - audioLevel = AUDIO_METER_SCALE_WIDTH; + if (audioLevel > audioMeterScaleWidth) { + audioLevel = audioMeterScaleWidth; } bool isClipping = ((audio->getTimeSinceLastClip() > 0.0f) && (audio->getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)); @@ -863,7 +893,7 @@ void ApplicationOverlay::renderAudioMeter() { renderCollisionOverlay(glCanvas->width(), glCanvas->height(), magnitude, 1.0f); } - DependencyManager::get()->render(MIRROR_VIEW_LEFT_PADDING + AUDIO_METER_GAP, audioMeterY, boxed); + DependencyManager::get()->render(MIRROR_VIEW_LEFT_PADDING + AUDIO_METER_GAP, audioMeterY, cameraSpace, boxed); DependencyManager::get()->render(glCanvas->width(), glCanvas->height()); DependencyManager::get()->render(WHITE_TEXT, glCanvas->width(), glCanvas->height()); @@ -871,10 +901,10 @@ void ApplicationOverlay::renderAudioMeter() { audioMeterY += AUDIO_METER_HEIGHT; // Draw audio meter background Quad - DependencyManager::get()->renderQuad(AUDIO_METER_X, audioMeterY, AUDIO_METER_WIDTH, AUDIO_METER_HEIGHT, + DependencyManager::get()->renderQuad(audioMeterX, audioMeterY, audioMeterWidth, AUDIO_METER_HEIGHT, glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); - if (audioLevel > AUDIO_RED_START) { + if (audioLevel > audioRedStart) { glm::vec4 quadColor; if (!isClipping) { quadColor = AUDIO_METER_RED; @@ -882,16 +912,16 @@ void ApplicationOverlay::renderAudioMeter() { quadColor = glm::vec4(1, 1, 1, 1); } // Draw Red Quad - DependencyManager::get()->renderQuad(AUDIO_METER_X + AUDIO_RED_START, + DependencyManager::get()->renderQuad(audioMeterX + audioRedStart, audioMeterY, - audioLevel - AUDIO_RED_START, + audioLevel - audioRedStart, AUDIO_METER_HEIGHT, quadColor, _audioRedQuad); - audioLevel = AUDIO_RED_START; + audioLevel = audioRedStart; } - if (audioLevel > AUDIO_GREEN_START) { + if (audioLevel > audioGreenStart) { glm::vec4 quadColor; if (!isClipping) { quadColor = AUDIO_METER_GREEN; @@ -899,13 +929,13 @@ void ApplicationOverlay::renderAudioMeter() { quadColor = glm::vec4(1, 1, 1, 1); } // Draw Green Quad - DependencyManager::get()->renderQuad(AUDIO_METER_X + AUDIO_GREEN_START, + DependencyManager::get()->renderQuad(audioMeterX + audioGreenStart, audioMeterY, - audioLevel - AUDIO_GREEN_START, + audioLevel - audioGreenStart, AUDIO_METER_HEIGHT, quadColor, _audioGreenQuad); - audioLevel = AUDIO_GREEN_START; + audioLevel = audioGreenStart; } if (audioLevel >= 0) { @@ -916,7 +946,7 @@ void ApplicationOverlay::renderAudioMeter() { quadColor = glm::vec4(1, 1, 1, 1); } // Draw Blue (low level) quad - DependencyManager::get()->renderQuad(AUDIO_METER_X, + DependencyManager::get()->renderQuad(audioMeterX, audioMeterY, audioLevel, AUDIO_METER_HEIGHT, quadColor, _audioBlueQuad); diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index e6c7526c5d..54c8613f94 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -101,6 +101,7 @@ private: void renderPointersOculus(const glm::vec3& eyePos); void renderAudioMeter(); + void renderCameraToggle(); void renderStatsAndLogs(); void renderDomainConnectionStatusBorder(); diff --git a/interface/src/ui/UserInputMapper.h b/interface/src/ui/UserInputMapper.h index 32a1782419..ab63bdbef7 100755 --- a/interface/src/ui/UserInputMapper.h +++ b/interface/src/ui/UserInputMapper.h @@ -57,7 +57,9 @@ public: bool isAxis() const { return getType() == ChannelType::AXIS; } bool isJoint() const { return getType() == ChannelType::JOINT; } - explicit Input() {} + // WORKAROUND: the explicit initializer here avoids a bug in GCC-4.8.2 (but not found in 4.9.2) + // where the default initializer (a C++-11ism) for the union data above is not applied. + explicit Input() : _id(0) {} explicit Input(uint32 id) : _id(id) {} explicit Input(uint16 device, uint16 channel, ChannelType type) : _device(device), _channel(channel), _type(uint16(type)) {} Input(const Input& src) : _id(src._id) {} diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp index 5077595831..24ef1a9cf3 100644 --- a/tests/ui/src/main.cpp +++ b/tests/ui/src/main.cpp @@ -162,6 +162,7 @@ public: Mirror, MuteAudio, MuteEnvironment, + MuteFaceTracking, NoFaceTracking, NoShadows, OctreeStats,