diff --git a/examples/example/misc/listAllScripts.js b/examples/example/misc/listAllScripts.js new file mode 100644 index 0000000000..1baa40751e --- /dev/null +++ b/examples/example/misc/listAllScripts.js @@ -0,0 +1,41 @@ +// +// listAllScripts.js +// examples/example/misc +// +// Created by Thijs Wenker on 7 Apr 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Outputs the running, public and local scripts to the console. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var runningScripts = ScriptDiscoveryService.getRunning(); +print("Running Scripts:"); +for (var i = 0; i < runningScripts.length; i++) { + print(" - " + runningScripts[i].name + (runningScripts[i].local ? "[Local]" : "") + " {" + runningScripts[i].url + "}"); +} + +var localScripts = ScriptDiscoveryService.getLocal(); +print("Local Scripts:"); +for (var i = 0; i < localScripts.length; i++) { + print(" - " + localScripts[i].name + " {" + localScripts[i].path + "}"); +} + +// recursive function to walk through all folders in public scripts +// adding 2 spaces to the prefix per depth level +function displayPublicScriptFolder(nodes, prefix) { + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].type == "folder") { + print(prefix + "<" + nodes[i].name + ">"); + displayPublicScriptFolder(nodes[i].children, " " + prefix); + continue; + } + print(prefix + nodes[i].name + " {" + nodes[i].url + "}"); + } +} + +var publicScripts = ScriptDiscoveryService.getPublic(); +print("Public Scripts:"); +displayPublicScriptFolder(publicScripts, " - "); \ No newline at end of file diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index 4ac1b70e33..dd5bced393 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -508,18 +508,22 @@ // To make this work we block the first mouseup event after the elements // received focus. If we block all mouseup events the user will not // be able to click within the selected text. + // We also check to see if the value has changed to make sure we aren't + // blocking a mouse-up event when clicking on an input spinner. var els = document.querySelectorAll("input, textarea"); for (var i = 0; i < els.length; i++) { var clicked = false; + var originalText; els[i].onfocus = function() { + originalText = this.value; this.select(); clicked = false; }; els[i].onmouseup = function(e) { - if (!clicked) { + if (!clicked && originalText == this.value) { e.preventDefault(); - clicked = true; } + clicked = true; }; } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 98ef907764..4892019ae5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -612,6 +612,10 @@ void Application::cleanupBeforeQuit() { // destroy the AudioClient so it and its thread have a chance to go down safely DependencyManager::destroy(); + +#ifdef HAVE_DDE + DependencyManager::destroy(); +#endif } Application::~Application() { @@ -1743,8 +1747,10 @@ FaceTracker* Application::getActiveFaceTracker() { void Application::setActiveFaceTracker() { #ifdef HAVE_FACESHIFT DependencyManager::get()->setTCPEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift)); -#endif +#endif +#ifdef HAVE_DDE DependencyManager::get()->setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::DDEFaceRegression)); +#endif #ifdef HAVE_VISAGE DependencyManager::get()->updateEnabled(); #endif @@ -3617,6 +3623,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("Scene", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("ScriptDiscoveryService", this->getRunningScriptsWidget()); + #ifdef HAVE_RTMIDI scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance()); #endif diff --git a/interface/src/Application.h b/interface/src/Application.h index cf047f02d4..6427b61e43 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -210,6 +210,8 @@ public: bool getLastMouseMoveWasSimulated() const { return _lastMouseMoveWasSimulated; } FaceTracker* getActiveFaceTracker(); + void setActiveFaceTracker(); + QSystemTrayIcon* getTrayIcon() { return _trayIcon; } ApplicationOverlay& getApplicationOverlay() { return _applicationOverlay; } Overlays& getOverlays() { return _overlays; } @@ -390,8 +392,6 @@ public slots: void notifyPacketVersionMismatch(); - void setActiveFaceTracker(); - void domainConnectionDenied(const QString& reason); private slots: diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 1cce0b1f56..b33275083e 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -362,28 +362,36 @@ Menu::Menu() { QAction* noFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::NoFaceTracking, 0, true, - qApp, SLOT(setActiveFaceTracker())); + this, SLOT(setActiveFaceTracker())); faceTrackerGroup->addAction(noFaceTracker); #ifdef HAVE_FACESHIFT QAction* faceshiftFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::Faceshift, 0, false, - qApp, SLOT(setActiveFaceTracker())); + this, SLOT(setActiveFaceTracker())); faceTrackerGroup->addAction(faceshiftFaceTracker); #endif - +#ifdef HAVE_DDE QAction* ddeFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::DDEFaceRegression, 0, false, - qApp, SLOT(setActiveFaceTracker())); + this, SLOT(setActiveFaceTracker())); faceTrackerGroup->addAction(ddeFaceTracker); - +#endif #ifdef HAVE_VISAGE QAction* visageFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::Visage, 0, false, - qApp, SLOT(setActiveFaceTracker())); + this, SLOT(setActiveFaceTracker())); faceTrackerGroup->addAction(visageFaceTracker); #endif } +#ifdef HAVE_DDE + faceTrackingMenu->addSeparator(); + QAction* ddeFaceTrackerReset = addActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::ResetDDETracking, + Qt::CTRL | Qt::Key_Apostrophe, + DependencyManager::get().data(), SLOT(resetTracking())); + ddeFaceTrackerReset->setVisible(false); + faceTrackingMenu->addAction(ddeFaceTrackerReset); +#endif addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSkeletonCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderHeadCollisionShapes); @@ -1003,3 +1011,11 @@ void Menu::visibilityChanged(Discoverability::Mode discoverabilityMode) { qCDebug(interfaceapp) << "ERROR Menu::visibilityChanged() called with unrecognized value."; } } + +void Menu::setActiveFaceTracker() { +#ifdef HAVE_DDE + bool isUsingDDE = Menu::getInstance()->isOptionChecked(MenuOption::DDEFaceRegression); + Menu::getInstance()->getActionForOption(MenuOption::ResetDDETracking)->setVisible(isUsingDDE); +#endif + qApp->setActiveFaceTracker(); +} diff --git a/interface/src/Menu.h b/interface/src/Menu.h index db126a3c9b..00f876a539 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -68,6 +68,7 @@ public slots: private slots: void setVisibility(); + void setActiveFaceTracker(); private: static Menu* _instance; @@ -230,6 +231,7 @@ namespace MenuOption { const QString RenderAmbientLight8 = "CAMPUS_SUNSET"; const QString RenderAmbientLight9 = "FUNSTON_BEACH_SUNSET"; const QString ResetAvatarSize = "Reset Avatar Size"; + const QString ResetDDETracking = "Reset DDE Tracking"; const QString ResetSensors = "Reset Sensors"; const QString RunningScripts = "Running Scripts"; const QString RunTimingTests = "Run Timing Tests"; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 5ee09ba1cf..38144dfe5f 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -149,7 +149,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { while (fadingIterator != _avatarFades.end()) { Avatar* avatar = static_cast(fadingIterator->data()); - avatar->setTargetScale(avatar->getScale() * SHRINK_RATE); + avatar->setTargetScale(avatar->getScale() * SHRINK_RATE, true); if (avatar->getTargetScale() < MIN_FADE_SCALE) { fadingIterator = _avatarFades.erase(fadingIterator); } else { diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index 751739ed24..a701160198 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -11,6 +11,7 @@ #include +#include #include #include #include @@ -19,10 +20,21 @@ #include "DdeFaceTracker.h" #include "FaceshiftConstants.h" #include "InterfaceLogging.h" +#include "Menu.h" -static const QHostAddress DDE_FEATURE_POINT_SERVER_ADDR("127.0.0.1"); -static const quint16 DDE_FEATURE_POINT_SERVER_PORT = 5555; +static const QHostAddress DDE_SERVER_ADDR("127.0.0.1"); +static const quint16 DDE_SERVER_PORT = 64204; +static const quint16 DDE_CONTROL_PORT = 64205; +#if defined(Q_OS_WIN) +static const QString DDE_PROGRAM_PATH = QCoreApplication::applicationDirPath() + "/dde/dde.exe"; +#elif defined(Q_OS_MAC) +static const QString DDE_PROGRAM_PATH = QCoreApplication::applicationDirPath() + "/dde.app/Contents/MacOS/dde"; +#endif +static const QStringList DDE_ARGUMENTS = QStringList() + << "--udp=" + DDE_SERVER_ADDR.toString() + ":" + QString::number(DDE_SERVER_PORT) + << "--receiver=" + QString::number(DDE_CONTROL_PORT) + << "--headless"; static const int NUM_EXPRESSIONS = 46; static const int MIN_PACKET_SIZE = (8 + NUM_EXPRESSIONS) * sizeof(float) + sizeof(int); @@ -121,14 +133,16 @@ struct Packet { }; DdeFaceTracker::DdeFaceTracker() : - DdeFaceTracker(QHostAddress::Any, DDE_FEATURE_POINT_SERVER_PORT) + DdeFaceTracker(QHostAddress::Any, DDE_SERVER_PORT, DDE_CONTROL_PORT) { } -DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 port) : +DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 serverPort, quint16 controlPort) : + _ddeProcess(NULL), _host(host), - _port(port), + _serverPort(serverPort), + _controlPort(controlPort), _lastReceiveTimestamp(0), _reset(false), _leftBlinkIndex(0), // see http://support.faceshift.com/support/articles/35129-export-of-blendshapes @@ -157,17 +171,50 @@ DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 port) : } DdeFaceTracker::~DdeFaceTracker() { - if (_udpSocket.isOpen()) { - _udpSocket.close(); - } + setEnabled(false); } void DdeFaceTracker::setEnabled(bool enabled) { +#ifdef HAVE_DDE // isOpen() does not work as one might expect on QUdpSocket; don't test isOpen() before closing socket. _udpSocket.close(); if (enabled) { - _udpSocket.bind(_host, _port); + _udpSocket.bind(_host, _serverPort); } + + if (enabled && !_ddeProcess) { + // Terminate any existing DDE process, perhaps left running after an Interface crash + const char* DDE_EXIT_COMMAND = "exit"; + _udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort); + + qDebug() << "[Info] DDE Face Tracker Starting"; + _ddeProcess = new QProcess(qApp); + connect(_ddeProcess, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus))); + _ddeProcess->start(QCoreApplication::applicationDirPath() + DDE_PROGRAM_PATH, DDE_ARGUMENTS); + } + + if (!enabled && _ddeProcess) { + _ddeProcess->kill(); // More robust than trying to send an "exit" command to DDE + _ddeProcess = NULL; + qDebug() << "[Info] DDE Face Tracker Stopped"; + } +#endif +} + +void DdeFaceTracker::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { + if (_ddeProcess) { + // DDE crashed or was manually terminated + qDebug() << "[Info] DDE Face Tracker Stopped Unexpectedly"; + _udpSocket.close(); + _ddeProcess = NULL; + Menu::getInstance()->setIsOptionChecked(MenuOption::NoFaceTracking, true); + } +} + +void DdeFaceTracker::resetTracking() { + qDebug() << "[Info] Reset DDE Tracking"; + const char* DDE_RESET_COMMAND = "reset"; + _udpSocket.writeDatagram(DDE_RESET_COMMAND, DDE_SERVER_ADDR, _controlPort); } bool DdeFaceTracker::isActive() const { @@ -239,7 +286,7 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) { } // Compute relative translation - float LEAN_DAMPING_FACTOR = 200.0f; + float LEAN_DAMPING_FACTOR = 75.0f; translation -= _referenceTranslation; translation /= LEAN_DAMPING_FACTOR; translation.x *= -1; diff --git a/interface/src/devices/DdeFaceTracker.h b/interface/src/devices/DdeFaceTracker.h index 3c95666a6c..490020e511 100644 --- a/interface/src/devices/DdeFaceTracker.h +++ b/interface/src/devices/DdeFaceTracker.h @@ -12,6 +12,11 @@ #ifndef hifi_DdeFaceTracker_h #define hifi_DdeFaceTracker_h +#if defined(Q_OS_WIN) || defined(Q_OS_OSX) + #define HAVE_DDE +#endif + +#include #include #include @@ -45,9 +50,11 @@ public: public slots: void setEnabled(bool enabled); + void resetTracking(); private slots: - + void processFinished(int exitCode, QProcess::ExitStatus exitStatus); + //sockets void socketErrorOccurred(QAbstractSocket::SocketError socketError); void readPendingDatagrams(); @@ -55,11 +62,14 @@ private slots: private: DdeFaceTracker(); - DdeFaceTracker(const QHostAddress& host, quint16 port); + DdeFaceTracker(const QHostAddress& host, quint16 serverPort, quint16 controlPort); ~DdeFaceTracker(); + QProcess* _ddeProcess; + QHostAddress _host; - quint16 _port; + quint16 _serverPort; + quint16 _controlPort; float getBlendshapeCoefficient(int index) const; void decodePacket(const QByteArray& buffer); diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index 9ff600675f..f892deabb6 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -210,3 +210,74 @@ void RunningScriptsWidget::scriptStopped(const QString& scriptName) { void RunningScriptsWidget::allScriptsStopped() { Application::getInstance()->stopAllScripts(); } + +QVariantList RunningScriptsWidget::getRunning() { + const int WINDOWS_DRIVE_LETTER_SIZE = 1; + QVariantList result; + QStringList runningScripts = Application::getInstance()->getRunningScripts(); + for (int i = 0; i < runningScripts.size(); i++) { + QUrl runningScriptURL = QUrl(runningScripts.at(i)); + if (runningScriptURL.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { + runningScriptURL = QUrl::fromLocalFile(runningScriptURL.toDisplayString(QUrl::FormattingOptions(QUrl::FullyEncoded))); + } + QVariantMap resultNode; + resultNode.insert("name", runningScriptURL.fileName()); + resultNode.insert("url", runningScriptURL.toDisplayString(QUrl::FormattingOptions(QUrl::FullyEncoded))); + resultNode.insert("local", runningScriptURL.isLocalFile()); + result.append(resultNode); + } + return result; +} + +QVariantList RunningScriptsWidget::getPublic() { + return getPublicChildNodes(NULL); +} + +QVariantList RunningScriptsWidget::getPublicChildNodes(TreeNodeFolder* parent) { + QVariantList result; + QList treeNodes = Application::getInstance()->getRunningScriptsWidget()->getScriptsModel() + ->getFolderNodes(parent); + for (int i = 0; i < treeNodes.size(); i++) { + TreeNodeBase* node = treeNodes.at(i); + if (node->getType() == TREE_NODE_TYPE_FOLDER) { + TreeNodeFolder* folder = static_cast(node); + QVariantMap resultNode; + resultNode.insert("name", node->getName()); + resultNode.insert("type", "folder"); + resultNode.insert("children", getPublicChildNodes(folder)); + result.append(resultNode); + continue; + } + TreeNodeScript* script = static_cast(node); + if (script->getOrigin() == ScriptOrigin::SCRIPT_ORIGIN_LOCAL) { + continue; + } + QVariantMap resultNode; + resultNode.insert("name", node->getName()); + resultNode.insert("type", "script"); + resultNode.insert("url", script->getFullPath()); + result.append(resultNode); + } + return result; +} + +QVariantList RunningScriptsWidget::getLocal() { + QVariantList result; + QList treeNodes = Application::getInstance()->getRunningScriptsWidget()->getScriptsModel() + ->getFolderNodes(NULL); + for (int i = 0; i < treeNodes.size(); i++) { + TreeNodeBase* node = treeNodes.at(i); + if (node->getType() != TREE_NODE_TYPE_SCRIPT) { + continue; + } + TreeNodeScript* script = static_cast(node); + if (script->getOrigin() != ScriptOrigin::SCRIPT_ORIGIN_LOCAL) { + continue; + } + QVariantMap resultNode; + resultNode.insert("name", node->getName()); + resultNode.insert("path", script->getFullPath()); + result.append(resultNode); + } + return result; +} diff --git a/interface/src/ui/RunningScriptsWidget.h b/interface/src/ui/RunningScriptsWidget.h index 3ec5590dee..5d3f6843af 100644 --- a/interface/src/ui/RunningScriptsWidget.h +++ b/interface/src/ui/RunningScriptsWidget.h @@ -33,6 +33,8 @@ public: void setRunningScripts(const QStringList& list); + const ScriptsModel* getScriptsModel() { return &_scriptsModel; } + signals: void stopScriptName(const QString& name); @@ -44,7 +46,10 @@ protected: public slots: void scriptStopped(const QString& scriptName); - + QVariantList getRunning(); + QVariantList getPublic(); + QVariantList getLocal(); + private slots: void allScriptsStopped(); void updateFileFilter(const QString& filter); @@ -60,6 +65,8 @@ private: ScriptsTableWidget* _recentlyLoadedScriptsTable; QStringList _recentlyLoadedScripts; QString _lastStoppedScript; + + QVariantList getPublicChildNodes(TreeNodeFolder* parent); }; #endif // hifi_RunningScriptsWidget_h diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 73968607f7..1d601d1294 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -297,10 +297,17 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { // no collision-model url, so we're ready to compute a shape (of type None). return true; } + if (_model->getURL().isEmpty()) { + // we need a render geometry with a scale to proceed, so give up. + return true; + } const QSharedPointer collisionNetworkGeometry = _model->getCollisionGeometry(); - if (! collisionNetworkGeometry.isNull() && collisionNetworkGeometry->isLoadedWithTextures()) { - // we have a _collisionModelURL AND a collisionNetworkGeometry AND it's fully loaded. + const QSharedPointer renderNetworkGeometry = _model->getGeometry(); + + if ((! collisionNetworkGeometry.isNull() && collisionNetworkGeometry->isLoadedWithTextures()) && + (! renderNetworkGeometry.isNull() && renderNetworkGeometry->isLoadedWithTextures())) { + // we have both URLs AND both geometries AND they are both fully loaded. return true; } @@ -309,7 +316,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { } void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { - if (_model->getCollisionURL().isEmpty()) { + if (_model->getCollisionURL().isEmpty() || _model->getURL().isEmpty()) { info.setParams(getShapeType(), 0.5f * getDimensions()); } else { const QSharedPointer collisionNetworkGeometry = _model->getCollisionGeometry(); diff --git a/libraries/physics/src/DynamicCharacterController.cpp b/libraries/physics/src/DynamicCharacterController.cpp index db84bea540..aaabe21e9b 100644 --- a/libraries/physics/src/DynamicCharacterController.cpp +++ b/libraries/physics/src/DynamicCharacterController.cpp @@ -245,7 +245,6 @@ void DynamicCharacterController::setEnabled(bool enabled) { // Don't bother clearing REMOVE bit since it might be paired with an UPDATE_SHAPE bit. // Setting the ADD bit here works for all cases so we don't even bother checking other bits. _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION; - setHovering(true); } else { if (_dynamicsWorld) { _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION; @@ -253,6 +252,7 @@ void DynamicCharacterController::setEnabled(bool enabled) { _pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION; _isOnGround = false; } + setHovering(true); _enabled = enabled; } }