From 6d4e4af0a5677afa41afdabf68b78334c0804f04 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 24 Sep 2015 22:02:22 +0200 Subject: [PATCH 1/6] ability to see Stats in JavaScript + example script which outputs the stats to the console/logviewer --- examples/example/misc/statsExample.js | 67 +++++++++++++++++++++++++++ interface/src/Application.cpp | 1 + interface/src/ui/Stats.cpp | 24 +++++----- interface/src/ui/Stats.h | 6 ++- 4 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 examples/example/misc/statsExample.js diff --git a/examples/example/misc/statsExample.js b/examples/example/misc/statsExample.js new file mode 100644 index 0000000000..3c677af96a --- /dev/null +++ b/examples/example/misc/statsExample.js @@ -0,0 +1,67 @@ +// +// statsExample.js +// examples/example/misc +// +// Created by Thijs Wenker on 24 Sept 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Prints the stats 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 +// + +// The stats to be displayed +var stats = [ + 'serverCount', + 'framerate', // a.k.a. FPS + 'simrate', + 'avatarSimrate', + 'avatarCount', + 'packetInCount', + 'packetOutCount', + 'mbpsIn', + 'mbpsOut', + 'audioPing', + 'avatarPing', + 'entitiesPing', + 'assetPing', + 'velocity', + 'yaw', + 'avatarMixerKbps', + 'avatarMixerPps', + 'audioMixerKbps', + 'audioMixerPps', + 'downloads', + 'downloadsPending', + 'triangles', + 'quads', + 'materialSwitches', + 'meshOpaque', + 'meshTranslucent', + 'opaqueConsidered', + 'opaqueOutOfView', + 'opaqueTooSmall', + 'translucentConsidered', + 'translucentOutOfView', + 'translucentTooSmall', + 'sendingMode', + 'packetStats', + 'lodStatus', + 'timingStats', + 'serverElements', + 'serverInternal', + 'serverLeaves', + 'localElements', + 'localInternal', + 'localLeaves' +]; + +// Force update the stats, in case the stats panel is invisible +Stats.forceUpdateStats(); + +// Loop through the stats and display them +for (var i in stats) { + var stat = stats[i]; + print(stat + " = " + Stats[stat]); +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c359275d8a..14dfaa3783 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4063,6 +4063,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("Stats", Stats::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 4dd552210c..ebce4f2b96 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -89,14 +89,14 @@ bool Stats::includeTimingRecord(const QString& name) { } -void Stats::updateStats() { - if (!Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { - if (isVisible()) { - setVisible(false); - } - return; - } else { - if (!isVisible()) { +void Stats::updateStats(bool force) { + if (!force) { + if (!Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { + if (isVisible()) { + setVisible(false); + } + return; + } else if (!isVisible()) { setVisible(true); } } @@ -161,7 +161,7 @@ void Stats::updateStats() { STAT_UPDATE(position, QVector3D(avatarPos.x, avatarPos.y, avatarPos.z)); STAT_UPDATE_FLOAT(velocity, glm::length(myAvatar->getVelocity()), 0.1f); STAT_UPDATE_FLOAT(yaw, myAvatar->getBodyYaw(), 0.1f); - if (_expanded) { + if (_expanded || force) { SharedNodePointer avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer); if (avatarMixer) { STAT_UPDATE(avatarMixerKbps, roundf( @@ -175,7 +175,7 @@ void Stats::updateStats() { STAT_UPDATE(avatarMixerPps, -1); } SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); - if (audioMixerNode) { + if (audioMixerNode || force) { STAT_UPDATE(audioMixerKbps, roundf( bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer) + bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AudioMixer))); @@ -230,7 +230,7 @@ void Stats::updateStats() { totalLeaves += stats.getTotalLeaves(); } } - if (_expanded) { + if (_expanded || force) { if (serverCount == 0) { sendingModeStream << "---"; } @@ -272,7 +272,7 @@ void Stats::updateStats() { STAT_UPDATE(serverElements, (int)totalNodes); STAT_UPDATE(localElements, (int)OctreeElement::getNodeCount()); - if (_expanded) { + if (_expanded || force) { STAT_UPDATE(serverInternal, (int)totalInternal); STAT_UPDATE(serverLeaves, (int)totalLeaves); // Local Voxels diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index e5c273926b..2e4c5e4dca 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -81,7 +81,7 @@ public: const QString& monospaceFont() { return _monospaceFont; } - void updateStats(); + void updateStats(bool force = false); bool isExpanded() { return _expanded; } bool isTimingExpanded() { return _timingExpanded; } @@ -93,6 +93,9 @@ public: } } +public slots: + void forceUpdateStats() { updateStats(true); } + signals: void expandedChanged(); void timingExpandedChanged(); @@ -149,3 +152,4 @@ private: }; #endif // hifi_Stats_h + From 20d784ba39dbefc5e3521f1a7aaea3b7b506bb66 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 24 Sep 2015 17:54:32 -0700 Subject: [PATCH 2/6] Threshold based walking while in HMD. This is a blend of the previous 'sitting' and 'standing' HMD modes. Basically, when you move your head within a small range (20cm) your avatar will lean appropriately, however when you cross that threshold your body will move underneath you, re-centering your head above the body. While this occurs the avatar should play the appropriate walking animations. --- .../defaultAvatar_full/avatar-animation.json | 4 +- interface/src/Application.cpp | 6 +- interface/src/Menu.cpp | 3 - interface/src/avatar/Head.cpp | 16 ++-- interface/src/avatar/MyAvatar.cpp | 74 ++++++++++++------- interface/src/avatar/MyAvatar.h | 9 +-- interface/src/avatar/SkeletonModel.cpp | 2 +- interface/src/ui/OverlayConductor.cpp | 6 +- 8 files changed, 64 insertions(+), 56 deletions(-) diff --git a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json index 550a95e980..03c5a103ff 100644 --- a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json +++ b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json @@ -559,7 +559,7 @@ "id": "strafeLeft", "type": "clip", "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx", + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_left.fbx", "startFrame": 0.0, "endFrame": 31.0, "timeScale": 1.0, @@ -571,7 +571,7 @@ "id": "strafeRight", "type": "clip", "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx", + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_right.fbx", "startFrame": 0.0, "endFrame": 31.0, "timeScale": 1.0, diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c68cf1cd4d..e41a375144 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -708,7 +708,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // Now that menu is initalized we can sync myAvatar with it's state. _myAvatar->updateMotionBehaviorFromMenu(); - _myAvatar->updateStandingHMDModeFromMenu(); // the 3Dconnexion device wants to be initiliazed after a window is displayed. ConnexionClient::getInstance().init(); @@ -1053,8 +1052,6 @@ void Application::paintGL() { auto displayPlugin = getActiveDisplayPlugin(); displayPlugin->preRender(); _offscreenContext->makeCurrent(); - // update the avatar with a fresh HMD pose - _myAvatar->updateFromHMDSensorMatrix(getHMDSensorPose()); auto lodManager = DependencyManager::get(); @@ -2883,6 +2880,9 @@ void Application::update(float deltaTime) { userInputMapper->getActionState(UserInputMapper::SHIFT), RIGHT_HAND_INDEX); } + // update the avatar with a fresh HMD pose + _myAvatar->updateFromHMDSensorMatrix(getHMDSensorPose(), deltaTime); + updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... updateCamera(deltaTime); // handle various camera tweaks like off axis projection diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index bd5287c256..5793510fb5 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -294,9 +294,6 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::StandingHMDSensorMode, 0, false, - avatar, SLOT(updateStandingHMDModeFromMenu())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::WorldAxes); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats); diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 96c55dfa93..d9a1b190d6 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -124,14 +124,12 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { _isEyeTrackerConnected = eyeTracker->isTracking(); } - if (!myAvatar->getStandingHMDSensorMode()) { - // Twist the upper body to follow the rotation of the head, but only do this with my avatar, - // since everyone else will see the full joint rotations for other people. - const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f; - const float BODY_FOLLOW_HEAD_FACTOR = 0.66f; - float currentTwist = getTorsoTwist(); - setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE); - } + // Twist the upper body to follow the rotation of the head, but only do this with my avatar, + // since everyone else will see the full joint rotations for other people. + const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f; + const float BODY_FOLLOW_HEAD_FACTOR = 0.66f; + float currentTwist = getTorsoTwist(); + setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE); } if (!(_isFaceTrackerConnected || billboard)) { @@ -392,7 +390,7 @@ glm::quat Head::getCameraOrientation() const { // always the same. if (qApp->getAvatarUpdater()->isHMDMode()) { MyAvatar* myAvatar = dynamic_cast(_owningAvatar); - if (myAvatar && myAvatar->getStandingHMDSensorMode()) { + if (myAvatar) { return glm::quat_cast(myAvatar->getSensorToWorldMatrix()) * myAvatar->getHMDSensorOrientation(); } else { return getOrientation(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c499f9f5bb..12da9b4070 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -104,7 +104,6 @@ MyAvatar::MyAvatar(RigPointer rig) : _hmdSensorPosition(), _bodySensorMatrix(), _sensorToWorldMatrix(), - _standingHMDSensorMode(false), _goToPending(false), _goToPosition(), _goToOrientation(), @@ -249,26 +248,54 @@ void MyAvatar::simulate(float deltaTime) { } glm::mat4 MyAvatar::getSensorToWorldMatrix() const { - if (getStandingHMDSensorMode()) { - return _sensorToWorldMatrix; - } else { - return createMatFromQuatAndPos(getWorldAlignedOrientation(), getDefaultEyePosition()); - } + return _sensorToWorldMatrix; } // best called at start of main loop just after we have a fresh hmd pose. // update internal body position from new hmd pose. -void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { +void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix, float deltaTime) { // update the sensorMatrices based on the new hmd pose _hmdSensorMatrix = hmdSensorMatrix; _hmdSensorPosition = extractTranslation(hmdSensorMatrix); _hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix); - _bodySensorMatrix = deriveBodyFromHMDSensor(); - if (getStandingHMDSensorMode()) { - // set the body position/orientation to reflect motion due to the head. - auto worldMat = _sensorToWorldMatrix * _bodySensorMatrix; - nextAttitude(extractTranslation(worldMat), glm::quat_cast(worldMat)); + auto newBodySensorMatrix = deriveBodyFromHMDSensor(); + glm::vec3 diff = extractTranslation(newBodySensorMatrix) - extractTranslation(_bodySensorMatrix); + if (!_straightingLean && glm::length(diff) > 0.2f) { + + // begin homing toward derived body position. + _straightingLean = true; + _straightingLeanAlpha = 0.0f; + + } else if (_straightingLean) { + + auto newBodySensorMatrix = deriveBodyFromHMDSensor(); + auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix; + glm::vec3 worldBodyPos = extractTranslation(worldBodyMatrix); + glm::quat worldBodyRot = glm::normalize(glm::quat_cast(worldBodyMatrix)); + + const float STRAIGHTING_LEAN_DURATION = 0.5f; + _straightingLeanAlpha += (1.0f / STRAIGHTING_LEAN_DURATION) * deltaTime; + + if (_straightingLeanAlpha >= 1.0f) { + _straightingLean = false; + nextAttitude(worldBodyPos, worldBodyRot); + _bodySensorMatrix = newBodySensorMatrix; + } else { + // interp position toward the desired pos + glm::vec3 pos = lerp(getPosition(), worldBodyPos, _straightingLeanAlpha); + glm::quat rot = glm::normalize(safeMix(getOrientation(), worldBodyRot, _straightingLeanAlpha)); + nextAttitude(pos, rot); + + // interp sensor matrix toward desired + glm::vec3 nextBodyPos = extractTranslation(newBodySensorMatrix); + glm::quat nextBodyRot = glm::normalize(glm::quat_cast(newBodySensorMatrix)); + glm::vec3 prevBodyPos = extractTranslation(_bodySensorMatrix); + glm::quat prevBodyRot = glm::normalize(glm::quat_cast(_bodySensorMatrix)); + pos = lerp(prevBodyPos, nextBodyPos, _straightingLeanAlpha); + rot = glm::normalize(safeMix(prevBodyRot, nextBodyRot, _straightingLeanAlpha)); + _bodySensorMatrix = createMatFromQuatAndPos(rot, pos); + } } } @@ -340,11 +367,9 @@ void MyAvatar::updateFromTrackers(float deltaTime) { Head* head = getHead(); if (inHmd || isPlaying()) { - if (!getStandingHMDSensorMode()) { - head->setDeltaPitch(estimatedRotation.x); - head->setDeltaYaw(estimatedRotation.y); - head->setDeltaRoll(estimatedRotation.z); - } + head->setDeltaPitch(estimatedRotation.x); + head->setDeltaYaw(estimatedRotation.y); + head->setDeltaRoll(estimatedRotation.z); } else { float magnifyFieldOfView = qApp->getFieldOfView() / _realWorldFieldOfView.get(); @@ -366,12 +391,10 @@ void MyAvatar::updateFromTrackers(float deltaTime) { relativePosition.x = -relativePosition.x; } - if (!(inHmd && getStandingHMDSensorMode())) { - head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)), - -MAX_LEAN, MAX_LEAN)); - head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)), - -MAX_LEAN, MAX_LEAN)); - } + head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)), + -MAX_LEAN, MAX_LEAN)); + head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)), + -MAX_LEAN, MAX_LEAN)); } @@ -1725,11 +1748,6 @@ void MyAvatar::updateMotionBehaviorFromMenu() { _characterController.setEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController)); } -void MyAvatar::updateStandingHMDModeFromMenu() { - Menu* menu = Menu::getInstance(); - _standingHMDSensorMode = menu->isOptionChecked(MenuOption::StandingHMDSensorMode); -} - //Renders sixense laser pointers for UI selection with controllers void MyAvatar::renderLaserPointers(gpu::Batch& batch) { const float PALM_TIP_ROD_RADIUS = 0.002f; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c58ca235f3..c1a6ada751 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -68,7 +68,7 @@ public: // best called at start of main loop just after we have a fresh hmd pose. // update internal body position from new hmd pose. - void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix); + void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix, float deltaTime); // best called at end of main loop, just before rendering. // update sensor to world matrix from current body position and hmd sensor. @@ -168,7 +168,6 @@ public: static const float ZOOM_MAX; static const float ZOOM_DEFAULT; - bool getStandingHMDSensorMode() const { return _standingHMDSensorMode; } void doUpdateBillboard(); void destroyAnimGraph(); @@ -194,7 +193,6 @@ public slots: void setThrust(glm::vec3 newThrust) { _thrust = newThrust; } void updateMotionBehaviorFromMenu(); - void updateStandingHMDModeFromMenu(); glm::vec3 getLeftPalmPosition(); glm::vec3 getLeftPalmVelocity(); @@ -345,8 +343,6 @@ private: // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers. glm::mat4 _sensorToWorldMatrix; - bool _standingHMDSensorMode; - bool _goToPending; glm::vec3 _goToPosition; glm::quat _goToOrientation; @@ -362,6 +358,9 @@ private: AudioListenerMode _audioListenerMode; glm::vec3 _customListenPosition; glm::quat _customListenOrientation; + + bool _straightingLean = false; + float _straightingLeanAlpha = 0.0f; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index c248b54dc7..703f3983ee 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -123,7 +123,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Rig::HeadParameters headParams; headParams.modelRotation = getRotation(); headParams.modelTranslation = getTranslation(); - headParams.enableLean = qApp->getAvatarUpdater()->isHMDMode() && !myAvatar->getStandingHMDSensorMode(); + headParams.enableLean = qApp->getAvatarUpdater()->isHMDMode(); headParams.leanSideways = head->getFinalLeanSideways(); headParams.leanForward = head->getFinalLeanForward(); headParams.torsoTwist = head->getTorsoTwist(); diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 21165b8c20..2e7838872a 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -69,11 +69,7 @@ void OverlayConductor::updateMode() { Mode newMode; if (qApp->isHMDMode()) { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - if (myAvatar->getStandingHMDSensorMode()) { - newMode = STANDING; - } else { - newMode = SITTING; - } + newMode = SITTING; } else { newMode = FLAT; } From 474c847ef1da4538f99afd707c4a826ec66b91d8 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 24 Sep 2015 18:00:59 -0700 Subject: [PATCH 3/6] Updated constants and units. --- interface/src/avatar/MyAvatar.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 12da9b4070..8e517991c0 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -259,9 +259,12 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix, float _hmdSensorPosition = extractTranslation(hmdSensorMatrix); _hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix); + const float STRAIGHTING_LEAN_DURATION = 0.5f; // seconds + const float STRAIGHTING_LEAN_THRESHOLD = 0.2f; // meters + auto newBodySensorMatrix = deriveBodyFromHMDSensor(); glm::vec3 diff = extractTranslation(newBodySensorMatrix) - extractTranslation(_bodySensorMatrix); - if (!_straightingLean && glm::length(diff) > 0.2f) { + if (!_straightingLean && glm::length(diff) > STRAIGHTING_LEAN_THRESHOLD) { // begin homing toward derived body position. _straightingLean = true; @@ -274,7 +277,6 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix, float glm::vec3 worldBodyPos = extractTranslation(worldBodyMatrix); glm::quat worldBodyRot = glm::normalize(glm::quat_cast(worldBodyMatrix)); - const float STRAIGHTING_LEAN_DURATION = 0.5f; _straightingLeanAlpha += (1.0f / STRAIGHTING_LEAN_DURATION) * deltaTime; if (_straightingLeanAlpha >= 1.0f) { @@ -298,7 +300,7 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix, float } } } - +// // best called at end of main loop, just before rendering. // update sensor to world matrix from current body position and hmd sensor. // This is so the correct camera can be used for rendering. From 1051416ed7b0927a877edb65300c94d433a9cc1f Mon Sep 17 00:00:00 2001 From: James Pollack Date: Fri, 25 Sep 2015 10:33:16 -0700 Subject: [PATCH 4/6] Change stat collection to batch stats together to make fewer network calls --- examples/example/misc/collectHifiStats.js | 93 +++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 examples/example/misc/collectHifiStats.js diff --git a/examples/example/misc/collectHifiStats.js b/examples/example/misc/collectHifiStats.js new file mode 100644 index 0000000000..3902622217 --- /dev/null +++ b/examples/example/misc/collectHifiStats.js @@ -0,0 +1,93 @@ +// +// collectHifiStats.js +// +// Created by Thijs Wenker on 24 Sept 2015 +// Additions by James B. Pollack @imgntn on 25 Sept 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Collects stats for analysis to a REST endpoint. Defaults to batching stats, but can be customized. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// The url where the data will be posted. +var ENDPOINT_URL = ""; + +var BATCH_STATS = true; +var BATCH_SIZE = 5; + +var batch = []; + +if (BATCH_STATS) { + + var RECORD_EVERY = 1000; // 1 seconds + var batchCount = 0; + + Script.setInterval(function() { + + if (batchCount === BATCH_SIZE) { + sendBatchToEndpoint(batch); + batchCount = 0; + } + Stats.forceUpdateStats(); + batch.push(getBatchStats()); + batchCount++; + }, RECORD_EVERY); + + +} else { + // Send the data every: + var SEND_EVERY = 30000; // 30 seconds + + Script.setInterval(function() { + Stats.forceUpdateStats(); + var req = new XMLHttpRequest(); + req.open("POST", ENDPOINT_URL, false); + req.send(getStats()); + }, SEND_EVERY); +} + +function getStats() { + return JSON.stringify({ + username: GlobalServices.username, + location: Window.location.hostname, + framerate: Stats.framerate, + simrate: Stats.simrate, + ping: { + audio: Stats.audioPing, + avatar: Stats.avatarPing, + entities: Stats.entitiesPing, + asset: Stats.assetPing + }, + position: Camera.position, + yaw: Stats.yaw, + rotation: Camera.orientation.x + ',' + Camera.orientation.y + ',' + Camera.orientation.z + ',' + Camera.orientation.w + }) +} + +function getBatchStats() { + // print('GET BATCH STATS'); + return { + username: GlobalServices.username, + location: Window.location.hostname, + framerate: Stats.framerate, + simrate: Stats.simrate, + ping: { + audio: Stats.audioPing, + avatar: Stats.avatarPing, + entities: Stats.entitiesPing, + asset: Stats.assetPing + }, + position: Camera.position, + yaw: Stats.yaw, + rotation: Camera.orientation.x + ',' + Camera.orientation.y + ',' + Camera.orientation.z + ',' + Camera.orientation.w + } +} + +function sendBatchToEndpoint(batch) { + // print('SEND BATCH TO ENDPOINT'); + var req = new XMLHttpRequest(); + req.open("POST", ENDPOINT_URL, false); + req.send(JSON.stringify(batch)); +} \ No newline at end of file From 402573dbf15d193a42b67735611ff9304203e900 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 25 Sep 2015 14:11:33 -0700 Subject: [PATCH 5/6] Remove unused local --- interface/src/ui/OverlayConductor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 2e7838872a..6e4d7e8248 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -68,7 +68,6 @@ void OverlayConductor::updateMode() { Mode newMode; if (qApp->isHMDMode()) { - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); newMode = SITTING; } else { newMode = FLAT; From a97f5569584371878752c8a18f5dfbcbd4e2da58 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 25 Sep 2015 15:13:57 -0700 Subject: [PATCH 6/6] enforce IK target rotation --- .../animation/src/AnimInverseKinematics.cpp | 66 +++++++++++++++---- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 7f32d8a2fa..de226092f1 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -9,6 +9,7 @@ #include "AnimInverseKinematics.h" +#include #include #include #include @@ -159,24 +160,34 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vectorgetParentIndex(tipIndex); + glm::quat tipParentRotation; + if (pivotIndex != -1) { + tipParentRotation = absolutePoses[pivotIndex].rot; + } // descend toward root, pivoting each joint to get tip closer to target - int pivotIndex = _skeleton->getParentIndex(tipIndex); - float fractionDenominator = 1.0f; + int ancestorCount = 1; while (pivotIndex != -1) { // compute the two lines that should be aligned glm::vec3 jointPosition = absolutePoses[pivotIndex].trans; - glm::vec3 leverArm = tip - jointPosition; + glm::vec3 leverArm = tipPosition - jointPosition; glm::vec3 targetLine = targetPose.trans - jointPosition; - // compute the axis of the rotation that would align them + // compute the swing that would get get tip closer glm::vec3 axis = glm::cross(leverArm, targetLine); float axisLength = glm::length(axis); glm::quat deltaRotation; const float MIN_AXIS_LENGTH = 1.0e-4f; if (axisLength > MIN_AXIS_LENGTH) { - // compute deltaRotation for alignment (brings tip closer to target) + // compute deltaRotation for alignment (swings tip closer to target) axis /= axisLength; float angle = acosf(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine))); @@ -184,24 +195,50 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector MIN_ADJUSTMENT_ANGLE) { - // reduce angle by half: slows convergence but adds stability to IK solution - angle /= fractionDenominator; + // reduce angle by a fraction (reduces IK swing contribution of this joint) + angle /= (float)ancestorCount; deltaRotation = glm::angleAxis(angle, axis); } + + // The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's + // new rotation and its target. We compute that delta here and rotate the tipJoint accordingly. + glm::quat tipRelativeRotation = glm::inverse(deltaRotation * tipParentRotation) * targetPose.rot; + + // enforce tip's constraint + RotationConstraint* constraint = getConstraint(tipIndex); + if (constraint) { + bool constrained = constraint->apply(tipRelativeRotation); + if (constrained) { + // The tip's final parent-relative rotation violates its constraint + // so we try to twist this pivot to compensate. + glm::quat constrainedTipRotation = deltaRotation * tipParentRotation * tipRelativeRotation; + glm::quat missingRotation = targetPose.rot * glm::inverse(constrainedTipRotation); + glm::quat swingPart; + glm::quat twistPart; + glm::vec3 axis = glm::normalize(deltaRotation * leverArm); + swingTwistDecomposition(missingRotation, axis, swingPart, twistPart); + deltaRotation = twistPart * deltaRotation; + } + // we update the tip rotation here to rotate it as close to its target orientation as possible + // before moving on to next pivot + tipRotation = tipParentRotation * tipRelativeRotation; + } } - fractionDenominator++; + ++ancestorCount; 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 + // compute joint's new parent-relative rotation after swing // 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); + + // enforce pivot's constraint RotationConstraint* constraint = getConstraint(pivotIndex); if (constraint) { bool constrained = constraint->apply(newRot); @@ -214,6 +251,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vectorgetParentIndex(pivotIndex); } @@ -464,7 +504,7 @@ void AnimInverseKinematics::initConstraints() { } else if (0 == baseName.compare("Hand", Qt::CaseInsensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); - const float MAX_HAND_TWIST = PI; + const float MAX_HAND_TWIST = 3.0f * PI / 5.0f; const float MIN_HAND_TWIST = -PI / 2.0f; if (isLeft) { stConstraint->setTwistLimits(-MAX_HAND_TWIST, -MIN_HAND_TWIST);