From 9992f4b5c5bbb557a05a0c7e09c35bf8127e533f Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 16 Sep 2015 01:16:58 +0200 Subject: [PATCH 01/19] Made stopping scripts by hash (path/url) more tolerable and fixed stopping script functionality in ScriptDiscoveryService: made stopScript(string) to stop script by url or path and stopScriptName(string) to stop by filename --- interface/src/Application.cpp | 15 ++++---- interface/src/Application.h | 4 +-- interface/src/ui/RunningScriptsWidget.cpp | 42 ++++++++++++++--------- interface/src/ui/RunningScriptsWidget.h | 8 ++--- 4 files changed, 39 insertions(+), 30 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 89ce392ba0..953c8162fc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -606,7 +606,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _overlays.init(); // do this before scripts load _runningScriptsWidget->setRunningScripts(getRunningScripts()); - connect(_runningScriptsWidget, &RunningScriptsWidget::stopScriptName, this, &Application::stopScript); connect(this, SIGNAL(aboutToQuit()), this, SLOT(saveScripts())); connect(this, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit())); @@ -4328,17 +4327,18 @@ void Application::stopAllScripts(bool restart) { _myAvatar->clearScriptableSettings(); } -void Application::stopScript(const QString &scriptName, bool restart) { - const QString& scriptURLString = QUrl(scriptName).toString(); - if (_scriptEnginesHash.contains(scriptURLString)) { - ScriptEngine* scriptEngine = _scriptEnginesHash[scriptURLString]; +bool Application::stopScript(const QString& scriptHash, bool restart) { + bool stoppedScript = false; + if (_scriptEnginesHash.contains(scriptHash)) { + ScriptEngine* scriptEngine = _scriptEnginesHash[scriptHash]; if (restart) { auto scriptCache = DependencyManager::get(); - scriptCache->deleteScript(scriptName); + scriptCache->deleteScript(QUrl(scriptHash)); connect(scriptEngine, SIGNAL(finished(const QString&)), SLOT(reloadScript(const QString&))); } scriptEngine->stop(); - qCDebug(interfaceapp) << "stopping script..." << scriptName; + stoppedScript = true; + qCDebug(interfaceapp) << "stopping script..." << scriptHash; // HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities // whenever a script stops in case it happened to have been setting joint rotations. // TODO: expose animation priorities and provide a layered animation control system. @@ -4347,6 +4347,7 @@ void Application::stopScript(const QString &scriptName, bool restart) { if (_scriptEnginesHash.empty()) { _myAvatar->clearScriptableSettings(); } + return stoppedScript; } void Application::reloadAllScripts() { diff --git a/interface/src/Application.h b/interface/src/Application.h index b997fae823..5cb70cf676 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -292,7 +292,7 @@ public: NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; } QStringList getRunningScripts() { return _scriptEnginesHash.keys(); } - ScriptEngine* getScriptEngine(QString scriptHash) { return _scriptEnginesHash.contains(scriptHash) ? _scriptEnginesHash[scriptHash] : NULL; } + ScriptEngine* getScriptEngine(const QString& scriptHash) { return _scriptEnginesHash.contains(scriptHash) ? _scriptEnginesHash[scriptHash] : NULL; } bool isLookingAtMyAvatar(AvatarSharedPointer avatar); @@ -395,7 +395,7 @@ public slots: void reloadScript(const QString& scriptName, bool isUserLoaded = true); void scriptFinished(const QString& scriptName); void stopAllScripts(bool restart = false); - void stopScript(const QString& scriptName, bool restart = false); + bool stopScript(const QString& scriptHash, bool restart = false); void reloadAllScripts(); void reloadOneScript(const QString& scriptName); void loadDefaultScripts(); diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index 1165de7592..61b03bd610 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -57,16 +57,15 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) : connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &RunningScriptsWidget::updateFileFilter); connect(ui->scriptTreeView, &QTreeView::doubleClicked, this, &RunningScriptsWidget::loadScriptFromList); - connect(ui->reloadAllButton, &QPushButton::clicked, - Application::getInstance(), &Application::reloadAllScripts); - connect(ui->stopAllButton, &QPushButton::clicked, - this, &RunningScriptsWidget::allScriptsStopped); - connect(ui->loadScriptFromDiskButton, &QPushButton::clicked, - Application::getInstance(), &Application::loadDialog); - connect(ui->loadScriptFromURLButton, &QPushButton::clicked, - Application::getInstance(), &Application::loadScriptURLDialog); - connect(&_reloadSignalMapper, SIGNAL(mapped(QString)), Application::getInstance(), SLOT(reloadOneScript(const QString&))); - connect(&_stopSignalMapper, SIGNAL(mapped(QString)), Application::getInstance(), SLOT(stopScript(const QString&))); + connect(ui->reloadAllButton, &QPushButton::clicked, Application::getInstance(), &Application::reloadAllScripts); + connect(ui->stopAllButton, &QPushButton::clicked, this, &RunningScriptsWidget::allScriptsStopped); + connect(ui->loadScriptFromDiskButton, &QPushButton::clicked, Application::getInstance(), &Application::loadDialog); + connect(ui->loadScriptFromURLButton, &QPushButton::clicked, Application::getInstance(), &Application::loadScriptURLDialog); + connect(&_reloadSignalMapper, static_cast(&QSignalMapper::mapped), + Application::getInstance(), &Application::reloadOneScript); + + connect(&_stopSignalMapper, static_cast(&QSignalMapper::mapped), + [](const QString& script) { Application::getInstance()->stopScript(script); }); UIUtil::scaleWidgetFontSizes(this); } @@ -217,9 +216,6 @@ void RunningScriptsWidget::keyPressEvent(QKeyEvent *keyEvent) { } } -void RunningScriptsWidget::scriptStopped(const QString& scriptName) { -} - void RunningScriptsWidget::allScriptsStopped() { Application::getInstance()->stopAllScripts(); } @@ -227,15 +223,16 @@ void RunningScriptsWidget::allScriptsStopped() { 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)); + foreach(const QString& runningScript, Application::getInstance()->getRunningScripts()) { + QUrl runningScriptURL = QUrl(runningScript); 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))); + // The path contains the exact path/URL of the script, which also is used in the stopScript function. + resultNode.insert("path", runningScript); resultNode.insert("local", runningScriptURL.isLocalFile()); result.append(resultNode); } @@ -294,3 +291,16 @@ QVariantList RunningScriptsWidget::getLocal() { } return result; } + +bool RunningScriptsWidget::stopScriptByName(const QString& name) { + foreach (const QString& runningScript, Application::getInstance()->getRunningScripts()) { + if (QUrl(runningScript).fileName().toLower() == name.trimmed().toLower()) { + return Application::getInstance()->stopScript(runningScript, false); + } + } + return false; +} + +bool RunningScriptsWidget::stopScript(const QString& name, bool restart) { + return Application::getInstance()->stopScript(name, restart); +} diff --git a/interface/src/ui/RunningScriptsWidget.h b/interface/src/ui/RunningScriptsWidget.h index c09bce5443..9029b13c56 100644 --- a/interface/src/ui/RunningScriptsWidget.h +++ b/interface/src/ui/RunningScriptsWidget.h @@ -36,7 +36,7 @@ public: const ScriptsModel* getScriptsModel() { return &_scriptsModel; } signals: - void stopScriptName(const QString& name, bool restart = false); + void scriptStopped(const QString& scriptName); protected: virtual bool eventFilter(QObject* sender, QEvent* event); @@ -45,10 +45,11 @@ protected: virtual void showEvent(QShowEvent* event); public slots: - void scriptStopped(const QString& scriptName); QVariantList getRunning(); QVariantList getPublic(); QVariantList getLocal(); + bool stopScript(const QString& name, bool restart = false); + bool stopScriptByName(const QString& name); private slots: void allScriptsStopped(); @@ -63,9 +64,6 @@ private: QSignalMapper _stopSignalMapper; ScriptsModelFilter _scriptsModelFilter; ScriptsModel _scriptsModel; - ScriptsTableWidget* _recentlyLoadedScriptsTable; - QStringList _recentlyLoadedScripts; - QString _lastStoppedScript; QVariantList getPublicChildNodes(TreeNodeFolder* parent); }; From efc321bc1053d5bd53cdc48ee199b61d778bd701 Mon Sep 17 00:00:00 2001 From: BOB LONG Date: Wed, 16 Sep 2015 00:40:26 -0700 Subject: [PATCH 02/19] Display face blend coefficients Display the face blend coefficients and update the value in real time. --- examples/faceBlendCoefficients.js | 98 ++++++++++++++++++++++++++++++ interface/src/avatar/MyAvatar.h | 2 + libraries/render-utils/src/Model.h | 5 ++ 3 files changed, 105 insertions(+) create mode 100644 examples/faceBlendCoefficients.js diff --git a/examples/faceBlendCoefficients.js b/examples/faceBlendCoefficients.js new file mode 100644 index 0000000000..56fcadca9d --- /dev/null +++ b/examples/faceBlendCoefficients.js @@ -0,0 +1,98 @@ +// +// coefficients.js +// +// version 2.0 +// +// Created by Bob Long, 9/14/2015 +// A simple panel that can display the blending coefficients of Avatar's face model. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include('utilities/tools/cookies.js') + +var panel; +var coeff; +var interval; +var item = 0; +var DEVELOPER_MENU = "Developer"; +var AVATAR_MENU = DEVELOPER_MENU + " > Avatar"; +var SHOW_FACE_BLEND_COEFFICIENTS = "Show face blend coefficients" + +function MenuConnect(menuItem) { + if (menuItem == SHOW_FACE_BLEND_COEFFICIENTS) { + if(Menu.isOptionChecked(SHOW_FACE_BLEND_COEFFICIENTS)) { + panel.show(); + Overlays.editOverlay(coeff, { visible : true }); + } else { + panel.hide(); + Overlays.editOverlay(coeff, { visible : false }); + } + } +} + +// Add a menu item to show/hide the coefficients +function setupMenu() { + if (!Menu.menuExists(DEVELOPER_MENU)) { + Menu.addMenu(DEVELOPER_MENU); + } + + if (!Menu.menuExists(AVATAR_MENU)) { + Menu.addMenu(AVATAR_MENU); + } + + Menu.addMenuItem({ menuName: AVATAR_MENU, menuItemName: SHOW_FACE_BLEND_COEFFICIENTS, isCheckable: true, isChecked: true }); + Menu.menuItemEvent.connect(MenuConnect); +} + +function setupPanel() { + panel = new Panel(10, 400); + + // Slider to select which coefficient to display + panel.newSlider("Select Coefficient Index", + 0, + 100, + function(value) { item = value.toFixed(0); }, + function() { return item; }, + function(value) { return "index = " + item; } + ); + + // The raw overlay used to show the actual coefficient value + coeff = Overlays.addOverlay("text", { + x: 10, + y: 420, + width: 300, + height: 50, + color: { red: 255, green: 255, blue: 255 }, + alpha: 1.0, + backgroundColor: { red: 127, green: 127, blue: 127 }, + backgroundAlpha: 0.5, + topMargin: 15, + leftMargin: 20, + text: "Coefficient: 0.0" + }); + + // Set up the interval (0.5 sec) to update the coefficient. + interval = Script.setInterval(function() { + Overlays.editOverlay(coeff, { text: "Coefficient: " + MyAvatar.getFaceBlendCoef(item).toFixed(4) }); + }, 500); + + // Mouse event setup + Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); }); + Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); }); + Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); }); +} + +// Clean up +function scriptEnding() { + panel.destroy(); + Overlays.deleteOverlay(coeff); + Script.clearInterval(interval); + + Menu.removeMenuItem(AVATAR_MENU, SHOW_FACE_BLEND_COEFFICIENTS); +} + +setupMenu(); +setupPanel(); +Script.scriptEnding.connect(scriptEnding); \ No newline at end of file diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index bb3c6385f9..5fd5ee4c29 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -110,6 +110,8 @@ public: Q_INVOKABLE float getHeadFinalRoll() const { return getHead()->getFinalRoll(); } Q_INVOKABLE float getHeadFinalPitch() const { return getHead()->getFinalPitch(); } Q_INVOKABLE float getHeadDeltaPitch() const { return getHead()->getDeltaPitch(); } + Q_INVOKABLE int getFaceBlendCoefNum() const { return getHead()->getFaceModel().getBlendshapeCoefficientsNum(); } + Q_INVOKABLE float getFaceBlendCoef(int index) const { return getHead()->getFaceModel().getBlendshapeCoefficient(index); } Q_INVOKABLE glm::vec3 getEyePosition() const { return getHead()->getEyePosition(); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 348e5cf549..d1aa2901c8 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -191,6 +191,11 @@ public: const std::unordered_set& getCauterizeBoneSet() const { return _cauterizeBoneSet; } void setCauterizeBoneSet(const std::unordered_set& boneSet) { _cauterizeBoneSet = boneSet; } + int getBlendshapeCoefficientsNum() const { return _blendshapeCoefficients.size(); } + float getBlendshapeCoefficient(int index) const { + return index >= _blendshapeCoefficients.size() || index < 0 ? + 0.0f : _blendshapeCoefficients.at(index); } + protected: void setPupilDilation(float dilation) { _pupilDilation = dilation; } From a897c17eb254080f445ef08a82eea839d73eece6 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Fri, 18 Sep 2015 14:31:40 -0700 Subject: [PATCH 03/19] Remove grab-frame and reset position code --- examples/toys/flashlight/flashlight.js | 28 ++------------------------ 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/examples/toys/flashlight/flashlight.js b/examples/toys/flashlight/flashlight.js index 165f693e8e..d5bdb4a630 100644 --- a/examples/toys/flashlight/flashlight.js +++ b/examples/toys/flashlight/flashlight.js @@ -31,15 +31,6 @@ _this._spotlight = null; }; - - GRAB_FRAME_USER_DATA_KEY = "grabFrame"; - - // These constants define the Flashlight model Grab Frame - var MODEL_GRAB_FRAME = { - relativePosition: {x: 0, y: -0.1, z: 0}, - relativeRotation: Quat.angleAxis(180, {x: 1, y: 0, z: 0}) - }; - // These constants define the Spotlight position and orientation relative to the model var MODEL_LIGHT_POSITION = {x: 0, y: 0, z: 0}; var MODEL_LIGHT_ROTATION = Quat.angleAxis (-90, {x: 1, y: 0, z: 0}); @@ -98,8 +89,6 @@ }); _this._hasSpotlight = true; - _this._startModelPosition = modelProperties.position; - _this._startModelRotation = modelProperties.rotation; debugPrint("Flashlight:: creating a spotlight"); } else { @@ -120,10 +109,6 @@ _this._hasSpotlight = false; _this._spotlight = null; - // Reset model to initial position - Entities.editEntity(_this.entityID, {position: _this._startModelPosition, rotation: _this._startModelRotation, - velocity: {x: 0, y: 0, z: 0}, angularVelocity: {x: 0, y: 0, z: 0}}); - // if we are not being grabbed, and we previously were, then we were just released, remember that // and print out a message _this.beingGrabbed = false; @@ -139,17 +124,8 @@ _this.entityID = entityID; var modelProperties = Entities.getEntityProperties(entityID); - _this._startModelPosition = modelProperties.position; - _this._startModelRotation = modelProperties.rotation; - - // Make sure the Flashlight entity has a correct grab frame setup - var userData = getEntityUserData(entityID); - debugPrint(JSON.stringify(userData)); - if (!userData.grabFrame) { - setEntityCustomData(GRAB_FRAME_USER_DATA_KEY, entityID, MODEL_GRAB_FRAME); - debugPrint(JSON.stringify(MODEL_GRAB_FRAME)); - debugPrint("Assigned the grab frmae for the Flashlight entity"); - } + + Script.update.connect(this.update); }, From dc17985e291ce406d91b5c92a64ae524c8ca3f9d Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Fri, 18 Sep 2015 15:24:44 -0700 Subject: [PATCH 04/19] Add some logic for turning off the flashlight when squeeze trigger pressure is low, and turning it back on when squeeze pressure goes over certain amount --- examples/toys/flashlight/flashlight.js | 90 ++++++++++++++++++++------ 1 file changed, 72 insertions(+), 18 deletions(-) diff --git a/examples/toys/flashlight/flashlight.js b/examples/toys/flashlight/flashlight.js index d5bdb4a630..cc708beccf 100644 --- a/examples/toys/flashlight/flashlight.js +++ b/examples/toys/flashlight/flashlight.js @@ -12,9 +12,10 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +// TODO: update to use new grab signals, which will include handedness. (function() { - + function debugPrint(message) { //print(message); } @@ -25,24 +26,35 @@ // this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember // our this object, so we can access it in cases where we're called without a this (like in the case of various global signals) - Flashlight = function() { + Flashlight = function() { _this = this; _this._hasSpotlight = false; _this._spotlight = null; }; + var DISABLE_LIGHT_THRESHOLD = 0.5; // These constants define the Spotlight position and orientation relative to the model - var MODEL_LIGHT_POSITION = {x: 0, y: 0, z: 0}; - var MODEL_LIGHT_ROTATION = Quat.angleAxis (-90, {x: 1, y: 0, z: 0}); + var MODEL_LIGHT_POSITION = { + x: 0, + y: 0, + z: 0 + }; + var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { + x: 1, + y: 0, + z: 0 + }); // Evaluate the world light entity position and orientation from the model ones function evalLightWorldTransform(modelPos, modelRot) { - return {p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), - q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)}; + return { + p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), + q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) + }; }; Flashlight.prototype = { - + lightOn: false, // update() will be called regulary, because we've hooked the update signal in our preload() function @@ -56,7 +68,10 @@ var entityID = _this.entityID; // we want to assume that if there is no grab data, then we are not being grabbed - var defaultGrabData = { activated: false, avatarId: null }; + var defaultGrabData = { + activated: false, + avatarId: null + }; // this handy function getEntityCustomData() is available in utils.js and it will return just the specific section // of user data we asked for. If it's not available it returns our default data. @@ -81,8 +96,16 @@ position: lightTransform.p, rotation: lightTransform.q, isSpotlight: true, - dimensions: { x: 2, y: 2, z: 20 }, - color: { red: 255, green: 255, blue: 255 }, + dimensions: { + x: 2, + y: 2, + z: 20 + }, + color: { + red: 255, + green: 255, + blue: 255 + }, intensity: 2, exponent: 0.3, cutoff: 20 @@ -93,8 +116,11 @@ debugPrint("Flashlight:: creating a spotlight"); } else { // Updating the spotlight - Entities.editEntity(_this._spotlight, {position: lightTransform.p, rotation: lightTransform.q}); - + Entities.editEntity(_this._spotlight, { + position: lightTransform.p, + rotation: lightTransform.q + }); + _this.changeLightWithTriggerPressure(); debugPrint("Flashlight:: updating the spotlight"); } @@ -115,19 +141,47 @@ debugPrint("I'm was released..."); } }, + changeLightWithTriggerPressure: function(flashLightHand) { + var handClickString = flashLightHand + "_HAND_CLICK"; + + var handClick = Controller.findAction(handClickString); + + this.triggerValue = Controller.getActionValue(handClick); + + if (this.triggerValue < DISABLE_LIGHT_THRESHOLD && this.lightOn === true) { + this.turnLightOff(); + } else if (this.triggerValue >= DISABLE_LIGHT_THRESHOLD && this.lightOn === false) { + this.turnLightOn(); + } + + return triggerValue + }, + turnLightOff: function() { + Entities.editEntity(_this._spotlight, { + intensity: 0 + }); + this.lightOn = false + }, + turnLightOn: function() { + Entities.editEntity(_this._spotlight, { + intensity: 2 + }); + this.lightOn = true + }, + // preload() will be called when the entity has become visible (or known) to the interface // it gives us a chance to set our local JavaScript object up. In this case it means: // * remembering our entityID, so we can access it in cases where we're called without an entityID // * connecting to the update signal so we can check our grabbed state preload: function(entityID) { _this.entityID = entityID; - - var modelProperties = Entities.getEntityProperties(entityID); - - - Script.update.connect(this.update); + var modelProperties = Entities.getEntityProperties(entityID); + + + + Script.update.connect(this.update); }, // unload() will be called when our entity is no longer available. It may be because we were deleted, @@ -147,4 +201,4 @@ // entity scripts always need to return a newly constructed object of our type return new Flashlight(); -}) +}) \ No newline at end of file From c364189bc858f914121b3f98841fd88697ce32a7 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Fri, 18 Sep 2015 15:26:34 -0700 Subject: [PATCH 05/19] Update file header --- examples/toys/flashlight/flashlight.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/toys/flashlight/flashlight.js b/examples/toys/flashlight/flashlight.js index cc708beccf..9aa64a4435 100644 --- a/examples/toys/flashlight/flashlight.js +++ b/examples/toys/flashlight/flashlight.js @@ -1,6 +1,7 @@ // -// flashligh.js -// examples/entityScripts +// flashlight.js +// +// Script Type: Entity // // Created by Sam Gateau on 9/9/15. // Copyright 2015 High Fidelity, Inc. @@ -13,6 +14,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // // TODO: update to use new grab signals, which will include handedness. +// BONUS: dim the light with pressure instead of binary on/off (function() { @@ -169,7 +171,7 @@ }); this.lightOn = true }, - + // preload() will be called when the entity has become visible (or known) to the interface // it gives us a chance to set our local JavaScript object up. In this case it means: // * remembering our entityID, so we can access it in cases where we're called without an entityID From e21c1cb67cbb94fd9efb627457b9159ba633d180 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 21 Sep 2015 11:51:53 -0700 Subject: [PATCH 06/19] make sure server/mixers are first in FIFO --- domain-server/src/DomainServer.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 49b7f2e183..7a57780fd1 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1744,9 +1744,22 @@ void DomainServer::addStaticAssignmentsToQueue() { // if the domain-server has just restarted, // check if there are static assignments that we need to throw into the assignment queue - QHash staticHashCopy = _allAssignments; - QHash::iterator staticAssignment = staticHashCopy.begin(); - while (staticAssignment != staticHashCopy.end()) { + auto sharedAssignments = _allAssignments.values(); + + // sort the assignments to put the server/mixer assignments first + qSort(sharedAssignments.begin(), sharedAssignments.end(), [](SharedAssignmentPointer a, SharedAssignmentPointer b){ + if (a->getType() == b->getType()) { + return true; + } else if (a->getType() != Assignment::AgentType && b->getType() != Assignment::AgentType) { + return a->getType() < b->getType(); + } else { + return a->getType() != Assignment::AgentType; + } + }); + + auto staticAssignment = sharedAssignments.begin(); + + while (staticAssignment != sharedAssignments.end()) { // add any of the un-matched static assignments to the queue // enumerate the nodes and check if there is one with an attached assignment with matching UUID From 030404157e9f144c8d723ba102026568e68f588e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 21 Sep 2015 11:54:41 -0700 Subject: [PATCH 07/19] cleanup Assignment grab from iterator --- domain-server/src/DomainServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 7a57780fd1..614b0a1528 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1763,7 +1763,7 @@ void DomainServer::addStaticAssignmentsToQueue() { // add any of the un-matched static assignments to the queue // enumerate the nodes and check if there is one with an attached assignment with matching UUID - if (!DependencyManager::get()->nodeWithUUID(staticAssignment->data()->getUUID())) { + if (!DependencyManager::get()->nodeWithUUID((*staticAssignment)->getUUID())) { // this assignment has not been fulfilled - reset the UUID and add it to the assignment queue refreshStaticAssignmentAndAddToQueue(*staticAssignment); } From fd67a2b86fd86f1f8a9f055d192eed6d4253741e Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Mon, 21 Sep 2015 16:10:08 -0700 Subject: [PATCH 08/19] Add on/off by pressure; add point bulb to end of flashlight --- examples/toys/flashlight/createFlashlight.js | 33 +-- examples/toys/flashlight/flashlight.js | 240 +++++++++++-------- 2 files changed, 153 insertions(+), 120 deletions(-) diff --git a/examples/toys/flashlight/createFlashlight.js b/examples/toys/flashlight/createFlashlight.js index bf03de547b..ad089e001a 100644 --- a/examples/toys/flashlight/createFlashlight.js +++ b/examples/toys/flashlight/createFlashlight.js @@ -15,30 +15,33 @@ Script.include("https://hifi-public.s3.amazonaws.com/scripts/utilities.js"); -var scriptURL = "https://hifi-public.s3.amazonaws.com/scripts/toys/flashlight/flashlight.js?"+randInt(0,1000); +var scriptURL = Script.resolvePath('flashlight.js?12322'); var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx"; - -var center = Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0, y: 0.5, z: 0}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation()))); +var center = Vec3.sum(Vec3.sum(MyAvatar.position, { + x: 0, + y: 0.5, + z: 0 +}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation()))); var flashlight = Entities.addEntity({ - type: "Model", - modelURL: modelURL, - position: center, - dimensions: { - x: 0.04, - y: 0.15, - z: 0.04 - }, - collisionsWillMove: true, - shapeType: 'box', - script: scriptURL + type: "Model", + modelURL: modelURL, + position: center, + dimensions: { + x: 0.04, + y: 0.15, + z: 0.04 + }, + collisionsWillMove: true, + shapeType: 'box', + script: scriptURL }); function cleanup() { - Entities.deleteEntity(flashlight); + Entities.deleteEntity(flashlight); } diff --git a/examples/toys/flashlight/flashlight.js b/examples/toys/flashlight/flashlight.js index 9aa64a4435..7f2bd210da 100644 --- a/examples/toys/flashlight/flashlight.js +++ b/examples/toys/flashlight/flashlight.js @@ -4,6 +4,7 @@ // Script Type: Entity // // Created by Sam Gateau on 9/9/15. +// Additions by James B. Pollack @imgntn on 9/21/2015 // Copyright 2015 High Fidelity, Inc. // // This is a toy script that can be added to the Flashlight model entity: @@ -13,15 +14,9 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// TODO: update to use new grab signals, which will include handedness. -// BONUS: dim the light with pressure instead of binary on/off (function() { - function debugPrint(message) { - //print(message); - } - Script.include("../../libraries/utils.js"); var _this; @@ -30,11 +25,11 @@ // our this object, so we can access it in cases where we're called without a this (like in the case of various global signals) Flashlight = function() { _this = this; - _this._hasSpotlight = false; - _this._spotlight = null; }; - var DISABLE_LIGHT_THRESHOLD = 0.5; + //if the trigger value goes below this while held, the flashlight will turn off. if it goes above, it will + var DISABLE_LIGHT_THRESHOLD = 0.7; + // These constants define the Spotlight position and orientation relative to the model var MODEL_LIGHT_POSITION = { x: 0, @@ -47,101 +42,131 @@ z: 0 }); - // Evaluate the world light entity position and orientation from the model ones + var GLOW_LIGHT_POSITION = { + x: 0, + y: -0.1, + z: 0 + } + + // Evaluate the world light entity positions and orientations from the model ones function evalLightWorldTransform(modelPos, modelRot) { + return { p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) }; }; + function glowLightWorldTransform(modelPos, modelRot) { + return { + p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, GLOW_LIGHT_POSITION)), + q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) + }; + }; + + Flashlight.prototype = { lightOn: false, + hand: null, + whichHand: null, + hasSpotlight: false, + spotlight: null, + setRightHand: function() { + this.hand = 'RIGHT'; + }, + setLeftHand: function() { + this.hand = 'LEFT'; + }, + startNearGrab: function() { + if (!_this.hasSpotlight) { + //this light casts the beam + this.spotlight = Entities.addEntity({ + type: "Light", + isSpotlight: true, + dimensions: { + x: 2, + y: 2, + z: 20 + }, + color: { + red: 255, + green: 255, + blue: 255 + }, + intensity: 2, + exponent: 0.3, + cutoff: 20 + }); - // update() will be called regulary, because we've hooked the update signal in our preload() function - // we will check out userData for the grabData. In the case of the hydraGrab script, it will tell us - // if we're currently being grabbed and if the person grabbing us is the current interfaces avatar. - // we will watch this for state changes and print out if we're being grabbed or released when it changes. - update: function() { - var GRAB_USER_DATA_KEY = "grabKey"; + //this light creates the effect of a bulb at the end of the flashlight + this.glowLight = Entities.addEntity({ + type: "Light", + dimensions: { + x: 0.25, + y: 0.25, + z: 0.25 + }, + isSpotlight: false, + color: { + red: 255, + green: 255, + blue: 255 + }, + exponent: 0, + cutoff: 90, // in degrees + }); - // because the update() signal doesn't have a valid this, we need to use our memorized _this to access our entityID - var entityID = _this.entityID; + this.hasSpotlight = true; - // we want to assume that if there is no grab data, then we are not being grabbed - var defaultGrabData = { - activated: false, - avatarId: null - }; - - // this handy function getEntityCustomData() is available in utils.js and it will return just the specific section - // of user data we asked for. If it's not available it returns our default data. - var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, defaultGrabData); - - - // if the grabData says we're being grabbed, and the owner ID is our session, then we are being grabbed by this interface - if (grabData.activated && grabData.avatarId == MyAvatar.sessionUUID) { - - // remember we're being grabbed so we can detect being released - _this.beingGrabbed = true; - - var modelProperties = Entities.getEntityProperties(entityID); - var lightTransform = evalLightWorldTransform(modelProperties.position, modelProperties.rotation); - - // Create the spot light driven by this model if we don;t have one yet - // Or make sure to keep it's position in sync - if (!_this._hasSpotlight) { - - _this._spotlight = Entities.addEntity({ - type: "Light", - position: lightTransform.p, - rotation: lightTransform.q, - isSpotlight: true, - dimensions: { - x: 2, - y: 2, - z: 20 - }, - color: { - red: 255, - green: 255, - blue: 255 - }, - intensity: 2, - exponent: 0.3, - cutoff: 20 - }); - _this._hasSpotlight = true; - - - debugPrint("Flashlight:: creating a spotlight"); - } else { - // Updating the spotlight - Entities.editEntity(_this._spotlight, { - position: lightTransform.p, - rotation: lightTransform.q - }); - _this.changeLightWithTriggerPressure(); - debugPrint("Flashlight:: updating the spotlight"); - } - - debugPrint("I'm being grabbed..."); - - } else if (_this.beingGrabbed) { - - if (_this._hasSpotlight) { - Entities.deleteEntity(_this._spotlight); - debugPrint("Destroying flashlight spotlight..."); - } - _this._hasSpotlight = false; - _this._spotlight = null; - - // if we are not being grabbed, and we previously were, then we were just released, remember that - // and print out a message - _this.beingGrabbed = false; - debugPrint("I'm was released..."); } + + }, + setWhichHand: function() { + this.whichHand = this.hand; + }, + continueNearGrab: function() { + if (this.whichHand === null) { + //only set the active hand once -- if we always read the current hand, our 'holding' hand will get overwritten + this.setWhichHand(); + } else { + this.updateLightPositions(); + this.changeLightWithTriggerPressure(this.whichHand); + } + }, + releaseGrab: function() { + //delete the lights and reset state + if (this.hasSpotlight) { + Entities.deleteEntity(this.spotlight); + Entities.deleteEntity(this.glowLight); + this.hasSpotlight = false; + this.glowLight = null; + this.spotlight = null; + this.whichHand = null; + + } + }, + updateLightPositions: function() { + var modelProperties = Entities.getEntityProperties(this.entityID, ['position', 'rotation']); + + //move the two lights along the vectors we set above + var lightTransform = evalLightWorldTransform(modelProperties.position, modelProperties.rotation); + var glowLightTransform = glowLightWorldTransform(modelProperties.position, modelProperties.rotation); + + + //move them with the entity model + Entities.editEntity(this.spotlight, { + position: lightTransform.p, + rotation: lightTransform.q, + }) + + + Entities.editEntity(this.glowLight, { + position: glowLightTransform.p, + rotation: glowLightTransform.q, + }) + + }, changeLightWithTriggerPressure: function(flashLightHand) { @@ -156,33 +181,34 @@ } else if (this.triggerValue >= DISABLE_LIGHT_THRESHOLD && this.lightOn === false) { this.turnLightOn(); } - - return triggerValue + return }, turnLightOff: function() { - Entities.editEntity(_this._spotlight, { + print('turn light off') + Entities.editEntity(this.spotlight, { + intensity: 0 + }); + Entities.editEntity(this.glowLight, { intensity: 0 }); this.lightOn = false }, turnLightOn: function() { - Entities.editEntity(_this._spotlight, { + print('turn light on') + Entities.editEntity(this.glowLight, { + intensity: 2 + }); + Entities.editEntity(this.spotlight, { intensity: 2 }); this.lightOn = true }, - // preload() will be called when the entity has become visible (or known) to the interface // it gives us a chance to set our local JavaScript object up. In this case it means: // * remembering our entityID, so we can access it in cases where we're called without an entityID // * connecting to the update signal so we can check our grabbed state preload: function(entityID) { - _this.entityID = entityID; - - var modelProperties = Entities.getEntityProperties(entityID); - - - + this.entityID = entityID; Script.update.connect(this.update); }, @@ -191,11 +217,15 @@ // to the update signal unload: function(entityID) { - if (_this._hasSpotlight) { - Entities.deleteEntity(_this._spotlight); + if (this.hasSpotlight) { + Entities.deleteEntity(this.spotlight); + Entities.deleteEntity(this.glowLight); + this.hasSpotlight = false; + this.glowLight = null; + this.spotlight = null; + this.whichHand = null; } - _this._hasSpotlight = false; - _this._spotlight = null; + Script.update.disconnect(this.update); }, From d79c1bec953e0b1f930b7adf158c86b0a895141d Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Mon, 21 Sep 2015 16:13:24 -0700 Subject: [PATCH 09/19] remove query string --- examples/toys/flashlight/createFlashlight.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/toys/flashlight/createFlashlight.js b/examples/toys/flashlight/createFlashlight.js index ad089e001a..f0d31b6934 100644 --- a/examples/toys/flashlight/createFlashlight.js +++ b/examples/toys/flashlight/createFlashlight.js @@ -15,7 +15,7 @@ Script.include("https://hifi-public.s3.amazonaws.com/scripts/utilities.js"); -var scriptURL = Script.resolvePath('flashlight.js?12322'); +var scriptURL = Script.resolvePath('flashlight.js'); var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx"; From e6776ef5ebe41cbe121ce672faa7f9197ba0d83c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 21 Sep 2015 17:29:39 -0700 Subject: [PATCH 10/19] split AnimIK::evaluate() into sub-functions also IK targets now in model-frame instead of root-frame --- .../animation/src/AnimInverseKinematics.cpp | 306 +++++++++--------- .../animation/src/AnimInverseKinematics.h | 10 +- libraries/animation/src/AnimNode.h | 17 + libraries/animation/src/Rig.cpp | 18 +- 4 files changed, 186 insertions(+), 165 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 6af58e89a1..1ccf984815 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -82,22 +82,8 @@ static int findRootJointInSkeleton(AnimSkeleton::ConstPointer skeleton, int inde return rootIndex; } -struct IKTarget { - AnimPose pose; - int index; - int rootIndex; -}; - -//virtual -const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) { - - // NOTE: we assume that _relativePoses are up to date (e.g. loadPoses() was just called) - if (_relativePoses.empty()) { - return _relativePoses; - } - - // build a list of targets from _targetVarVec - std::vector targets; +void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector& targets) { + // build a list of valid targets from _targetVarVec and animVars bool removeUnfoundJoints = false; for (auto& targetVar : _targetVarVec) { if (targetVar.jointIndex == -1) { @@ -141,154 +127,160 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar } } } +} - if (targets.empty()) { - // no IK targets but still need to enforce constraints - std::map::iterator constraintItr = _constraints.begin(); - while (constraintItr != _constraints.end()) { - int index = constraintItr->first; - glm::quat rotation = _relativePoses[index].rot; - constraintItr->second->apply(rotation); - _relativePoses[index].rot = rotation; - ++constraintItr; - } - } else { - // clear the accumulators before we start the IK solver - for (auto& accumulatorPair: _accumulators) { - accumulatorPair.second.clear(); - } +void AnimInverseKinematics::solveWithCyclicCoordinateDescent(std::vector& targets) { + // compute absolute poses that correspond to relative target poses + AnimPoseVec absolutePoses; + computeAbsolutePoses(absolutePoses); - // compute absolute poses that correspond to relative target poses - AnimPoseVec absolutePoses; - computeAbsolutePoses(absolutePoses); + // clear the accumulators before we start the IK solver + for (auto& accumulatorPair: _accumulators) { + accumulatorPair.second.clear(); + } - float largestError = 0.0f; - const float ACCEPTABLE_RELATIVE_ERROR = 1.0e-3f; - int numLoops = 0; - const int MAX_IK_LOOPS = 16; - const quint64 MAX_IK_TIME = 10 * USECS_PER_MSEC; - quint64 expiry = usecTimestampNow() + MAX_IK_TIME; - do { - largestError = 0.0f; - int lowestMovedIndex = _relativePoses.size(); - for (auto& target: targets) { - int tipIndex = target.index; - AnimPose targetPose = target.pose; - int rootIndex = target.rootIndex; - if (rootIndex != -1) { - // transform targetPose into skeleton's absolute frame - AnimPose& rootPose = _relativePoses[rootIndex]; - targetPose.trans = rootPose.trans + rootPose.rot * targetPose.trans; - targetPose.rot = rootPose.rot * targetPose.rot; - } - - glm::vec3 tip = absolutePoses[tipIndex].trans; - float error = glm::length(targetPose.trans - tip); - - // descend toward root, pivoting each joint to get tip closer to target - int pivotIndex = _skeleton->getParentIndex(tipIndex); - while (pivotIndex != -1 && error > ACCEPTABLE_RELATIVE_ERROR) { - // compute the two lines that should be aligned - glm::vec3 jointPosition = absolutePoses[pivotIndex].trans; - glm::vec3 leverArm = tip - jointPosition; - glm::vec3 targetLine = targetPose.trans - jointPosition; - - // compute the axis of the rotation that would align them - glm::vec3 axis = glm::cross(leverArm, targetLine); - float axisLength = glm::length(axis); - if (axisLength > EPSILON) { - // compute deltaRotation for alignment (brings tip closer to target) - axis /= axisLength; - float angle = acosf(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine))); - - // NOTE: even when axisLength is not zero (e.g. lever-arm and pivot-arm are not quite aligned) it is - // still possible for the angle to be zero so we also check that to avoid unnecessary calculations. - if (angle > EPSILON) { - // reduce angle by half: slows convergence but adds stability to IK solution - angle = 0.5f * angle; - glm::quat deltaRotation = glm::angleAxis(angle, axis); - - int parentIndex = _skeleton->getParentIndex(pivotIndex); - if (parentIndex == -1) { - // TODO? apply constraints to root? - // TODO? harvest the root's transform as movement of entire skeleton? - } else { - // compute joint's new parent-relative rotation - // Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q - glm::quat newRot = glm::normalize(glm::inverse( - absolutePoses[parentIndex].rot) * - deltaRotation * - absolutePoses[pivotIndex].rot); - RotationConstraint* constraint = getConstraint(pivotIndex); - if (constraint) { - bool constrained = constraint->apply(newRot); - if (constrained) { - // the constraint will modify the movement of the tip so we have to compute the modified - // model-frame deltaRotation - // Q' = Qp^ * dQ * Q --> dQ = Qp * Q' * Q^ - deltaRotation = absolutePoses[parentIndex].rot * - newRot * - glm::inverse(absolutePoses[pivotIndex].rot); - } - } - // store the rotation change in the accumulator - _accumulators[pivotIndex].add(newRot); - } - // this joint has been changed so we check to see if it has the lowest index - if (pivotIndex < lowestMovedIndex) { - lowestMovedIndex = pivotIndex; - } - - // keep track of tip's new position as we descend towards root - tip = jointPosition + deltaRotation * leverArm; - error = glm::length(targetPose.trans - tip); - } - } - pivotIndex = _skeleton->getParentIndex(pivotIndex); - } - if (largestError < error) { - largestError = error; - } - } - ++numLoops; - - // harvest accumulated rotations and apply the average - for (auto& accumulatorPair: _accumulators) { - RotationAccumulator& accumulator = accumulatorPair.second; - if (accumulator.size() > 0) { - _relativePoses[accumulatorPair.first].rot = accumulator.getAverage(); - accumulator.clear(); - } - } - - // only update the absolutePoses that need it: those between lowestMovedIndex and _maxTargetIndex - for (int i = lowestMovedIndex; i <= _maxTargetIndex; ++i) { - int parentIndex = _skeleton->getParentIndex(i); - if (parentIndex != -1) { - absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i]; - } - } - } while (largestError > ACCEPTABLE_RELATIVE_ERROR && numLoops < MAX_IK_LOOPS && usecTimestampNow() < expiry); - - // finally set the relative rotation of each tip to agree with absolute target rotation + float largestError = 0.0f; + const float ACCEPTABLE_RELATIVE_ERROR = 1.0e-3f; + int numLoops = 0; + const int MAX_IK_LOOPS = 16; + const quint64 MAX_IK_TIME = 10 * USECS_PER_MSEC; + quint64 expiry = usecTimestampNow() + MAX_IK_TIME; + do { + largestError = 0.0f; + int lowestMovedIndex = _relativePoses.size(); for (auto& target: targets) { int tipIndex = target.index; - int parentIndex = _skeleton->getParentIndex(tipIndex); - if (parentIndex != -1) { - AnimPose targetPose = target.pose; - // compute tip's new parent-relative rotation - // Q = Qp * q --> q' = Qp^ * Q - glm::quat newRelativeRotation = glm::inverse(absolutePoses[parentIndex].rot) * targetPose.rot; - RotationConstraint* constraint = getConstraint(tipIndex); - if (constraint) { - constraint->apply(newRelativeRotation); - // TODO: ATM the final rotation target just fails but we need to provide - // feedback to the IK system so that it can adjust the bones up the skeleton - // to help this rotation target get met. + AnimPose targetPose = target.pose; + + glm::vec3 tip = absolutePoses[tipIndex].trans; + float error = glm::length(targetPose.trans - tip); + + // descend toward root, pivoting each joint to get tip closer to target + int pivotIndex = _skeleton->getParentIndex(tipIndex); + while (pivotIndex != -1 && error > ACCEPTABLE_RELATIVE_ERROR) { + // compute the two lines that should be aligned + glm::vec3 jointPosition = absolutePoses[pivotIndex].trans; + glm::vec3 leverArm = tip - jointPosition; + glm::vec3 targetLine = targetPose.trans - jointPosition; + + // compute the axis of the rotation that would align them + glm::vec3 axis = glm::cross(leverArm, targetLine); + float axisLength = glm::length(axis); + if (axisLength > EPSILON) { + // compute deltaRotation for alignment (brings tip closer to target) + axis /= axisLength; + float angle = acosf(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine))); + + // NOTE: even when axisLength is not zero (e.g. lever-arm and pivot-arm are not quite aligned) it is + // still possible for the angle to be zero so we also check that to avoid unnecessary calculations. + if (angle > EPSILON) { + // reduce angle by half: slows convergence but adds stability to IK solution + angle = 0.5f * angle; + glm::quat deltaRotation = glm::angleAxis(angle, axis); + + int parentIndex = _skeleton->getParentIndex(pivotIndex); + if (parentIndex == -1) { + // TODO? apply constraints to root? + // TODO? harvest the root's transform as movement of entire skeleton? + } else { + // compute joint's new parent-relative rotation + // Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q + glm::quat newRot = glm::normalize(glm::inverse( + absolutePoses[parentIndex].rot) * + deltaRotation * + absolutePoses[pivotIndex].rot); + RotationConstraint* constraint = getConstraint(pivotIndex); + if (constraint) { + bool constrained = constraint->apply(newRot); + if (constrained) { + // the constraint will modify the movement of the tip so we have to compute the modified + // model-frame deltaRotation + // Q' = Qp^ * dQ * Q --> dQ = Qp * Q' * Q^ + deltaRotation = absolutePoses[parentIndex].rot * + newRot * + glm::inverse(absolutePoses[pivotIndex].rot); + } + } + // store the rotation change in the accumulator + _accumulators[pivotIndex].add(newRot); + } + // this joint has been changed so we check to see if it has the lowest index + if (pivotIndex < lowestMovedIndex) { + lowestMovedIndex = pivotIndex; + } + + // keep track of tip's new position as we descend towards root + tip = jointPosition + deltaRotation * leverArm; + error = glm::length(targetPose.trans - tip); + } } - _relativePoses[tipIndex].rot = newRelativeRotation; - absolutePoses[tipIndex].rot = targetPose.rot; + pivotIndex = _skeleton->getParentIndex(pivotIndex); } + if (largestError < error) { + largestError = error; + } + } + ++numLoops; + + // harvest accumulated rotations and apply the average + for (auto& accumulatorPair: _accumulators) { + RotationAccumulator& accumulator = accumulatorPair.second; + if (accumulator.size() > 0) { + _relativePoses[accumulatorPair.first].rot = accumulator.getAverage(); + accumulator.clear(); + } + } + + // only update the absolutePoses that need it: those between lowestMovedIndex and _maxTargetIndex + for (int i = lowestMovedIndex; i <= _maxTargetIndex; ++i) { + int parentIndex = _skeleton->getParentIndex(i); + if (parentIndex != -1) { + absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i]; + } + } + } while (largestError > ACCEPTABLE_RELATIVE_ERROR && numLoops < MAX_IK_LOOPS && usecTimestampNow() < expiry); + + // finally set the relative rotation of each tip to agree with absolute target rotation + for (auto& target: targets) { + int tipIndex = target.index; + int parentIndex = _skeleton->getParentIndex(tipIndex); + if (parentIndex != -1) { + AnimPose targetPose = target.pose; + // compute tip's new parent-relative rotation + // Q = Qp * q --> q' = Qp^ * Q + glm::quat newRelativeRotation = glm::inverse(absolutePoses[parentIndex].rot) * targetPose.rot; + RotationConstraint* constraint = getConstraint(tipIndex); + if (constraint) { + constraint->apply(newRelativeRotation); + // TODO: ATM the final rotation target just fails but we need to provide + // feedback to the IK system so that it can adjust the bones up the skeleton + // to help this rotation target get met. + } + _relativePoses[tipIndex].rot = newRelativeRotation; + absolutePoses[tipIndex].rot = targetPose.rot; + } + } +} + +//virtual +const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) { + if (!_relativePoses.empty()) { + // build a list of targets from _targetVarVec + std::vector targets; + computeTargets(animVars, targets); + + if (targets.empty()) { + // no IK targets but still need to enforce constraints + std::map::iterator constraintItr = _constraints.begin(); + while (constraintItr != _constraints.end()) { + int index = constraintItr->first; + glm::quat rotation = _relativePoses[index].rot; + constraintItr->second->apply(rotation); + _relativePoses[index].rot = rotation; + ++constraintItr; + } + } else { + solveWithCyclicCoordinateDescent(targets); } } return _relativePoses; diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index c4bda1be89..f2073c01b8 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -37,6 +37,14 @@ public: virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override; protected: + struct IKTarget { + AnimPose pose; + int index; + int rootIndex; + }; + + void computeTargets(const AnimVariantMap& animVars, std::vector& targets); + void solveWithCyclicCoordinateDescent(std::vector& targets); virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton); // for AnimDebugDraw rendering @@ -64,7 +72,7 @@ protected: }; std::map _constraints; - std::map _accumulators; + std::map _accumulators; // class-member to exploit temporal coherency std::vector _targetVarVec; AnimPoseVec _defaultRelativePoses; // poses of the relaxed state AnimPoseVec _relativePoses; // current relative poses diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 9325ef3835..b6f9987f33 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -87,6 +87,23 @@ public: return evaluate(animVars, dt, triggersOut); } + const AnimPose getRootPose(int jointIndex) const { + AnimPose pose = AnimPose::identity; + if (_skeleton && jointIndex != -1) { + const AnimPoseVec& poses = getPosesInternal(); + int numJoints = (int)(poses.size()); + if (jointIndex < numJoints) { + int parentIndex = _skeleton->getParentIndex(jointIndex); + while (parentIndex != -1 && parentIndex < numJoints) { + jointIndex = parentIndex; + parentIndex = _skeleton->getParentIndex(jointIndex); + } + pose = poses[jointIndex]; + } + } + return pose; + } + protected: void setCurrentFrame(float frame) { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b0ffd081c2..29d0bc011b 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -986,7 +986,7 @@ void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, floa void Rig::updateNeckJoint(int index, const HeadParameters& params) { if (index >= 0 && _jointStates[index].getParentIndex() >= 0) { - if (_enableAnimGraph && _animSkeleton) { + if (_enableAnimGraph && _animSkeleton && _animNode) { // the params.localHeadOrientation is composed incorrectly, so re-compose it correctly from pitch, yaw and roll. glm::quat realLocalHeadOrientation = (glm::angleAxis(glm::radians(-params.localHeadRoll), Z_AXIS) * glm::angleAxis(glm::radians(params.localHeadYaw), Y_AXIS) * @@ -995,7 +995,9 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) { // There's a theory that when not in hmd, we should _animVars.unset("headPosition"). // However, until that works well, let's always request head be positioned where requested by hmd, camera, or default. - _animVars.set("headPosition", params.localHeadPosition); + int headIndex = _animSkeleton->nameToJointIndex("Head"); + AnimPose rootPose = _animNode->getRootPose(headIndex); + _animVars.set("headPosition", rootPose.trans + rootPose.rot * params.localHeadPosition); } else if (!_enableAnimGraph) { auto& state = _jointStates[index]; @@ -1044,20 +1046,22 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm void Rig::updateFromHandParameters(const HandParameters& params, float dt) { - if (_enableAnimGraph && _animSkeleton) { + if (_enableAnimGraph && _animSkeleton && _animNode) { // TODO: figure out how to obtain the yFlip from where it is actually stored glm::quat yFlipHACK = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); + int leftHandIndex = _animSkeleton->nameToJointIndex("LeftHand"); + AnimPose rootPose = _animNode->getRootPose(leftHandIndex); if (params.isLeftEnabled) { - _animVars.set("leftHandPosition", yFlipHACK * params.leftPosition); - _animVars.set("leftHandRotation", yFlipHACK * params.leftOrientation); + _animVars.set("leftHandPosition", rootPose.trans + rootPose.rot * yFlipHACK * params.leftPosition); + _animVars.set("leftHandRotation", rootPose.rot * yFlipHACK * params.leftOrientation); } else { _animVars.unset("leftHandPosition"); _animVars.unset("leftHandRotation"); } if (params.isRightEnabled) { - _animVars.set("rightHandPosition", yFlipHACK * params.rightPosition); - _animVars.set("rightHandRotation", yFlipHACK * params.rightOrientation); + _animVars.set("rightHandPosition", rootPose.trans + rootPose.rot * yFlipHACK * params.rightPosition); + _animVars.set("rightHandRotation", rootPose.rot * yFlipHACK * params.rightOrientation); } else { _animVars.unset("rightHandPosition"); _animVars.unset("rightHandRotation"); From bf22d5a942d5f71c0a745a5d8ae29d2a8e036787 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Mon, 21 Sep 2015 17:43:05 -0700 Subject: [PATCH 11/19] dont delete wand at cleanup from createWand.js --- examples/toys/flashlight/createFlashlight.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/toys/flashlight/createFlashlight.js b/examples/toys/flashlight/createFlashlight.js index f0d31b6934..65cafbcdaa 100644 --- a/examples/toys/flashlight/createFlashlight.js +++ b/examples/toys/flashlight/createFlashlight.js @@ -41,7 +41,8 @@ var flashlight = Entities.addEntity({ function cleanup() { - Entities.deleteEntity(flashlight); + //commenting out the line below makes this persistent. to delete at cleanup, uncomment + //Entities.deleteEntity(flashlight); } From 3869887610e10e69ecfb7d2368448905f9b4776b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 21 Sep 2015 17:53:59 -0700 Subject: [PATCH 12/19] splitting AnimNode implementation into two files --- libraries/animation/src/AnimNode.cpp | 54 ++++++++++++++++++++++++++++ libraries/animation/src/AnimNode.h | 48 ++++--------------------- 2 files changed, 61 insertions(+), 41 deletions(-) create mode 100644 libraries/animation/src/AnimNode.cpp diff --git a/libraries/animation/src/AnimNode.cpp b/libraries/animation/src/AnimNode.cpp new file mode 100644 index 0000000000..02d0e1b283 --- /dev/null +++ b/libraries/animation/src/AnimNode.cpp @@ -0,0 +1,54 @@ +// +// AnimNode.cpp +// +// Created by Anthony J. Thibault on 9/2/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimNode.h" + +void AnimNode::removeChild(AnimNode::Pointer child) { + auto iter = std::find(_children.begin(), _children.end(), child); + if (iter != _children.end()) { + _children.erase(iter); + } +} + +AnimNode::Pointer AnimNode::getChild(int i) const { + assert(i >= 0 && i < (int)_children.size()); + return _children[i]; +} + +void AnimNode::setSkeleton(const AnimSkeleton::Pointer skeleton) { + setSkeletonInternal(skeleton); + for (auto&& child : _children) { + child->setSkeleton(skeleton); + } +} + +const AnimPose AnimNode::getRootPose(int jointIndex) const { + AnimPose pose = AnimPose::identity; + if (_skeleton && jointIndex != -1) { + const AnimPoseVec& poses = getPosesInternal(); + int numJoints = (int)(poses.size()); + if (jointIndex < numJoints) { + int parentIndex = _skeleton->getParentIndex(jointIndex); + while (parentIndex != -1 && parentIndex < numJoints) { + jointIndex = parentIndex; + parentIndex = _skeleton->getParentIndex(jointIndex); + } + pose = poses[jointIndex]; + } + } + return pose; +} + +void AnimNode::setCurrentFrame(float frame) { + setCurrentFrameInternal(frame); + for (auto&& child : _children) { + child->setCurrentFrameInternal(frame); + } +} diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index b6f9987f33..d5da552a0f 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -60,25 +60,13 @@ public: // hierarchy accessors void addChild(Pointer child) { _children.push_back(child); } - void removeChild(Pointer child) { - auto iter = std::find(_children.begin(), _children.end(), child); - if (iter != _children.end()) { - _children.erase(iter); - } - } - Pointer getChild(int i) const { - assert(i >= 0 && i < (int)_children.size()); - return _children[i]; - } + void removeChild(Pointer child); + + Pointer getChild(int i) const; int getChildCount() const { return (int)_children.size(); } // pair this AnimNode graph with a skeleton. - void setSkeleton(const AnimSkeleton::Pointer skeleton) { - setSkeletonInternal(skeleton); - for (auto&& child : _children) { - child->setSkeleton(skeleton); - } - } + void setSkeleton(const AnimSkeleton::Pointer skeleton); AnimSkeleton::ConstPointer getSkeleton() const { return _skeleton; } @@ -87,36 +75,14 @@ public: return evaluate(animVars, dt, triggersOut); } - const AnimPose getRootPose(int jointIndex) const { - AnimPose pose = AnimPose::identity; - if (_skeleton && jointIndex != -1) { - const AnimPoseVec& poses = getPosesInternal(); - int numJoints = (int)(poses.size()); - if (jointIndex < numJoints) { - int parentIndex = _skeleton->getParentIndex(jointIndex); - while (parentIndex != -1 && parentIndex < numJoints) { - jointIndex = parentIndex; - parentIndex = _skeleton->getParentIndex(jointIndex); - } - pose = poses[jointIndex]; - } - } - return pose; - } + const AnimPose getRootPose(int jointIndex) const; protected: - void setCurrentFrame(float frame) { - setCurrentFrameInternal(frame); - for (auto&& child : _children) { - child->setCurrentFrameInternal(frame); - } - } + void setCurrentFrame(float frame); virtual void setCurrentFrameInternal(float frame) {} - virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { - _skeleton = skeleton; - } + virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { _skeleton = skeleton; } // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const = 0; From a23a90bf5f99dbf4b1d7c2e4ed108b97b667e4a8 Mon Sep 17 00:00:00 2001 From: BOB LONG Date: Mon, 21 Sep 2015 19:11:13 -0700 Subject: [PATCH 13/19] Code simplification Simplify the code a bit as suggested: 1) Use unsigned int instead of signed int, so we can avoid checking the negative case 2) Merge two lines into a single line so we can inline the implementation Correct the js sample file header. testing done: - Build locally - Pass -1 as index from js and the code still can correctly handle the input. --- examples/faceBlendCoefficients.js | 4 ++-- libraries/render-utils/src/Model.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/faceBlendCoefficients.js b/examples/faceBlendCoefficients.js index 56fcadca9d..6756be548f 100644 --- a/examples/faceBlendCoefficients.js +++ b/examples/faceBlendCoefficients.js @@ -1,10 +1,10 @@ // -// coefficients.js +// faceBlendCoefficients.js // // version 2.0 // // Created by Bob Long, 9/14/2015 -// A simple panel that can display the blending coefficients of Avatar's face model. +// A simple panel that can select and display the blending coefficient of the Avatar's face model. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index d1aa2901c8..e8f158be08 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -192,9 +192,9 @@ public: void setCauterizeBoneSet(const std::unordered_set& boneSet) { _cauterizeBoneSet = boneSet; } int getBlendshapeCoefficientsNum() const { return _blendshapeCoefficients.size(); } - float getBlendshapeCoefficient(int index) const { - return index >= _blendshapeCoefficients.size() || index < 0 ? - 0.0f : _blendshapeCoefficients.at(index); } + float getBlendshapeCoefficient(unsigned int index) const { + return index >= _blendshapeCoefficients.size() ? 0.0f : _blendshapeCoefficients.at(index); + } protected: From f38bb77d0a9fc2bb46d717e0e86f7cf8e069c86b Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 22 Sep 2015 18:48:46 +0200 Subject: [PATCH 14/19] less calls to QHash --- interface/src/Application.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index 30f89d8421..2a5138638e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -292,7 +292,7 @@ public: NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; } QStringList getRunningScripts() { return _scriptEnginesHash.keys(); } - ScriptEngine* getScriptEngine(const QString& scriptHash) { return _scriptEnginesHash.contains(scriptHash) ? _scriptEnginesHash[scriptHash] : NULL; } + ScriptEngine* getScriptEngine(const QString& scriptHash) { return _scriptEnginesHash.value(scriptHash, NULL); } bool isLookingAtMyAvatar(AvatarSharedPointer avatar); From 8f1dde69cc1e98d98fc6ef237a09268f8e4cea5f Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 22 Sep 2015 10:10:29 -0700 Subject: [PATCH 15/19] Always keep targets, even when both position and rotation are unset. (Get from underpose.) Filtering these was necessary before when the underpose coordinate was wrong, but now that we have that working, there shouldn't be any need to filter. --- .../animation/src/AnimInverseKinematics.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 1ccf984815..57459abacb 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -99,15 +99,13 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: } } else { // TODO: get this done without a double-lookup of each var in animVars - if (animVars.hasKey(targetVar.positionVar) || animVars.hasKey(targetVar.rotationVar)) { - IKTarget target; - AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, _relativePoses); - target.pose.trans = animVars.lookup(targetVar.positionVar, defaultPose.trans); - target.pose.rot = animVars.lookup(targetVar.rotationVar, defaultPose.rot); - target.rootIndex = targetVar.rootIndex; - target.index = targetVar.jointIndex; - targets.push_back(target); - } + IKTarget target; + AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, _relativePoses); + target.pose.trans = animVars.lookup(targetVar.positionVar, defaultPose.trans); + target.pose.rot = animVars.lookup(targetVar.rotationVar, defaultPose.rot); + target.rootIndex = targetVar.rootIndex; + target.index = targetVar.jointIndex; + targets.push_back(target); } } From 7e52d38870d296ead5bf9b3053cfbcaf6686c0fa Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 22 Sep 2015 10:12:59 -0700 Subject: [PATCH 16/19] Don't include the root rot, because it seems that this is already accounted for in the head params. Restore the hmd conditional on setting head position. This had been removed when failing to pin it cause lean. I believe that lean was being caused by coordinate system issues that are now addressed by the above and Andrew's big cleanup. --- libraries/animation/src/Rig.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 29d0bc011b..a74560114a 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -993,11 +993,13 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) { glm::angleAxis(glm::radians(-params.localHeadPitch), X_AXIS)); _animVars.set("headRotation", realLocalHeadOrientation); - // There's a theory that when not in hmd, we should _animVars.unset("headPosition"). - // However, until that works well, let's always request head be positioned where requested by hmd, camera, or default. - int headIndex = _animSkeleton->nameToJointIndex("Head"); - AnimPose rootPose = _animNode->getRootPose(headIndex); - _animVars.set("headPosition", rootPose.trans + rootPose.rot * params.localHeadPosition); + if (params.isInHMD) { + int headIndex = _animSkeleton->nameToJointIndex("Head"); + AnimPose rootPose = _animNode->getRootPose(headIndex); + _animVars.set("headPosition", rootPose.trans + params.localHeadPosition); // rootPose.rot is handled in params?d + } else { + _animVars.unset("headPosition"); + } } else if (!_enableAnimGraph) { auto& state = _jointStates[index]; From 5bb908f081f9f41a01160205cfd8458eaacfc908 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 22 Sep 2015 12:34:47 -0700 Subject: [PATCH 17/19] remove dead code and fix warning --- libraries/model/src/model/Material.cpp | 12 ------------ libraries/model/src/model/Material.h | 2 -- 2 files changed, 14 deletions(-) diff --git a/libraries/model/src/model/Material.cpp b/libraries/model/src/model/Material.cpp index cf91836254..c2ff828af3 100755 --- a/libraries/model/src/model/Material.cpp +++ b/libraries/model/src/model/Material.cpp @@ -15,18 +15,6 @@ using namespace model; using namespace gpu; -float componentSRGBToLinear(float cs) { - if (cs > 0.04045) { - return pow(((cs + 0.055)/1.055), 2.4); - } else { - return cs / 12.92; - } -} - -glm::vec3 convertSRGBToLinear(const glm::vec3& srgb) { - return glm::vec3(componentSRGBToLinear(srgb.x), componentSRGBToLinear(srgb.y), componentSRGBToLinear(srgb.z)); -} - Material::Material() : _key(0), _schemaBuffer(), diff --git a/libraries/model/src/model/Material.h b/libraries/model/src/model/Material.h index 0e7388f722..b0725d9908 100755 --- a/libraries/model/src/model/Material.h +++ b/libraries/model/src/model/Material.h @@ -20,8 +20,6 @@ namespace model { -static glm::vec3 convertSRGBToLinear(const glm::vec3& srgb); - class TextureMap; typedef std::shared_ptr< TextureMap > TextureMapPointer; From 0ad238a5082a978e463d2b25acb28215a3f0e1d0 Mon Sep 17 00:00:00 2001 From: James Pollack Date: Tue, 22 Sep 2015 12:39:48 -0700 Subject: [PATCH 18/19] Enlarge flashlight --- examples/toys/flashlight/createFlashlight.js | 8 +++---- examples/toys/flashlight/flashlight.js | 24 ++++++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/examples/toys/flashlight/createFlashlight.js b/examples/toys/flashlight/createFlashlight.js index 65cafbcdaa..38907efa75 100644 --- a/examples/toys/flashlight/createFlashlight.js +++ b/examples/toys/flashlight/createFlashlight.js @@ -15,7 +15,7 @@ Script.include("https://hifi-public.s3.amazonaws.com/scripts/utilities.js"); -var scriptURL = Script.resolvePath('flashlight.js'); +var scriptURL = Script.resolvePath('flashlight.js?123123'); var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx"; @@ -30,9 +30,9 @@ var flashlight = Entities.addEntity({ modelURL: modelURL, position: center, dimensions: { - x: 0.04, - y: 0.15, - z: 0.04 + x: 0.08, + y: 0.30, + z: 0.08 }, collisionsWillMove: true, shapeType: 'box', diff --git a/examples/toys/flashlight/flashlight.js b/examples/toys/flashlight/flashlight.js index 7f2bd210da..f5929dcfc8 100644 --- a/examples/toys/flashlight/flashlight.js +++ b/examples/toys/flashlight/flashlight.js @@ -33,7 +33,7 @@ // These constants define the Spotlight position and orientation relative to the model var MODEL_LIGHT_POSITION = { x: 0, - y: 0, + y: -0.3, z: 0 }; var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { @@ -117,6 +117,21 @@ cutoff: 90, // in degrees }); + this.debugBox = Entities.addEntity({ + type: 'Box', + color: { + red: 255, + blue: 0, + green: 0 + }, + dimensions: { + x: 0.25, + y: 0.25, + z: 0.25 + }, + ignoreForCollisions:true + }) + this.hasSpotlight = true; } @@ -166,6 +181,10 @@ rotation: glowLightTransform.q, }) + // Entities.editEntity(this.debugBox, { + // position: lightTransform.p, + // rotation: lightTransform.q, + // }) }, changeLightWithTriggerPressure: function(flashLightHand) { @@ -184,7 +203,6 @@ return }, turnLightOff: function() { - print('turn light off') Entities.editEntity(this.spotlight, { intensity: 0 }); @@ -194,7 +212,6 @@ this.lightOn = false }, turnLightOn: function() { - print('turn light on') Entities.editEntity(this.glowLight, { intensity: 2 }); @@ -227,7 +244,6 @@ } - Script.update.disconnect(this.update); }, }; From e5c42c76a23e2242e01cf5117d3ffc14256a5b89 Mon Sep 17 00:00:00 2001 From: James Pollack Date: Tue, 22 Sep 2015 12:41:00 -0700 Subject: [PATCH 19/19] Remove update loop refs --- examples/toys/flashlight/flashlight.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/toys/flashlight/flashlight.js b/examples/toys/flashlight/flashlight.js index f5929dcfc8..0465266b1c 100644 --- a/examples/toys/flashlight/flashlight.js +++ b/examples/toys/flashlight/flashlight.js @@ -223,15 +223,12 @@ // preload() will be called when the entity has become visible (or known) to the interface // it gives us a chance to set our local JavaScript object up. In this case it means: // * remembering our entityID, so we can access it in cases where we're called without an entityID - // * connecting to the update signal so we can check our grabbed state preload: function(entityID) { this.entityID = entityID; - Script.update.connect(this.update); }, // unload() will be called when our entity is no longer available. It may be because we were deleted, - // or because we've left the domain or quit the application. In all cases we want to unhook our connection - // to the update signal + // or because we've left the domain or quit the application. unload: function(entityID) { if (this.hasSpotlight) {