From 43782a29c78d8504e1104dbf853bd73777bcda49 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Wed, 12 Jul 2017 22:04:58 +0100 Subject: [PATCH 01/13] Avatar Scaler - WIP --- .../qml/dialogs/preferences/Section.qml | 6 ++ .../preferences/SpinnerSliderPreference.qml | 64 +++++++++++++++++++ .../tabletWindows/preferences/Section.qml | 5 ++ interface/src/ui/PreferencesDialog.cpp | 36 ++++++----- libraries/shared/src/Preferences.h | 10 +++ 5 files changed, 104 insertions(+), 17 deletions(-) create mode 100644 interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index a813dc6b5f..61b3c7530b 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -72,6 +72,7 @@ Preference { property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } + property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } property var preferences: [] property int checkBoxCount: 0 @@ -128,6 +129,11 @@ Preference { checkBoxCount = 0; builder = comboBoxBuilder; break; + + case Preference.SpinnerSlider: + checkBoxCount = 0; + builder = spinnerSliderBuilder; + break; }; if (builder) { diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml new file mode 100644 index 0000000000..e9013bc17a --- /dev/null +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -0,0 +1,64 @@ +// +// SpinBoxPreference.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +import "../../dialogs" +import "../../controls-uit" + +Preference { + id: root + property alias slider: slider + height: control.height + hifi.dimensions.controlInterlineHeight + + Component.onCompleted: { + slider.value = preference.value; + } + + function save() { + preference.value = slider.value; + preference.save(); + } + + Item { + id: control + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: Math.max(labelText.height, slider.height) + + Label { + id: labelText + text: root.label + ":" + colorScheme: hifi.colorSchemes.dark + anchors { + left: parent.left + right: slider.left + rightMargin: hifi.dimensions.labelPadding + verticalCenter: parent.verticalCenter + } + horizontalAlignment: Text.AlignRight + wrapMode: Text.Wrap + } + + Slider { + id: slider + value: preference.value + width: 130 + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + } + colorScheme: hifi.colorSchemes.dark + } + } +} diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml index 9dd0956000..25279bb6bf 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml @@ -81,6 +81,7 @@ Preference { property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } + property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } property var preferences: [] property int checkBoxCount: 0 @@ -143,6 +144,10 @@ Preference { //to be not overlapped when drop down is active zpos = root.z + 1000 - itemNum break; + case Preference.SpinnerSlider: + checkBoxCount = 0; + builder = spinnerSliderBuilder; + break; }; if (builder) { diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 767c122bb6..9c1b3e8c70 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -181,16 +181,18 @@ void setupPreferences() { preference->setStep(1); preferences->addPreference(preference); } - { - auto getter = [=]()->float { return myAvatar->getUniformScale(); }; - auto setter = [=](float value) { myAvatar->setTargetScale(value); }; - auto preference = new SpinnerPreference(AVATAR_TUNING, "Avatar scale (default is 1.0)", getter, setter); - preference->setMin(0.01f); - preference->setMax(99.9f); - preference->setDecimals(2); - preference->setStep(1); - preferences->addPreference(preference); - } + { + auto getter = [=]()->float { return myAvatar->getUniformScale(); }; + auto setter = [=](float value) { myAvatar->setTargetScale(value); }; + + auto scaleSpinner = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); + scaleSpinner->setMin(0.01f); + scaleSpinner->setMax(99.9f); + scaleSpinner->setDecimals(2); + scaleSpinner->setStep(1); + + preferences->addPreference(scaleSpinner); + } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; auto setter = [](float value) { DependencyManager::get()->setEyeClosingThreshold(value); }; @@ -227,17 +229,17 @@ void setupPreferences() { preferences->addPreference(preference); } - static const QString AUDIO("Audio"); + static const QString AUDIO_BUFFERS("Audio Buffers"); { auto getter = []()->bool { return !DependencyManager::get()->getReceivedAudioStream().dynamicJitterBufferEnabled(); }; auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setDynamicJitterBufferEnabled(!value); }; - auto preference = new CheckPreference(AUDIO, "Disable dynamic jitter buffer", getter, setter); + auto preference = new CheckPreference(AUDIO_BUFFERS, "Disable dynamic jitter buffer", getter, setter); preferences->addPreference(preference); } { auto getter = []()->float { return DependencyManager::get()->getReceivedAudioStream().getStaticJitterBufferFrames(); }; auto setter = [](float value) { DependencyManager::get()->getReceivedAudioStream().setStaticJitterBufferFrames(value); }; - auto preference = new SpinnerPreference(AUDIO, "Static jitter buffer frames", getter, setter); + auto preference = new SpinnerPreference(AUDIO_BUFFERS, "Static jitter buffer frames", getter, setter); preference->setMin(0); preference->setMax(2000); preference->setStep(1); @@ -246,13 +248,13 @@ void setupPreferences() { { auto getter = []()->bool { return !DependencyManager::get()->getOutputStarveDetectionEnabled(); }; auto setter = [](bool value) { DependencyManager::get()->setOutputStarveDetectionEnabled(!value); }; - auto preference = new CheckPreference(AUDIO, "Disable output starve detection", getter, setter); + auto preference = new CheckPreference(AUDIO_BUFFERS, "Disable output starve detection", getter, setter); preferences->addPreference(preference); } { auto getter = []()->float { return DependencyManager::get()->getOutputBufferSize(); }; auto setter = [](float value) { DependencyManager::get()->setOutputBufferSize(value); }; - auto preference = new SpinnerPreference(AUDIO, "Output buffer initial frames", getter, setter); + auto preference = new SpinnerPreference(AUDIO_BUFFERS, "Output buffer initial frames", getter, setter); preference->setMin(AudioClient::MIN_BUFFER_FRAMES); preference->setMax(AudioClient::MAX_BUFFER_FRAMES); preference->setStep(1); @@ -262,13 +264,13 @@ void setupPreferences() { { auto getter = []()->bool { return DependencyManager::get()->isSimulatingJitter(); }; auto setter = [](bool value) { return DependencyManager::get()->setIsSimulatingJitter(value); }; - auto preference = new CheckPreference(AUDIO, "Packet jitter simulator", getter, setter); + auto preference = new CheckPreference(AUDIO_BUFFERS, "Packet jitter simulator", getter, setter); preferences->addPreference(preference); } { auto getter = []()->float { return DependencyManager::get()->getGateThreshold(); }; auto setter = [](float value) { return DependencyManager::get()->setGateThreshold(value); }; - auto preference = new SpinnerPreference(AUDIO, "Packet throttle threshold", getter, setter); + auto preference = new SpinnerPreference(AUDIO_BUFFERS, "Packet throttle threshold", getter, setter); preference->setMin(1); preference->setMax(200); preference->setStep(1); diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index f1915a9d6a..271df58951 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -52,6 +52,7 @@ public: Browsable, Slider, Spinner, + SpinnerSlider, Checkbox, Button, ComboBox, @@ -254,6 +255,15 @@ public: Type getType() override { return Spinner; } }; +class SpinnerSliderPreference : public FloatPreference { + Q_OBJECT +public: + SpinnerSliderPreference(const QString& category, const QString& name, Getter getter, Setter setter) + : FloatPreference(category, name, getter, setter) { } + + Type getType() override { return SpinnerSlider; } +}; + class IntSpinnerPreference : public IntPreference { Q_OBJECT public: From 521babb6bd558641667094c8139568af7ba34ee2 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 19:18:51 +0100 Subject: [PATCH 02/13] Avatar Scaler - should be PR Ready. --- .../resources/qml/controls-uit/Slider.qml | 2 +- .../qml/dialogs/preferences/Section.qml | 6 +- .../preferences/SpinnerSliderPreference.qml | 54 +- interface/src/avatar/MyAvatar.cpp | 745 ++++++++++++++---- interface/src/avatar/MyAvatar.h | 131 ++- interface/src/ui/PreferencesDialog.cpp | 12 +- libraries/shared/src/Preferences.h | 2 +- 7 files changed, 738 insertions(+), 214 deletions(-) diff --git a/interface/resources/qml/controls-uit/Slider.qml b/interface/resources/qml/controls-uit/Slider.qml index 39831546e1..18d0d33fe2 100644 --- a/interface/resources/qml/controls-uit/Slider.qml +++ b/interface/resources/qml/controls-uit/Slider.qml @@ -36,7 +36,7 @@ Slider { Rectangle { width: parent.height - 2 - height: slider.value * (slider.width/(slider.maximumValue - slider.minimumValue)) - 1 + height: slider.width * (slider.value - slider.minimumValue)/(slider.maximumValue-slider.minimumValue)-1 radius: height / 2 anchors { top: parent.top diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index 61b3c7530b..a2bfa9ba0e 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -72,7 +72,7 @@ Preference { property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } - property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } + property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } property var preferences: [] property int checkBoxCount: 0 @@ -87,7 +87,7 @@ Preference { } function buildPreference(preference) { - console.log("\tPreference type " + preference.type + " name " + preference.name) + console.log("\tPreference type " + preference.type + " name " + preference.name); var builder; switch (preference.type) { case Preference.Editable: @@ -128,11 +128,13 @@ Preference { case Preference.ComboBox: checkBoxCount = 0; builder = comboBoxBuilder; + console.log("Built COMBOBOX"); break; case Preference.SpinnerSlider: checkBoxCount = 0; builder = spinnerSliderBuilder; + console.log("Built SPINNERSLIDER"); break; }; diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index e9013bc17a..5b9e70a42a 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -1,7 +1,7 @@ // -// SpinBoxPreference.qml +// SpinnerSliderPreference.qml // -// Created by Bradley Austin Davis on 18 Jan 2016 +// Created by Cain Kilgore on 11th July 2017 // Copyright 2016 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -27,6 +27,10 @@ Preference { preference.save(); } + function sliderToAvatarScale(sliderValue) { + return MyAvatar.getDomainMinScale() + (MyAvatar.getDomainMaxScale()-MyAvatar.getDomainMinScale())*value; + } + Item { id: control anchors { @@ -53,12 +57,50 @@ Preference { Slider { id: slider value: preference.value - width: 130 + width: 100 + minimumValue: MyAvatar.getDomainMinScale() + maximumValue: MyAvatar.getDomainMaxScale() + stepSize: preference.step + onValueChanged: { + spinner.value = value + } anchors { - right: parent.right - verticalCenter: parent.verticalCenter + right: spinner.left + rightMargin: 10 + } + } + SpinBox { + id: spinner + decimals: preference.decimals + value: preference.value + minimumValue: MyAvatar.getDomainMinScale() + maximumValue: MyAvatar.getDomainMaxScale() + width: 100 + onValueChanged: { + slider.value = value; + } + anchors { + right: button.left + rightMargin: 10 } colorScheme: hifi.colorSchemes.dark } + Button { + id: button + onClicked: { + if(spinner.maximumValue >= 1) { + spinner.value = 1 + slider.value = 1 + } else { + spinner.value = spinner.maximumValue + slider.value = spinner.maximumValue + } + } + width: 50 + text: "Reset" + anchors { + right: parent.right + } + } } -} +} \ No newline at end of file diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 333c33ba3b..728fba2fc7 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -21,6 +21,7 @@ #include +#include #include #include #include @@ -38,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -86,19 +88,30 @@ const float MyAvatar::ZOOM_MAX = 25.0f; const float MyAvatar::ZOOM_DEFAULT = 1.5f; // default values, used when avatar is missing joints... (avatar space) -// static const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; +static const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; static const glm::vec3 DEFAULT_AVATAR_MIDDLE_EYE_POS { 0.0f, 0.6f, 0.0f }; static const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.53f, 0.0f }; +static const glm::quat DEFAULT_AVATAR_HEAD_ROT { Quaternions::Y_180 }; +static const glm::vec3 DEFAULT_AVATAR_RIGHTARM_POS { -0.134824f, 0.396348f, -0.0515777f }; +static const glm::quat DEFAULT_AVATAR_RIGHTARM_ROT { -0.536241f, 0.536241f, -0.460918f, -0.460918f }; +static const glm::vec3 DEFAULT_AVATAR_LEFTARM_POS { 0.134795f, 0.396349f, -0.0515881f }; +static const glm::quat DEFAULT_AVATAR_LEFTARM_ROT { 0.536257f, 0.536258f, -0.460899f, 0.4609f }; +static const glm::vec3 DEFAULT_AVATAR_RIGHTHAND_POS { -0.72768f, 0.396349f, -0.0515779f }; +static const glm::quat DEFAULT_AVATAR_RIGHTHAND_ROT { 0.479184f, -0.520013f, 0.522537f, 0.476365f}; +static const glm::vec3 DEFAULT_AVATAR_LEFTHAND_POS { 0.727588f, 0.39635f, -0.0515878f }; +static const glm::quat DEFAULT_AVATAR_LEFTHAND_ROT { -0.479181f, -0.52001f, 0.52254f, -0.476369f }; static const glm::vec3 DEFAULT_AVATAR_NECK_POS { 0.0f, 0.445f, 0.025f }; static const glm::vec3 DEFAULT_AVATAR_SPINE2_POS { 0.0f, 0.32f, 0.02f }; +static const glm::quat DEFAULT_AVATAR_SPINE2_ROT { Quaternions::Y_180 }; static const glm::vec3 DEFAULT_AVATAR_HIPS_POS { 0.0f, 0.0f, 0.0f }; +static const glm::quat DEFAULT_AVATAR_HIPS_ROT { Quaternions::Y_180 }; static const glm::vec3 DEFAULT_AVATAR_LEFTFOOT_POS { -0.08f, -0.96f, 0.029f}; static const glm::quat DEFAULT_AVATAR_LEFTFOOT_ROT { -0.40167322754859924f, 0.9154590368270874f, -0.005437685176730156f, -0.023744143545627594f }; static const glm::vec3 DEFAULT_AVATAR_RIGHTFOOT_POS { 0.08f, -0.96f, 0.029f }; static const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { -0.4016716778278351f, 0.9154615998268127f, 0.0053307069465518f, 0.023696165531873703f }; -MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : - Avatar(thread, rig), +MyAvatar::MyAvatar(QThread* thread) : + Avatar(thread), _yawSpeed(YAW_SPEED_DEFAULT), _pitchSpeed(PITCH_SPEED_DEFAULT), _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), @@ -109,6 +122,9 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : _realWorldFieldOfView("realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), _useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", false), + _smoothOrientationTimer(std::numeric_limits::max()), + _smoothOrientationInitial(), + _smoothOrientationTarget(), _hmdSensorMatrix(), _hmdSensorOrientation(), _hmdSensorPosition(), @@ -116,7 +132,6 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : _goToPending(false), _goToPosition(), _goToOrientation(), - _rig(rig), _prevShouldDrawHead(true), _audioListenerMode(FROM_HEAD), _hmdAtRestDetector(glm::vec3(0), glm::quat()) @@ -125,7 +140,7 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : // give the pointer to our head to inherited _headData variable from AvatarData _headData = new MyHead(this); - _skeletonModel = std::make_shared(this, nullptr, rig); + _skeletonModel = std::make_shared(this, nullptr); connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); @@ -176,9 +191,7 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : auto audioIO = DependencyManager::get(); audioIO->setIsPlayingBackRecording(isPlaying); - if (_rig) { - _rig->setEnableAnimations(!isPlaying); - } + _skeletonModel->getRig().setEnableAnimations(!isPlaying); }); connect(recorder.data(), &Recorder::recordingStateChanged, [=] { @@ -229,12 +242,13 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : } auto jointData = dummyAvatar.getRawJointData(); - if (jointData.length() > 0 && _rig) { - _rig->copyJointsFromJointData(jointData); + if (jointData.length() > 0) { + _skeletonModel->getRig().copyJointsFromJointData(jointData); } }); - connect(rig.get(), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete())); + connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete())); + _characterController.setDensity(_density); } MyAvatar::~MyAvatar() { @@ -264,15 +278,27 @@ QVariant MyAvatar::getOrientationVar() const { return quatToVariant(Avatar::getOrientation()); } +glm::quat MyAvatar::getOrientationOutbound() const { + // Allows MyAvatar to send out smoothed data to remote agents if required. + if (_smoothOrientationTimer > SMOOTH_TIME_ORIENTATION) { + return (getLocalOrientation()); + } + + // Smooth the remote avatar movement. + float t = _smoothOrientationTimer / SMOOTH_TIME_ORIENTATION; + float interp = Interpolate::easeInOutQuad(glm::clamp(t, 0.0f, 1.0f)); + return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, interp)); +} // virtual void MyAvatar::simulateAttachments(float deltaTime) { // don't update attachments here, do it in harvestResultsFromPhysicsSimulation() } -QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { +QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) { CameraMode mode = qApp->getCamera().getMode(); _globalPosition = getPosition(); + // This might not be right! Isn't the capsule local offset in avatar space, and don't we need to add the radius to the y as well? -HRS 5/26/17 _globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius(); _globalBoundingBoxDimensions.y = _characterController.getCapsuleHalfHeight(); _globalBoundingBoxDimensions.z = _characterController.getCapsuleRadius(); @@ -290,6 +316,11 @@ QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { } void MyAvatar::resetSensorsAndBody() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "resetSensorsAndBody"); + return; + } + qApp->getActiveDisplayPlugin()->resetSensors(); reset(true, false, true); } @@ -334,9 +365,7 @@ void MyAvatar::clearIKJointLimitHistory() { return; } - if (_rig) { - _rig->clearIKJointLimitHistory(); - } + _skeletonModel->getRig().clearIKJointLimitHistory(); } void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { @@ -385,19 +414,26 @@ void MyAvatar::update(float deltaTime) { // update moving average of HMD facing in xz plane. const float HMD_FACING_TIMESCALE = 4.0f; // very slow average float tau = deltaTime / HMD_FACING_TIMESCALE; - _hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau); + _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau); + + if (_smoothOrientationTimer < SMOOTH_TIME_ORIENTATION) { + _rotationChanged = usecTimestampNow(); + _smoothOrientationTimer += deltaTime; + } #ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE - glm::vec3 p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacingMovingAverage.x, 0.0f, _hmdSensorFacingMovingAverage.y)); + glm::vec3 p = transformPoint(getSensorToWorldMatrix(), getHeadControllerPoseInAvatarFrame() * + glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y)); DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f)); - p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacing.x, 0.0f, _hmdSensorFacing.y)); + p = transformPoint(getSensorToWorldMatrix(), getHMDSensorPosition() + + glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y)); DebugDraw::getInstance().addMarker("facing", getOrientation(), p, glm::vec4(1.0f)); #endif if (_goToPending) { setPosition(_goToPosition); setOrientation(_goToOrientation); - _hmdSensorFacingMovingAverage = _hmdSensorFacing; // reset moving average + _headControllerFacingMovingAverage = _headControllerFacing; // reset moving average _goToPending = false; // updateFromHMDSensorMatrix (called from paintGL) expects that the sensorToWorldMatrix is updated for any position changes // that happen between render and Application::update (which calls updateSensorToWorldMatrix to do so). @@ -405,6 +441,13 @@ void MyAvatar::update(float deltaTime) { // so we update now. It's ok if it updates again in the normal way. updateSensorToWorldMatrix(); emit positionGoneTo(); + // Run safety tests as soon as we can after goToLocation, or clear if we're not colliding. + _physicsSafetyPending = getCollisionsEnabled(); + } + if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { + // When needed and ready, arrange to check and fix. + _physicsSafetyPending = false; + safeLanding(_goToPosition); // no-op if already safe } Head* head = getHead(); @@ -419,6 +462,7 @@ void MyAvatar::update(float deltaTime) { setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius()); + // This might not be right! Isn't the capsule local offset in avatar space? -HRS 5/26/17 halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset(); QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters", Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)), @@ -502,10 +546,9 @@ void MyAvatar::simulate(float deltaTime) { { PerformanceTimer perfTimer("skeleton"); - if (_rig) { - _rig->setEnableDebugDrawIKTargets(_enableDebugDrawIKTargets); - } - + _skeletonModel->getRig().setEnableDebugDrawIKTargets(_enableDebugDrawIKTargets); + _skeletonModel->getRig().setEnableDebugDrawIKConstraints(_enableDebugDrawIKConstraints); + _skeletonModel->getRig().setEnableDebugDrawIKChains(_enableDebugDrawIKChains); _skeletonModel->simulate(deltaTime); } @@ -523,7 +566,7 @@ void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("joints"); // copy out the skeleton joints from the model if (_rigEnabled) { - _rig->copyJointsIntoJointData(_jointData); + _skeletonModel->getRig().copyJointsIntoJointData(_jointData); } } @@ -551,12 +594,12 @@ void MyAvatar::simulate(float deltaTime) { auto entityTreeRenderer = qApp->getEntities(); EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; if (entityTree) { - bool flyingAllowed = true; + bool zoneAllowsFlying = true; bool collisionlessAllowed = true; entityTree->withWriteLock([&] { std::shared_ptr zone = entityTreeRenderer->myAvatarZone(); if (zone) { - flyingAllowed = zone->getFlyingAllowed(); + zoneAllowsFlying = zone->getFlyingAllowed(); collisionlessAllowed = zone->getGhostingAllowed(); } auto now = usecTimestampNow(); @@ -587,7 +630,7 @@ void MyAvatar::simulate(float deltaTime) { entityTree->recurseTreeWithOperator(&moveOperator); } }); - _characterController.setFlyingAllowed(flyingAllowed); + _characterController.setFlyingAllowed(zoneAllowsFlying && _enableFlying); _characterController.setCollisionlessAllowed(collisionlessAllowed); } @@ -605,15 +648,21 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { _hmdSensorMatrix = hmdSensorMatrix; auto newHmdSensorPosition = extractTranslation(hmdSensorMatrix); - if (newHmdSensorPosition != _hmdSensorPosition && + if (newHmdSensorPosition != getHMDSensorPosition() && glm::length(newHmdSensorPosition) > MAX_HMD_ORIGIN_DISTANCE) { qWarning() << "Invalid HMD sensor position " << newHmdSensorPosition; // Ignore unreasonable HMD sensor data return; } + _hmdSensorPosition = newHmdSensorPosition; _hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix); - _hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation); + auto headPose = _headControllerPoseInSensorFrameCache.get(); + if (headPose.isValid()) { + _headControllerFacing = getFacingDir2D(headPose.rotation); + } else { + _headControllerFacing = glm::vec2(1.0f, 0.0f); + } } void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache) { @@ -651,7 +700,7 @@ void MyAvatar::updateSensorToWorldMatrix() { // Update avatar head rotation with sensor data void MyAvatar::updateFromTrackers(float deltaTime) { - glm::vec3 estimatedPosition, estimatedRotation; + glm::vec3 estimatedRotation; bool inHmd = qApp->isHMDMode(); bool playing = DependencyManager::get()->isPlaying(); @@ -662,11 +711,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) { FaceTracker* tracker = qApp->getActiveFaceTracker(); bool inFacetracker = tracker && !FaceTracker::isMuted(); - if (inHmd) { - estimatedPosition = extractTranslation(getHMDSensorMatrix()); - estimatedPosition.x *= -1.0f; - } else if (inFacetracker) { - estimatedPosition = tracker->getHeadTranslation(); + if (inFacetracker) { estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation())); } @@ -753,6 +798,77 @@ controller::Pose MyAvatar::getRightHandTipPose() const { return pose; } +glm::vec3 MyAvatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const { + glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if (jointIndex != -1) { + if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) { + _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot); + } else { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + } + glm::vec3 modelOffset = position - jointPos; + glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset; + + return jointSpacePosition; +} + +glm::vec3 MyAvatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const { + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + + glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir; + return jointSpaceDir; +} + +glm::quat MyAvatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const { + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot; + return jointSpaceRot; +} + +glm::vec3 MyAvatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const { + glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + + if (jointIndex != -1) { + if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) { + _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot); + } else { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + } + + glm::vec3 worldOffset = jointRot * jointSpacePos; + glm::vec3 worldPos = jointPos + worldOffset; + + return worldPos; +} + +glm::vec3 MyAvatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const { + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + glm::vec3 worldDir = jointRot * jointSpaceDir; + return worldDir; +} + +glm::quat MyAvatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const { + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + glm::quat worldRot = jointRot * jointSpaceRot; + return worldRot; +} + // virtual void MyAvatar::render(RenderArgs* renderArgs) { // don't render if we've been asked to disable local rendering @@ -768,7 +884,7 @@ void MyAvatar::overrideAnimation(const QString& url, float fps, bool loop, float Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); return; } - _rig->overrideAnimation(url, fps, loop, firstFrame, lastFrame); + _skeletonModel->getRig().overrideAnimation(url, fps, loop, firstFrame, lastFrame); } void MyAvatar::restoreAnimation() { @@ -776,16 +892,16 @@ void MyAvatar::restoreAnimation() { QMetaObject::invokeMethod(this, "restoreAnimation"); return; } - _rig->restoreAnimation(); + _skeletonModel->getRig().restoreAnimation(); } QStringList MyAvatar::getAnimationRoles() { if (QThread::currentThread() != thread()) { QStringList result; - QMetaObject::invokeMethod(this, "getAnimationRoles", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QStringList, result)); + BLOCKING_INVOKE_METHOD(this, "getAnimationRoles", Q_RETURN_ARG(QStringList, result)); return result; } - return _rig->getAnimationRoles(); + return _skeletonModel->getRig().getAnimationRoles(); } void MyAvatar::overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, @@ -795,7 +911,7 @@ void MyAvatar::overrideRoleAnimation(const QString& role, const QString& url, fl Q_ARG(float, fps), Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); return; } - _rig->overrideRoleAnimation(role, url, fps, loop, firstFrame, lastFrame); + _skeletonModel->getRig().overrideRoleAnimation(role, url, fps, loop, firstFrame, lastFrame); } void MyAvatar::restoreRoleAnimation(const QString& role) { @@ -803,7 +919,7 @@ void MyAvatar::restoreRoleAnimation(const QString& role) { QMetaObject::invokeMethod(this, "restoreRoleAnimation", Q_ARG(const QString&, role)); return; } - _rig->restoreRoleAnimation(role); + _skeletonModel->getRig().restoreRoleAnimation(role); } void MyAvatar::saveData() { @@ -814,10 +930,15 @@ void MyAvatar::saveData() { settings.setValue("scale", _targetScale); - settings.setValue("fullAvatarURL", + // only save the fullAvatarURL if it has not been overwritten on command line + // (so the overrideURL is not valid), or it was overridden _and_ we specified + // --replaceAvatarURL (so _saveAvatarOverrideUrl is true) + if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid() ) { + settings.setValue("fullAvatarURL", _fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ? "" : _fullAvatarURLFromPreferences.toString()); + } settings.setValue("fullAvatarModelName", _fullAvatarModelName); @@ -927,6 +1048,14 @@ void MyAvatar::setEnableDebugDrawIKTargets(bool isEnabled) { _enableDebugDrawIKTargets = isEnabled; } +void MyAvatar::setEnableDebugDrawIKConstraints(bool isEnabled) { + _enableDebugDrawIKConstraints = isEnabled; +} + +void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { + _enableDebugDrawIKChains = isEnabled; +} + void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene()); } @@ -937,7 +1066,7 @@ void MyAvatar::setUseAnimPreAndPostRotations(bool isEnabled) { } void MyAvatar::setEnableInverseKinematics(bool isEnabled) { - _rig->setEnableInverseKinematics(isEnabled); + _skeletonModel->getRig().setEnableInverseKinematics(isEnabled); } void MyAvatar::loadData() { @@ -1186,7 +1315,7 @@ void MyAvatar::setJointData(int index, const glm::quat& rotation, const glm::vec return; } // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); + _skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); } void MyAvatar::setJointRotation(int index, const glm::quat& rotation) { @@ -1195,7 +1324,7 @@ void MyAvatar::setJointRotation(int index, const glm::quat& rotation) { return; } // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointRotation(index, true, rotation, SCRIPT_PRIORITY); + _skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY); } void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) { @@ -1204,7 +1333,7 @@ void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) { return; } // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointTranslation(index, true, translation, SCRIPT_PRIORITY); + _skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY); } void MyAvatar::clearJointData(int index) { @@ -1212,7 +1341,7 @@ void MyAvatar::clearJointData(int index) { QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index)); return; } - _rig->clearJointAnimationPriority(index); + _skeletonModel->getRig().clearJointAnimationPriority(index); } void MyAvatar::clearJointsData() { @@ -1220,13 +1349,14 @@ void MyAvatar::clearJointsData() { QMetaObject::invokeMethod(this, "clearJointsData"); return; } - _rig->clearJointStates(); + _skeletonModel->getRig().clearJointStates(); } void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { Avatar::setSkeletonModelURL(skeletonModelURL); _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene()); _headBoneSet.clear(); + emit skeletonChanged(); } @@ -1240,7 +1370,7 @@ void MyAvatar::resetFullAvatarURL() { void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "useFullAvatarURL", Qt::BlockingQueuedConnection, + BLOCKING_INVOKE_METHOD(this, "useFullAvatarURL", Q_ARG(const QUrl&, fullAvatarURL), Q_ARG(const QString&, modelName)); return; @@ -1266,7 +1396,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN void MyAvatar::setAttachmentData(const QVector& attachmentData) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setAttachmentData", Qt::BlockingQueuedConnection, + BLOCKING_INVOKE_METHOD(this, "setAttachmentData", Q_ARG(const QVector, attachmentData)); return; } @@ -1295,28 +1425,10 @@ void MyAvatar::rebuildCollisionShape() { _characterController.setLocalBoundingBox(corner, diagonal); } -static controller::Pose applyLowVelocityFilter(const controller::Pose& oldPose, const controller::Pose& newPose) { - controller::Pose finalPose = newPose; - if (newPose.isValid()) { - // Use a velocity sensitive filter to damp small motions and preserve large ones with - // no latency. - float velocityFilter = glm::clamp(1.0f - glm::length(oldPose.getVelocity()), 0.0f, 1.0f); - finalPose.translation = oldPose.getTranslation() * velocityFilter + newPose.getTranslation() * (1.0f - velocityFilter); - finalPose.rotation = safeMix(oldPose.getRotation(), newPose.getRotation(), 1.0f - velocityFilter); - } - return finalPose; -} void MyAvatar::setHandControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right) { - if (controller::InputDevice::getLowVelocityFilter()) { - auto oldLeftPose = getLeftHandControllerPoseInSensorFrame(); - auto oldRightPose = getRightHandControllerPoseInSensorFrame(); - _leftHandControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldLeftPose, left)); - _rightHandControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldRightPose, right)); - } else { - _leftHandControllerPoseInSensorFrameCache.set(left); - _rightHandControllerPoseInSensorFrameCache.set(right); - } + _leftHandControllerPoseInSensorFrameCache.set(left); + _rightHandControllerPoseInSensorFrameCache.set(right); } controller::Pose MyAvatar::getLeftHandControllerPoseInSensorFrame() const { @@ -1345,16 +1457,22 @@ controller::Pose MyAvatar::getRightHandControllerPoseInAvatarFrame() const { return getRightHandControllerPoseInWorldFrame().transform(invAvatarMatrix); } +void MyAvatar::setFingerControllerPosesInSensorFrame(const FingerPosesMap& left, const FingerPosesMap& right) { + _leftHandFingerPosesInSensorFramceCache.set(left); + _rightHandFingerPosesInSensorFramceCache.set(right); +} + +MyAvatar::FingerPosesMap MyAvatar::getLeftHandFingerControllerPosesInSensorFrame() const { + return _leftHandFingerPosesInSensorFramceCache.get(); +} + +MyAvatar::FingerPosesMap MyAvatar::getRightHandFingerControllerPosesInSensorFrame() const { + return _rightHandFingerPosesInSensorFramceCache.get(); +} + void MyAvatar::setFootControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right) { - if (controller::InputDevice::getLowVelocityFilter()) { - auto oldLeftPose = getLeftFootControllerPoseInSensorFrame(); - auto oldRightPose = getRightFootControllerPoseInSensorFrame(); - _leftFootControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldLeftPose, left)); - _rightFootControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldRightPose, right)); - } else { - _leftFootControllerPoseInSensorFrameCache.set(left); - _rightFootControllerPoseInSensorFrameCache.set(right); - } + _leftFootControllerPoseInSensorFrameCache.set(left); + _rightFootControllerPoseInSensorFrameCache.set(right); } controller::Pose MyAvatar::getLeftFootControllerPoseInSensorFrame() const { @@ -1384,15 +1502,8 @@ controller::Pose MyAvatar::getRightFootControllerPoseInAvatarFrame() const { } void MyAvatar::setSpineControllerPosesInSensorFrame(const controller::Pose& hips, const controller::Pose& spine2) { - if (controller::InputDevice::getLowVelocityFilter()) { - auto oldHipsPose = getHipsControllerPoseInSensorFrame(); - auto oldSpine2Pose = getSpine2ControllerPoseInSensorFrame(); - _hipsControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldHipsPose, hips)); - _spine2ControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldSpine2Pose, spine2)); - } else { - _hipsControllerPoseInSensorFrameCache.set(hips); - _spine2ControllerPoseInSensorFrameCache.set(spine2); - } + _hipsControllerPoseInSensorFrameCache.set(hips); + _spine2ControllerPoseInSensorFrameCache.set(spine2); } controller::Pose MyAvatar::getHipsControllerPoseInSensorFrame() const { @@ -1422,12 +1533,7 @@ controller::Pose MyAvatar::getSpine2ControllerPoseInAvatarFrame() const { } void MyAvatar::setHeadControllerPoseInSensorFrame(const controller::Pose& head) { - if (controller::InputDevice::getLowVelocityFilter()) { - auto oldHeadPose = getHeadControllerPoseInSensorFrame(); - _headControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldHeadPose, head)); - } else { - _headControllerPoseInSensorFrameCache.set(head); - } + _headControllerPoseInSensorFrameCache.set(head); } controller::Pose MyAvatar::getHeadControllerPoseInSensorFrame() const { @@ -1443,18 +1549,49 @@ controller::Pose MyAvatar::getHeadControllerPoseInAvatarFrame() const { return getHeadControllerPoseInWorldFrame().transform(invAvatarMatrix); } +void MyAvatar::setArmControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right) { + _leftArmControllerPoseInSensorFrameCache.set(left); + _rightArmControllerPoseInSensorFrameCache.set(right); +} + +controller::Pose MyAvatar::getLeftArmControllerPoseInSensorFrame() const { + return _leftArmControllerPoseInSensorFrameCache.get(); +} + +controller::Pose MyAvatar::getRightArmControllerPoseInSensorFrame() const { + return _rightArmControllerPoseInSensorFrameCache.get(); +} + +controller::Pose MyAvatar::getLeftArmControllerPoseInWorldFrame() const { + return getLeftArmControllerPoseInSensorFrame().transform(getSensorToWorldMatrix()); +} + +controller::Pose MyAvatar::getRightArmControllerPoseInWorldFrame() const { + return getRightArmControllerPoseInSensorFrame().transform(getSensorToWorldMatrix()); +} + +controller::Pose MyAvatar::getLeftArmControllerPoseInAvatarFrame() const { + glm::mat4 worldToAvatarMat = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition())); + return getLeftArmControllerPoseInWorldFrame().transform(worldToAvatarMat); +} + +controller::Pose MyAvatar::getRightArmControllerPoseInAvatarFrame() const { + glm::mat4 worldToAvatarMat = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition())); + return getRightArmControllerPoseInWorldFrame().transform(worldToAvatarMat); +} + void MyAvatar::updateMotors() { _characterController.clearMotors(); glm::quat motorRotation; if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { if (_characterController.getState() == CharacterController::State::Hover || _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { - motorRotation = getMyHead()->getCameraOrientation(); + motorRotation = getMyHead()->getHeadOrientation(); } else { // non-hovering = walking: follow camera twist about vertical but not lift // so we decompose camera's rotation and store the twist part in motorRotation glm::quat liftRotation; - swingTwistDecomposition(getMyHead()->getCameraOrientation(), _worldUpDirection, liftRotation, motorRotation); + swingTwistDecomposition(getMyHead()->getHeadOrientation(), _worldUpDirection, liftRotation, motorRotation); } const float DEFAULT_MOTOR_TIMESCALE = 0.2f; const float INVALID_MOTOR_TIMESCALE = 1.0e6f; @@ -1468,7 +1605,7 @@ void MyAvatar::updateMotors() { } if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) { if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { - motorRotation = getMyHead()->getCameraOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); + motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { motorRotation = getOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); } else { @@ -1498,7 +1635,8 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setParentVelocity(parentVelocity); _characterController.setPositionAndOrientation(getPosition(), getOrientation()); - if (qApp->isHMDMode()) { + auto headPose = getHeadControllerPoseInAvatarFrame(); + if (headPose.isValid()) { _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } else { _follow.deactivate(); @@ -1518,6 +1656,10 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { if (_characterController.isEnabledAndReady()) { setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); + if (_characterController.isStuck()) { + _physicsSafetyPending = true; + _goToPosition = getPosition(); + } } else { setVelocity(getVelocity() + _characterController.getFollowVelocity()); } @@ -1663,7 +1805,7 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) { _skeletonModel->reset(); // Why is this necessary? Without this, we crash in the next render. _currentAnimGraphUrl.set(url); - _rig->initAnimGraph(url); + _skeletonModel->getRig().initAnimGraph(url); _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes @@ -1679,7 +1821,7 @@ void MyAvatar::initAnimGraph() { graphUrl = QUrl::fromLocalFile(PathUtils::resourcesPath() + "avatar/avatar-animation.json"); } - _rig->initAnimGraph(graphUrl); + _skeletonModel->getRig().initAnimGraph(graphUrl); _currentAnimGraphUrl.set(graphUrl); _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. @@ -1687,7 +1829,7 @@ void MyAvatar::initAnimGraph() { } void MyAvatar::destroyAnimGraph() { - _rig->destroyAnimGraph(); + _skeletonModel->getRig().destroyAnimGraph(); } void MyAvatar::postUpdate(float deltaTime) { @@ -1703,22 +1845,23 @@ void MyAvatar::postUpdate(float deltaTime) { if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { - auto animSkeleton = _rig->getAnimSkeleton(); + auto animSkeleton = _skeletonModel->getRig().getAnimSkeleton(); // the rig is in the skeletonModel frame AnimPose xform(glm::vec3(1), _skeletonModel->getRotation(), _skeletonModel->getTranslation()); if (_enableDebugDrawDefaultPose && animSkeleton) { glm::vec4 gray(0.2f, 0.2f, 0.2f, 0.2f); - AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarDefaultPoses", animSkeleton, _rig->getAbsoluteDefaultPoses(), xform, gray); + AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarDefaultPoses", animSkeleton, _skeletonModel->getRig().getAbsoluteDefaultPoses(), xform, gray); } if (_enableDebugDrawAnimPose && animSkeleton) { // build absolute AnimPoseVec from rig AnimPoseVec absPoses; - absPoses.reserve(_rig->getJointStateCount()); - for (int i = 0; i < _rig->getJointStateCount(); i++) { - absPoses.push_back(AnimPose(_rig->getJointTransform(i))); + const Rig& rig = _skeletonModel->getRig(); + absPoses.reserve(rig.getJointStateCount()); + for (int i = 0; i < rig.getJointStateCount(); i++) { + absPoses.push_back(AnimPose(rig.getJointTransform(i))); } glm::vec4 cyan(0.1f, 0.6f, 0.6f, 1.0f); AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarAnimPoses", animSkeleton, absPoses, xform, cyan); @@ -1762,15 +1905,14 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { const float RENDER_HEAD_CUTOFF_DISTANCE = 0.3f; -bool MyAvatar::cameraInsideHead() const { - const glm::vec3 cameraPosition = qApp->getCamera().getPosition(); +bool MyAvatar::cameraInsideHead(const glm::vec3& cameraPosition) const { return glm::length(cameraPosition - getHeadPosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getUniformScale()); } bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const { bool defaultMode = renderArgs->_renderMode == RenderArgs::DEFAULT_RENDER_MODE; bool firstPerson = qApp->getCamera().getMode() == CAMERA_MODE_FIRST_PERSON; - bool insideHead = cameraInsideHead(); + bool insideHead = cameraInsideHead(renderArgs->getViewFrustum().getPosition()); return !defaultMode || !firstPerson || !insideHead; } @@ -1806,15 +1948,17 @@ void MyAvatar::updateOrientation(float deltaTime) { // Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll // get an instantaneous 15 degree turn. If you keep holding the key down you'll get another // snap turn every half second. + bool snapTurn = false; if (getDriveKey(STEP_YAW) != 0.0f) { totalBodyYaw += getDriveKey(STEP_YAW); + snapTurn = true; } // use head/HMD orientation to turn while flying if (getCharacterController()->getState() == CharacterController::State::Hover) { // This is the direction the user desires to fly in. - glm::vec3 desiredFacing = getMyHead()->getCameraOrientation() * Vectors::UNIT_Z; + glm::vec3 desiredFacing = getMyHead()->getHeadOrientation() * Vectors::UNIT_Z; desiredFacing.y = 0.0f; // This is our reference frame, it is captured when the user begins to move. @@ -1840,17 +1984,48 @@ void MyAvatar::updateOrientation(float deltaTime) { totalBodyYaw += (speedFactor * deltaAngle * (180.0f / PI)); } + // Use head/HMD roll to turn while walking or flying. + if (qApp->isHMDMode() && _hmdRollControlEnabled) { + // Turn with head roll. + const float MIN_CONTROL_SPEED = 0.01f; + float speed = glm::length(getVelocity()); + if (speed >= MIN_CONTROL_SPEED) { + // Feather turn when stopping moving. + float speedFactor; + if (getDriveKey(TRANSLATE_Z) != 0.0f || _lastDrivenSpeed == 0.0f) { + _lastDrivenSpeed = speed; + speedFactor = 1.0f; + } else { + speedFactor = glm::min(speed / _lastDrivenSpeed, 1.0f); + } + + float direction = glm::dot(getVelocity(), getRotation() * Vectors::UNIT_NEG_Z) > 0.0f ? 1.0f : -1.0f; + + float rollAngle = glm::degrees(asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT))); + float rollSign = rollAngle < 0.0f ? -1.0f : 1.0f; + rollAngle = fabsf(rollAngle); + rollAngle = rollAngle > _hmdRollControlDeadZone ? rollSign * (rollAngle - _hmdRollControlDeadZone) : 0.0f; + + totalBodyYaw += speedFactor * direction * rollAngle * deltaTime * _hmdRollControlRate; + } + } // update body orientation by movement inputs + glm::quat initialOrientation = getOrientationOutbound(); setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f)))); + if (snapTurn) { + // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. + _smoothOrientationInitial = initialOrientation; + _smoothOrientationTarget = getOrientation(); + _smoothOrientationTimer = 0.0f; + } + getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime); - if (qApp->isHMDMode()) { - glm::quat orientation = glm::quat_cast(getSensorToWorldMatrix()) * getHMDSensorOrientation(); - glm::quat bodyOrientation = getWorldBodyOrientation(); - glm::quat localOrientation = glm::inverse(bodyOrientation) * orientation; - + auto headPose = getHeadControllerPoseInAvatarFrame(); + if (headPose.isValid()) { + glm::quat localOrientation = headPose.rotation * Quaternions::Y_180; // these angles will be in radians // ... so they need to be converted to degrees before we do math... glm::vec3 euler = glm::eulerAngles(localOrientation) * DEGREES_PER_RADIAN; @@ -1964,11 +2139,14 @@ void MyAvatar::updatePosition(float deltaTime) { } // capture the head rotation, in sensor space, when the user first indicates they would like to move/fly. - if (!_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) > 0.1f || fabs(getDriveKey(TRANSLATE_X)) > 0.1f)) { + if (!_hoverReferenceCameraFacingIsCaptured && + (fabs(getDriveKey(TRANSLATE_Z)) > 0.1f || fabs(getDriveKey(TRANSLATE_X)) > 0.1f)) { _hoverReferenceCameraFacingIsCaptured = true; // transform the camera facing vector into sensor space. - _hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix), getMyHead()->getCameraOrientation() * Vectors::UNIT_Z); - } else if (_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) <= 0.1f && fabs(getDriveKey(TRANSLATE_X)) <= 0.1f)) { + _hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix), + getMyHead()->getHeadOrientation() * Vectors::UNIT_Z); + } else if (_hoverReferenceCameraFacingIsCaptured && + (fabs(getDriveKey(TRANSLATE_Z)) <= 0.1f && fabs(getDriveKey(TRANSLATE_X)) <= 0.1f)) { _hoverReferenceCameraFacingIsCaptured = false; } } @@ -2049,6 +2227,14 @@ void MyAvatar::clampScaleChangeToDomainLimits(float desiredScale) { qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale); } +float MyAvatar::getDomainMinScale() { + return _domainMinimumScale; +} + +float MyAvatar::getDomainMaxScale() { + return _domainMaximumScale; +} + void MyAvatar::increaseSize() { // make sure we're starting from an allowable scale clampTargetScaleToDomainLimits(); @@ -2179,6 +2365,144 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, emit transformChanged(); } +void MyAvatar::goToLocationAndEnableCollisions(const glm::vec3& position) { // See use case in safeLanding. + goToLocation(position); + QMetaObject::invokeMethod(this, "setCollisionsEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); +} +bool MyAvatar::safeLanding(const glm::vec3& position) { + // Considers all collision hull or non-collisionless primitive intersections on a vertical line through the point. + // There needs to be a "landing" if: + // a) the closest above and the closest below are less than the avatar capsule height apart, or + // b) the above point is the top surface of an entity, indicating that we are inside it. + // If no landing is required, we go to that point directly and return false; + // When a landing is required by a, we find the highest intersection on that closest-agbove entity + // (which may be that same "nearest above intersection"). That highest intersection is the candidate landing point. + // For b, use that top surface point. + // We then place our feet there, recurse with new capsule center point, and return true. + + if (QThread::currentThread() != thread()) { + bool result; + BLOCKING_INVOKE_METHOD(this, "safeLanding", Q_RETURN_ARG(bool, result), Q_ARG(const glm::vec3&, position)); + return result; + } + glm::vec3 better; + if (!requiresSafeLanding(position, better)) { + return false; + } + if (!getCollisionsEnabled()) { + goToLocation(better); // recurses on next update + } else { // If you try to go while stuck, physics will keep you stuck. + setCollisionsEnabled(false); + // Don't goToLocation just yet. Yield so that physics can act on the above. + QMetaObject::invokeMethod(this, "goToLocationAndEnableCollisions", Qt::QueuedConnection, // The equivalent of javascript nextTick + Q_ARG(glm::vec3, better)); + } + return true; +} + +// If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut. +bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) { + // We begin with utilities and tests. The Algorithm in four parts is below. + auto halfHeight = _characterController.getCapsuleHalfHeight() + _characterController.getCapsuleRadius(); + if (halfHeight == 0) { + return false; // zero height avatar + } + auto entityTree = DependencyManager::get()->getTree(); + if (!entityTree) { + return false; // no entity tree + } + // More utilities. + const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset(); + const auto capsuleCenter = positionIn + offset; + const auto up = _worldUpDirection, down = -up; + glm::vec3 upperIntersection, upperNormal, lowerIntersection, lowerNormal; + EntityItemID upperId, lowerId; + QVector include{}, ignore{}; + auto mustMove = [&] { // Place bottom of capsule at the upperIntersection, and check again based on the capsule center. + betterPositionOut = upperIntersection + (up * halfHeight) - offset; + return true; + }; + auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) { + OctreeElementPointer element; + EntityItemPointer intersectedEntity = NULL; + float distance; + BoxFace face; + const bool visibleOnly = false; + // This isn't quite what we really want here. findRayIntersection always works on mesh, skipping entirely based on collidable. + // What we really want is to use the collision hull! + // See https://highfidelity.fogbugz.com/f/cases/5003/findRayIntersection-has-option-to-use-collidableOnly-but-doesn-t-actually-use-colliders + const bool collidableOnly = true; + const bool precisionPicking = true; + const auto lockType = Octree::Lock; // Should we refactor to take a lock just once? + bool* accurateResult = NULL; + + bool intersects = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking, + element, distance, face, normalOut, (void**)&intersectedEntity, lockType, accurateResult); + if (!intersects || !intersectedEntity) { + return false; + } + intersectionOut = startPointIn + (directionIn * distance); + entityIdOut = intersectedEntity->getEntityItemID(); + return true; + }; + + // The Algorithm, in four parts: + + if (!findIntersection(capsuleCenter, up, upperIntersection, upperId, upperNormal)) { + // We currently believe that physics will reliably push us out if our feet are embedded, + // as long as our capsule center is out and there's room above us. Here we have those + // conditions, so no need to check our feet below. + return false; // nothing above + } + + if (!findIntersection(capsuleCenter, down, lowerIntersection, lowerId, lowerNormal)) { + // Our head may be embedded, but our center is out and there's room below. See corresponding comment above. + return false; // nothing below + } + + // See if we have room between entities above and below, but that we are not contained. + // First check if the surface above us is the bottom of something, and the surface below us it the top of something. + // I.e., we are in a clearing between two objects. + if (isDown(upperNormal) && isUp(lowerNormal)) { + auto spaceBetween = glm::distance(upperIntersection, lowerIntersection); + const float halfHeightFactor = 2.5f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise. + if (spaceBetween > (halfHeightFactor * halfHeight)) { + // There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below. + // We're now going to iterate upwards through successive upperIntersections, testing to see if we're contained within the top surface of some entity. + // There will be one of two outcomes: + // a) We're not contained, so we have enough room and our position is good. + // b) We are contained, so we'll bail out of this but try again at a position above the containing entity. + const int iterationLimit = 1000; + for (int counter = 0; counter < iterationLimit; counter++) { + ignore.push_back(upperId); + if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) { + // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good! + return false; // enough room + } + if (isUp(upperNormal)) { + // This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it. + // We could break here and recurse from the top of the original ceiling, but since we've already done the work to find the top + // of the enclosing entity, let's put our feet at upperIntersection and start over. + return mustMove(); + } + // We found a new bottom surface, which we're not interested in. + // But there could still be a top surface above us for an entity we haven't seen, so keep looking upward. + } + qCDebug(interfaceapp) << "Loop in requiresSafeLanding. Floor/ceiling do not make sense."; + } + } + + include.push_back(upperId); // We're now looking for the intersection from above onto this entity. + const float big = (float)TREE_SCALE; + const auto skyHigh = up * big; + auto fromAbove = capsuleCenter + skyHigh; + if (!findIntersection(fromAbove, down, upperIntersection, upperId, upperNormal)) { + return false; // Unable to find a landing + } + // Our arbitrary rule is to always go up. There's no need to look down or sideways for a "closer" safe candidate. + return mustMove(); +} + void MyAvatar::updateMotionBehaviorFromMenu() { if (QThread::currentThread() != thread()) { @@ -2200,6 +2524,45 @@ void MyAvatar::updateMotionBehaviorFromMenu() { setCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions)); } +void MyAvatar::setFlyingEnabled(bool enabled) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setFlyingEnabled", Q_ARG(bool, enabled)); + return; + } + + _enableFlying = enabled; +} + +bool MyAvatar::isFlying() { + // Avatar is Flying, and is not Falling, or Taking off + return _characterController.getState() == CharacterController::State::Hover; +} + +bool MyAvatar::isInAir() { + // If Avatar is Hover, Falling, or Taking off, they are in Air. + return _characterController.getState() != CharacterController::State::Ground; +} + +bool MyAvatar::getFlyingEnabled() { + // May return true even if client is not allowed to fly in the zone. + return _enableFlying; +} + +// Public interface for targetscale +float MyAvatar::getAvatarScale() { + return getTargetScale(); +} + +void MyAvatar::setAvatarScale(float val) { + + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setAvatarScale", Q_ARG(float, val)); + return; + } + + setTargetScale(val); +} + void MyAvatar::setCollisionsEnabled(bool enabled) { if (QThread::currentThread() != thread()) { @@ -2282,35 +2645,27 @@ bool MyAvatar::isDriveKeyDisabled(DriveKeys key) const { } } -glm::vec3 MyAvatar::getWorldBodyPosition() const { - return transformPoint(_sensorToWorldMatrix, extractTranslation(_bodySensorMatrix)); -} - -glm::quat MyAvatar::getWorldBodyOrientation() const { - return glm::quat_cast(_sensorToWorldMatrix * _bodySensorMatrix); -} - // old school meat hook style glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { - - // HMD is in sensor space. - const glm::vec3 hmdPosition = getHMDSensorPosition(); - const glm::quat hmdOrientation = getHMDSensorOrientation(); - const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation); - - int rightEyeIndex = _rig->indexOfJoint("RightEye"); - int leftEyeIndex = _rig->indexOfJoint("LeftEye"); - int neckIndex = _rig->indexOfJoint("Neck"); - int hipsIndex = _rig->indexOfJoint("Hips"); - - glm::vec3 rigMiddleEyePos = DEFAULT_AVATAR_MIDDLE_EYE_POS; - if (leftEyeIndex >= 0 && rightEyeIndex >= 0) { - rigMiddleEyePos = (_rig->getAbsoluteDefaultPose(leftEyeIndex).trans() + _rig->getAbsoluteDefaultPose(rightEyeIndex).trans()) / 2.0f; + glm::vec3 headPosition; + glm::quat headOrientation; + auto headPose = getHeadControllerPoseInSensorFrame(); + if (headPose.isValid()) { + headPosition = getHeadControllerPoseInSensorFrame().translation; + headOrientation = getHeadControllerPoseInSensorFrame().rotation * Quaternions::Y_180; } - glm::vec3 rigNeckPos = neckIndex != -1 ? _rig->getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS; - glm::vec3 rigHipsPos = hipsIndex != -1 ? _rig->getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS; + const glm::quat headOrientationYawOnly = cancelOutRollAndPitch(headOrientation); - glm::vec3 localEyes = (rigMiddleEyePos - rigHipsPos); + const Rig& rig = _skeletonModel->getRig(); + int headIndex = rig.indexOfJoint("Head"); + int neckIndex = rig.indexOfJoint("Neck"); + int hipsIndex = rig.indexOfJoint("Hips"); + + glm::vec3 rigHeadPos = headIndex != -1 ? rig.getAbsoluteDefaultPose(headIndex).trans() : DEFAULT_AVATAR_HEAD_POS; + glm::vec3 rigNeckPos = neckIndex != -1 ? rig.getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS; + glm::vec3 rigHipsPos = hipsIndex != -1 ? rig.getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS; + + glm::vec3 localHead = (rigHeadPos - rigHipsPos); glm::vec3 localNeck = (rigNeckPos - rigHipsPos); // apply simplistic head/neck model @@ -2319,11 +2674,11 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { // eyeToNeck offset is relative full HMD orientation. // while neckToRoot offset is only relative to HMDs yaw. // Y_180 is necessary because rig is z forward and hmdOrientation is -z forward - glm::vec3 eyeToNeck = hmdOrientation * Quaternions::Y_180 * (localNeck - localEyes); - glm::vec3 neckToRoot = hmdOrientationYawOnly * Quaternions::Y_180 * -localNeck; - glm::vec3 bodyPos = hmdPosition + eyeToNeck + neckToRoot; + glm::vec3 headToNeck = headOrientation * Quaternions::Y_180 * (localNeck - localHead); + glm::vec3 neckToRoot = headOrientationYawOnly * Quaternions::Y_180 * -localNeck; + glm::vec3 bodyPos = headPosition + headToNeck + neckToRoot; - return createMatFromQuatAndPos(hmdOrientationYawOnly, bodyPos); + return createMatFromQuatAndPos(headOrientationYawOnly, bodyPos); } glm::vec3 MyAvatar::getPositionForAudio() { @@ -2439,7 +2794,7 @@ bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, co } else { const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); - return glm::dot(myAvatar.getHMDSensorFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; + return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; } } @@ -2476,11 +2831,11 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); } -void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { - _desiredBodyMatrix = desiredBodyMatrix; +void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, bool hasDriveInput) { if (myAvatar.getHMDLeanRecenterEnabled()) { - if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { + if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Rotation); } if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { @@ -2491,7 +2846,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } } - glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix; + glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * desiredBodyMatrix; glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix; AnimPose followWorldPose(currentWorldMatrix); @@ -2586,9 +2941,10 @@ glm::mat4 MyAvatar::computeCameraRelativeHandControllerMatrix(const glm::mat4& c cameraWorldMatrix *= createMatFromScaleQuatAndPos(vec3(-1.0f, 1.0f, 1.0f), glm::quat(), glm::vec3()); } - // compute a NEW sensorToWorldMatrix for the camera. The equation is cameraWorldMatrix = cameraSensorToWorldMatrix * _hmdSensorMatrix. + // compute a NEW sensorToWorldMatrix for the camera. + // The equation is cameraWorldMatrix = cameraSensorToWorldMatrix * _hmdSensorMatrix. // here we solve for the unknown cameraSensorToWorldMatrix. - glm::mat4 cameraSensorToWorldMatrix = cameraWorldMatrix * glm::inverse(_hmdSensorMatrix); + glm::mat4 cameraSensorToWorldMatrix = cameraWorldMatrix * glm::inverse(getHMDSensorMatrix()); // Using the new cameraSensorToWorldMatrix, compute where the controller is in world space. glm::mat4 controllerWorldMatrix = cameraSensorToWorldMatrix * controllerSensorMatrix; @@ -2674,74 +3030,119 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const { glm::mat4 MyAvatar::getCenterEyeCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int rightEyeIndex = _rig->indexOfJoint("RightEye"); - int leftEyeIndex = _rig->indexOfJoint("LeftEye"); + int rightEyeIndex = _skeletonModel->getRig().indexOfJoint("RightEye"); + int leftEyeIndex = _skeletonModel->getRig().indexOfJoint("LeftEye"); if (rightEyeIndex >= 0 && leftEyeIndex >= 0) { auto centerEyePos = (getAbsoluteDefaultJointTranslationInObjectFrame(rightEyeIndex) + getAbsoluteDefaultJointTranslationInObjectFrame(leftEyeIndex)) * 0.5f; auto centerEyeRot = Quaternions::Y_180; return createMatFromQuatAndPos(centerEyeRot, centerEyePos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_POS, DEFAULT_AVATAR_MIDDLE_EYE_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_ROT, DEFAULT_AVATAR_MIDDLE_EYE_POS); } } glm::mat4 MyAvatar::getHeadCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int headIndex = _rig->indexOfJoint("Head"); + int headIndex = _skeletonModel->getRig().indexOfJoint("Head"); if (headIndex >= 0) { auto headPos = getAbsoluteDefaultJointTranslationInObjectFrame(headIndex); auto headRot = getAbsoluteDefaultJointRotationInObjectFrame(headIndex); return createMatFromQuatAndPos(headRot, headPos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_POS, DEFAULT_AVATAR_HEAD_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_ROT, DEFAULT_AVATAR_HEAD_POS); } } glm::mat4 MyAvatar::getSpine2CalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int spine2Index = _rig->indexOfJoint("Spine2"); + int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2"); if (spine2Index >= 0) { auto spine2Pos = getAbsoluteDefaultJointTranslationInObjectFrame(spine2Index); auto spine2Rot = getAbsoluteDefaultJointRotationInObjectFrame(spine2Index); return createMatFromQuatAndPos(spine2Rot, spine2Pos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_SPINE2_POS, DEFAULT_AVATAR_SPINE2_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_SPINE2_ROT, DEFAULT_AVATAR_SPINE2_POS); } } glm::mat4 MyAvatar::getHipsCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int hipsIndex = _rig->indexOfJoint("Hips"); + int hipsIndex = _skeletonModel->getRig().indexOfJoint("Hips"); if (hipsIndex >= 0) { auto hipsPos = getAbsoluteDefaultJointTranslationInObjectFrame(hipsIndex); auto hipsRot = getAbsoluteDefaultJointRotationInObjectFrame(hipsIndex); return createMatFromQuatAndPos(hipsRot, hipsPos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_HIPS_POS, DEFAULT_AVATAR_HIPS_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_HIPS_ROT, DEFAULT_AVATAR_HIPS_POS); } } glm::mat4 MyAvatar::getLeftFootCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int leftFootIndex = _rig->indexOfJoint("LeftFoot"); + int leftFootIndex = _skeletonModel->getRig().indexOfJoint("LeftFoot"); if (leftFootIndex >= 0) { auto leftFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftFootIndex); auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex); return createMatFromQuatAndPos(leftFootRot, leftFootPos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_POS, DEFAULT_AVATAR_LEFTFOOT_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS); } } glm::mat4 MyAvatar::getRightFootCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int rightFootIndex = _rig->indexOfJoint("RightFoot"); + int rightFootIndex = _skeletonModel->getRig().indexOfJoint("RightFoot"); if (rightFootIndex >= 0) { auto rightFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightFootIndex); auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex); return createMatFromQuatAndPos(rightFootRot, rightFootPos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_POS, DEFAULT_AVATAR_RIGHTFOOT_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS); + } +} + + +glm::mat4 MyAvatar::getRightArmCalibrationMat() const { + int rightArmIndex = _skeletonModel->getRig().indexOfJoint("RightArm"); + if (rightArmIndex >= 0) { + auto rightArmPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightArmIndex); + auto rightArmRot = getAbsoluteDefaultJointRotationInObjectFrame(rightArmIndex); + return createMatFromQuatAndPos(rightArmRot, rightArmPos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS); + } +} + +glm::mat4 MyAvatar::getLeftArmCalibrationMat() const { + int leftArmIndex = _skeletonModel->getRig().indexOfJoint("LeftArm"); + if (leftArmIndex >= 0) { + auto leftArmPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftArmIndex); + auto leftArmRot = getAbsoluteDefaultJointRotationInObjectFrame(leftArmIndex); + return createMatFromQuatAndPos(leftArmRot, leftArmPos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS); + } +} + +glm::mat4 MyAvatar::getRightHandCalibrationMat() const { + int rightHandIndex = _skeletonModel->getRig().indexOfJoint("RightHand"); + if (rightHandIndex >= 0) { + auto rightHandPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightHandIndex); + auto rightHandRot = getAbsoluteDefaultJointRotationInObjectFrame(rightHandIndex); + return createMatFromQuatAndPos(rightHandRot, rightHandPos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTHAND_ROT, DEFAULT_AVATAR_RIGHTHAND_POS); + } +} + +glm::mat4 MyAvatar::getLeftHandCalibrationMat() const { + int leftHandIndex = _skeletonModel->getRig().indexOfJoint("LeftHand"); + if (leftHandIndex >= 0) { + auto leftHandPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftHandIndex); + auto leftHandRot = getAbsoluteDefaultJointRotationInObjectFrame(leftHandIndex); + return createMatFromQuatAndPos(leftHandRot, leftHandPos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTHAND_ROT, DEFAULT_AVATAR_LEFTHAND_POS); } } @@ -2755,7 +3156,7 @@ bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& o slamPosition(position); setOrientation(orientation); - _rig->setMaxHipsOffsetLength(0.05f); + _skeletonModel->getRig().setMaxHipsOffsetLength(0.05f); auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index); if (it == _pinnedJoints.end()) { @@ -2772,7 +3173,7 @@ bool MyAvatar::clearPinOnJoint(int index) { auto hipsIndex = getJointIndex("Hips"); if (index == hipsIndex) { - _rig->setMaxHipsOffsetLength(FLT_MAX); + _skeletonModel->getRig().setMaxHipsOffsetLength(FLT_MAX); } return true; @@ -2781,7 +3182,7 @@ bool MyAvatar::clearPinOnJoint(int index) { } float MyAvatar::getIKErrorOnLastSolve() const { - return _rig->getIKErrorOnLastSolve(); + return _skeletonModel->getRig().getIKErrorOnLastSolve(); } // thread-safe @@ -2817,6 +3218,6 @@ void MyAvatar::updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& } } -const MyHead* MyAvatar::getMyHead() const { - return static_cast(getHead()); +const MyHead* MyAvatar::getMyHead() const { + return static_cast(getHead()); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 04fa37cb1d..648a5b5f29 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -56,6 +56,7 @@ class MyAvatar : public Avatar { * * @namespace MyAvatar * @augments Avatar + * @property qmlPosition {Vec3} Used as a stopgap for position access by QML, as glm::vec3 is unavailable outside of scripts * @property shouldRenderLocally {bool} Set it to true if you would like to see MyAvatar in your local interface, * and false if you would not like to see MyAvatar in your local interface. * @property motorVelocity {Vec3} Can be used to move the avatar with this velocity. @@ -101,6 +102,10 @@ class MyAvatar : public Avatar { * "scripts/system/controllers/toggleAdvancedMovementForHandControllers.js". */ + // FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type + Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition) + QVector3D getQmlPosition() { auto p = getPosition(); return QVector3D(p.x, p.y, p.z); } + Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally) Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity) Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale) @@ -125,13 +130,17 @@ class MyAvatar : public Avatar { Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose) Q_PROPERTY(float energy READ getEnergy WRITE setEnergy) - Q_PROPERTY(float isAway READ getIsAway WRITE setAway) + Q_PROPERTY(bool isAway READ getIsAway WRITE setAway) Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled) Q_PROPERTY(bool collisionsEnabled READ getCollisionsEnabled WRITE setCollisionsEnabled) Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls) + Q_PROPERTY(bool hmdRollControlEnabled READ getHMDRollControlEnabled WRITE setHMDRollControlEnabled) + Q_PROPERTY(float hmdRollControlDeadZone READ getHMDRollControlDeadZone WRITE setHMDRollControlDeadZone) + Q_PROPERTY(float hmdRollControlRate READ getHMDRollControlRate WRITE setHMDRollControlRate) + public: enum DriveKeys { TRANSLATE_X = 0, @@ -148,7 +157,7 @@ public: }; Q_ENUM(DriveKeys) - explicit MyAvatar(QThread* thread, RigPointer rig); + explicit MyAvatar(QThread* thread); ~MyAvatar(); void instantiableAvatar() override {}; @@ -185,11 +194,12 @@ public: const glm::mat4& getHMDSensorMatrix() const { return _hmdSensorMatrix; } const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; } const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; } - const glm::vec2& getHMDSensorFacingMovingAverage() const { return _hmdSensorFacingMovingAverage; } Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar); Q_INVOKABLE QVariant getOrientationVar() const; + // A method intended to be overriden by MyAvatar for polling orientation for network transmission. + glm::quat getOrientationOutbound() const override; // Pass a recent sample of the HMD to the avatar. // This can also update the avatar's position to follow the HMD @@ -320,9 +330,9 @@ public: // adding one of the other handlers. While any handler may change a value in animStateDictionaryIn (or supply different values in animStateDictionaryOut) // a handler must not remove properties from animStateDictionaryIn, nor change property values that it does not intend to change. // It is not specified in what order multiple handlers are called. - Q_INVOKABLE QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { return _rig->addAnimationStateHandler(handler, propertiesList); } + Q_INVOKABLE QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { return _skeletonModel->getRig().addAnimationStateHandler(handler, propertiesList); } // Removes a handler previously added by addAnimationStateHandler. - Q_INVOKABLE void removeAnimationStateHandler(QScriptValue handler) { _rig->removeAnimationStateHandler(handler); } + Q_INVOKABLE void removeAnimationStateHandler(QScriptValue handler) { _skeletonModel->getRig().removeAnimationStateHandler(handler); } Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; } Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } @@ -336,6 +346,13 @@ public: void setUseAdvancedMovementControls(bool useAdvancedMovementControls) { _useAdvancedMovementControls.set(useAdvancedMovementControls); } + void setHMDRollControlEnabled(bool value) { _hmdRollControlEnabled = value; } + bool getHMDRollControlEnabled() const { return _hmdRollControlEnabled; } + void setHMDRollControlDeadZone(float value) { _hmdRollControlDeadZone = value; } + float getHMDRollControlDeadZone() const { return _hmdRollControlDeadZone; } + void setHMDRollControlRate(float value) { _hmdRollControlRate = value; } + float getHMDRollControlRate() const { return _hmdRollControlRate; } + // get/set avatar data void saveData(); void loadData(); @@ -349,7 +366,7 @@ public: float getDriveKey(DriveKeys key) const; Q_INVOKABLE float getRawDriveKey(DriveKeys key) const; void relayDriveKeysToCharacterController(); - + Q_INVOKABLE void disableDriveKey(DriveKeys key); Q_INVOKABLE void enableDriveKey(DriveKeys key); Q_INVOKABLE bool isDriveKeyDisabled(DriveKeys key) const; @@ -377,6 +394,15 @@ public: Q_INVOKABLE controller::Pose getLeftHandTipPose() const; Q_INVOKABLE controller::Pose getRightHandTipPose() const; + // world-space to avatar-space rigconversion functions + Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const; + Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const; + Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const; + + Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const; + Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const; + Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; + AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; } void updateLookAtTargetAvatar(); void clearLookAtTargetAvatar(); @@ -448,6 +474,11 @@ public: controller::Pose getLeftHandControllerPoseInAvatarFrame() const; controller::Pose getRightHandControllerPoseInAvatarFrame() const; + typedef std::map> FingerPosesMap; + void setFingerControllerPosesInSensorFrame(const FingerPosesMap& left, const FingerPosesMap& right); + FingerPosesMap getLeftHandFingerControllerPosesInSensorFrame() const; + FingerPosesMap getRightHandFingerControllerPosesInSensorFrame() const; + void setFootControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right); controller::Pose getLeftFootControllerPoseInSensorFrame() const; controller::Pose getRightFootControllerPoseInSensorFrame() const; @@ -468,9 +499,27 @@ public: controller::Pose getHeadControllerPoseInSensorFrame() const; controller::Pose getHeadControllerPoseInWorldFrame() const; controller::Pose getHeadControllerPoseInAvatarFrame() const; + const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; } + + + void setArmControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right); + controller::Pose getLeftArmControllerPoseInSensorFrame() const; + controller::Pose getRightArmControllerPoseInSensorFrame() const; + controller::Pose getLeftArmControllerPoseInWorldFrame() const; + controller::Pose getRightArmControllerPoseInWorldFrame() const; + controller::Pose getLeftArmControllerPoseInAvatarFrame() const; + controller::Pose getRightArmControllerPoseInAvatarFrame() const; bool hasDriveInput() const; + Q_INVOKABLE bool isFlying(); + Q_INVOKABLE bool isInAir(); + Q_INVOKABLE void setFlyingEnabled(bool enabled); + Q_INVOKABLE bool getFlyingEnabled(); + + Q_INVOKABLE float getAvatarScale(); + Q_INVOKABLE void setAvatarScale(float scale); + Q_INVOKABLE void setCollisionsEnabled(bool enabled); Q_INVOKABLE bool getCollisionsEnabled(); Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); // deprecated @@ -486,6 +535,10 @@ public: glm::mat4 getHipsCalibrationMat() const; glm::mat4 getLeftFootCalibrationMat() const; glm::mat4 getRightFootCalibrationMat() const; + glm::mat4 getRightArmCalibrationMat() const; + glm::mat4 getLeftArmCalibrationMat() const; + glm::mat4 getLeftHandCalibrationMat() const; + glm::mat4 getRightHandCalibrationMat() const; void addHoldAction(AvatarActionHold* holdAction); // thread-safe void removeHoldAction(AvatarActionHold* holdAction); // thread-safe @@ -495,15 +548,23 @@ public: // results are in HMD frame glm::mat4 deriveBodyFromHMDSensor() const; + Q_INVOKABLE bool isUp(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) > 0.0f; }; // true iff direction points up wrt avatar's definition of up. + Q_INVOKABLE bool isDown(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) < 0.0f; }; + + public slots: void increaseSize(); void decreaseSize(); void resetSize(); + float getDomainMinScale(); + float getDomainMaxScale(); void goToLocation(const glm::vec3& newPosition, bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(), bool shouldFaceLocation = false); void goToLocation(const QVariant& properties); + void goToLocationAndEnableCollisions(const glm::vec3& newPosition); + bool safeLanding(const glm::vec3& position); void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject); void clearScaleRestriction(); @@ -521,6 +582,9 @@ public slots: void setEnableDebugDrawHandControllers(bool isEnabled); void setEnableDebugDrawSensorToWorldMatrix(bool isEnabled); void setEnableDebugDrawIKTargets(bool isEnabled); + void setEnableDebugDrawIKConstraints(bool isEnabled); + void setEnableDebugDrawIKChains(bool isEnabled); + bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); } void setEnableMeshVisible(bool isEnabled); void setUseAnimPreAndPostRotations(bool isEnabled); @@ -544,14 +608,13 @@ signals: void onLoadComplete(); void wentAway(); void wentActive(); + void skeletonChanged(); private: - glm::vec3 getWorldBodyPosition() const; - glm::quat getWorldBodyOrientation() const; + bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut); - - virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override; + virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override; void simulate(float deltaTime); void updateFromTrackers(float deltaTime); @@ -574,7 +637,7 @@ private: float scale = 1.0f, bool isSoft = false, bool allowDuplicates = false, bool useSaved = true) override; - bool cameraInsideHead() const; + bool cameraInsideHead(const glm::vec3& cameraPosition) const; void updateEyeContactTarget(float deltaTime); @@ -595,6 +658,7 @@ private: std::array _driveKeys; std::bitset _disabledDriveKeys; + bool _enableFlying { true }; bool _wasPushing { false }; bool _isPushing { false }; bool _isBeingPushed { false }; @@ -632,6 +696,14 @@ private: Setting::Handle _realWorldFieldOfView; Setting::Handle _useAdvancedMovementControls; + // Smoothing. + const float SMOOTH_TIME_ORIENTATION = 0.5f; + + // Smoothing data for blending from one position/orientation to another on remote agents. + float _smoothOrientationTimer; + glm::quat _smoothOrientationInitial; + glm::quat _smoothOrientationTarget; + // private methods void updateOrientation(float deltaTime); void updateActionMotor(float deltaTime); @@ -649,16 +721,23 @@ private: bool _useSnapTurn { true }; bool _clearOverlayWhenMoving { true }; + const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // deg + const float ROLL_CONTROL_RATE_DEFAULT = 2.5f; // deg/sec/deg + bool _hmdRollControlEnabled { true }; + float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT }; + float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT }; + float _lastDrivenSpeed { 0.0f }; + // working copies -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access glm::mat4 _sensorToWorldMatrix { glm::mat4() }; - // cache of the current HMD sensor position and orientation - // in sensor space. + // cache of the current HMD sensor position and orientation in sensor space. glm::mat4 _hmdSensorMatrix; glm::quat _hmdSensorOrientation; glm::vec3 _hmdSensorPosition; - glm::vec2 _hmdSensorFacing; // facing vector in xz plane - glm::vec2 _hmdSensorFacingMovingAverage { 0, 0 }; // facing vector in xz plane + // cache head controller pose in sensor space + glm::vec2 _headControllerFacing; // facing vector in xz plane + glm::vec2 _headControllerFacingMovingAverage { 0, 0 }; // facing vector in xz plane // cache of the current body position and orientation of the avatar's body, // in sensor space. @@ -673,7 +752,6 @@ private: Vertical, NumFollowTypes }; - glm::mat4 _desiredBodyMatrix; float _timeRemaining[NumFollowTypes]; void deactivate(); @@ -692,12 +770,12 @@ private: }; FollowHelper _follow; - bool _goToPending; + bool _goToPending { false }; + bool _physicsSafetyPending { false }; glm::vec3 _goToPosition; glm::quat _goToOrientation; std::unordered_set _headBoneSet; - RigPointer _rig; bool _prevShouldDrawHead; bool _rigEnabled { true }; @@ -706,6 +784,8 @@ private: bool _enableDebugDrawHandControllers { false }; bool _enableDebugDrawSensorToWorldMatrix { false }; bool _enableDebugDrawIKTargets { false }; + bool _enableDebugDrawIKConstraints { false }; + bool _enableDebugDrawIKChains { false }; AudioListenerMode _audioListenerMode; glm::vec3 _customListenPosition; @@ -719,14 +799,17 @@ private: // These are stored in SENSOR frame ThreadSafeValueCache _leftHandControllerPoseInSensorFrameCache { controller::Pose() }; ThreadSafeValueCache _rightHandControllerPoseInSensorFrameCache { controller::Pose() }; - ThreadSafeValueCache _leftFootControllerPoseInSensorFrameCache{ controller::Pose() }; - ThreadSafeValueCache _rightFootControllerPoseInSensorFrameCache{ controller::Pose() }; - ThreadSafeValueCache _hipsControllerPoseInSensorFrameCache{ controller::Pose() }; - ThreadSafeValueCache _spine2ControllerPoseInSensorFrameCache{ controller::Pose() }; - ThreadSafeValueCache _headControllerPoseInSensorFrameCache{ controller::Pose() }; + ThreadSafeValueCache _leftHandFingerPosesInSensorFramceCache { }; + ThreadSafeValueCache _rightHandFingerPosesInSensorFramceCache { }; + ThreadSafeValueCache _leftFootControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _rightFootControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _hipsControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _spine2ControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _headControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _leftArmControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _rightArmControllerPoseInSensorFrameCache { controller::Pose() }; bool _hmdLeanRecenterEnabled = true; - AnimPose _prePhysicsRoomPose; std::mutex _holdActionsMutex; std::vector _holdActions; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 9c1b3e8c70..acbfc8edf3 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -184,14 +184,10 @@ void setupPreferences() { { auto getter = [=]()->float { return myAvatar->getUniformScale(); }; auto setter = [=](float value) { myAvatar->setTargetScale(value); }; - - auto scaleSpinner = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); - scaleSpinner->setMin(0.01f); - scaleSpinner->setMax(99.9f); - scaleSpinner->setDecimals(2); - scaleSpinner->setStep(1); - - preferences->addPreference(scaleSpinner); + auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); + preference->setStep(0.1); + preference->setDecimals(2); + preferences->addPreference(preference); } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index 271df58951..6093cd3c8a 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -52,7 +52,7 @@ public: Browsable, Slider, Spinner, - SpinnerSlider, + SpinnerSlider, Checkbox, Button, ComboBox, From 5329c1ce420d93d362b8ccb6ac674a2771f09dc0 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 19:23:28 +0100 Subject: [PATCH 03/13] Fixed Indentation, removed old debug code --- interface/resources/qml/dialogs/preferences/Section.qml | 4 +--- .../qml/dialogs/preferences/SpinnerSliderPreference.qml | 4 ++-- interface/src/ui/PreferencesDialog.cpp | 6 +++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index a2bfa9ba0e..95372cf9d0 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -72,7 +72,7 @@ Preference { property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } - property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } + property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } property var preferences: [] property int checkBoxCount: 0 @@ -128,13 +128,11 @@ Preference { case Preference.ComboBox: checkBoxCount = 0; builder = comboBoxBuilder; - console.log("Built COMBOBOX"); break; case Preference.SpinnerSlider: checkBoxCount = 0; builder = spinnerSliderBuilder; - console.log("Built SPINNERSLIDER"); break; }; diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index 5b9e70a42a..13a4696fd3 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -60,7 +60,7 @@ Preference { width: 100 minimumValue: MyAvatar.getDomainMinScale() maximumValue: MyAvatar.getDomainMaxScale() - stepSize: preference.step + stepSize: preference.step onValueChanged: { spinner.value = value } @@ -72,7 +72,7 @@ Preference { SpinBox { id: spinner decimals: preference.decimals - value: preference.value + value: preference.value minimumValue: MyAvatar.getDomainMinScale() maximumValue: MyAvatar.getDomainMaxScale() width: 100 diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index acbfc8edf3..558f6fd1b8 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -182,9 +182,9 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = [=]()->float { return myAvatar->getUniformScale(); }; - auto setter = [=](float value) { myAvatar->setTargetScale(value); }; - auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); + auto getter = [=]()->float { return myAvatar->getUniformScale(); }; + auto setter = [=](float value) { myAvatar->setTargetScale(value); }; + auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); preference->setStep(0.1); preference->setDecimals(2); preferences->addPreference(preference); From 092725ad1a200cc5d78f50f00bb7d405fd6ba19c Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 19:24:30 +0100 Subject: [PATCH 04/13] a thing --- interface/resources/qml/dialogs/preferences/Section.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index 95372cf9d0..2b90d402bc 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -72,7 +72,7 @@ Preference { property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } - property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } + property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } property var preferences: [] property int checkBoxCount: 0 From 247e7b8bed5a6b9b4969d54b604dc7d1c7878954 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 19:25:31 +0100 Subject: [PATCH 05/13] indentation!!! --- .../qml/hifi/tablet/tabletWindows/preferences/Section.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml index 25279bb6bf..af1fbd0070 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml @@ -81,7 +81,7 @@ Preference { property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } - property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } + property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } property var preferences: [] property int checkBoxCount: 0 From 677b177dce38c63230930326adf0792ebac0fe1a Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 20:29:52 +0100 Subject: [PATCH 06/13] Build error fix --- interface/src/ui/PreferencesDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 558f6fd1b8..e80b7f7f34 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -185,7 +185,7 @@ void setupPreferences() { auto getter = [=]()->float { return myAvatar->getUniformScale(); }; auto setter = [=](float value) { myAvatar->setTargetScale(value); }; auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); - preference->setStep(0.1); + preference->setStep(0.1f); preference->setDecimals(2); preferences->addPreference(preference); } From 60300f5d7b7c36dc25fb761787d4eea00bbd82a8 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 22:06:44 +0100 Subject: [PATCH 07/13] changed to icon (hopefully) --- .../qml/dialogs/preferences/SpinnerSliderPreference.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index 13a4696fd3..ea8109397e 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -97,7 +97,8 @@ Preference { } } width: 50 - text: "Reset" + font.family: hiFiGlyphs.name + text: "a" anchors { right: parent.right } From d74c834f1237c96326900112d7648ad69671fc61 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 22:40:37 +0100 Subject: [PATCH 08/13] Fixed colour scheming and changed to a reload icon --- .../qml/dialogs/preferences/SpinnerSliderPreference.qml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index ea8109397e..85caa1befc 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -68,6 +68,7 @@ Preference { right: spinner.left rightMargin: 10 } + colorScheme: hifi.colorSchemes.dark } SpinBox { id: spinner @@ -85,7 +86,7 @@ Preference { } colorScheme: hifi.colorSchemes.dark } - Button { + GlyphButton { id: button onClicked: { if(spinner.maximumValue >= 1) { @@ -96,12 +97,12 @@ Preference { slider.value = spinner.maximumValue } } - width: 50 - font.family: hiFiGlyphs.name - text: "a" + width: 30 + glyph: hifi.glyphs.reload anchors { right: parent.right } + colorScheme: hifi.colorSchemes.dark } } } \ No newline at end of file From 72c3e789e9f399d1e0f8e5c1cf268566e5f30b0e Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 23:42:20 +0100 Subject: [PATCH 09/13] More Spaces! --- interface/src/ui/PreferencesDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index e80b7f7f34..4db83aab7a 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -181,14 +181,14 @@ void setupPreferences() { preference->setStep(1); preferences->addPreference(preference); } - { + { auto getter = [=]()->float { return myAvatar->getUniformScale(); }; auto setter = [=](float value) { myAvatar->setTargetScale(value); }; auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); preference->setStep(0.1f); preference->setDecimals(2); preferences->addPreference(preference); - } + } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; auto setter = [](float value) { DependencyManager::get()->setEyeClosingThreshold(value); }; From 4f8d958831e3556255d5cc0c9bcfba29b150ab38 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 14 Jul 2017 00:56:42 +0100 Subject: [PATCH 10/13] Some code cleaning, added comment for reasoning of specifying min and max in the qml --- .../resources/qml/controls-uit/Slider.qml | 2 +- .../qml/dialogs/preferences/Section.qml | 8 ++-- .../preferences/SpinnerSliderPreference.qml | 47 ++++++++++--------- interface/src/ui/PreferencesDialog.cpp | 3 ++ 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/interface/resources/qml/controls-uit/Slider.qml b/interface/resources/qml/controls-uit/Slider.qml index 18d0d33fe2..89bae9bcde 100644 --- a/interface/resources/qml/controls-uit/Slider.qml +++ b/interface/resources/qml/controls-uit/Slider.qml @@ -36,7 +36,7 @@ Slider { Rectangle { width: parent.height - 2 - height: slider.width * (slider.value - slider.minimumValue)/(slider.maximumValue-slider.minimumValue)-1 + height: slider.width * (slider.value - slider.minimumValue) / (slider.maximumValue - slider.minimumValue) - 1 radius: height / 2 anchors { top: parent.top diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index 2b90d402bc..3985c7d6f6 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -130,10 +130,10 @@ Preference { builder = comboBoxBuilder; break; - case Preference.SpinnerSlider: - checkBoxCount = 0; - builder = spinnerSliderBuilder; - break; + case Preference.SpinnerSlider: + checkBoxCount = 0; + builder = spinnerSliderBuilder; + break; }; if (builder) { diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index 85caa1befc..f5dfa3a795 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -16,10 +16,12 @@ import "../../controls-uit" Preference { id: root property alias slider: slider + property alias spinner: spinner height: control.height + hifi.dimensions.controlInterlineHeight Component.onCompleted: { slider.value = preference.value; + spinner.value = preference.value; } function save() { @@ -27,10 +29,6 @@ Preference { preference.save(); } - function sliderToAvatarScale(sliderValue) { - return MyAvatar.getDomainMinScale() + (MyAvatar.getDomainMaxScale()-MyAvatar.getDomainMinScale())*value; - } - Item { id: control anchors { @@ -38,7 +36,7 @@ Preference { right: parent.right bottom: parent.bottom } - height: Math.max(labelText.height, slider.height) + height: Math.max(labelText.height, slider.height, spinner.height, button.height) Label { id: labelText @@ -67,9 +65,11 @@ Preference { anchors { right: spinner.left rightMargin: 10 + verticalCenter: parent.verticalCenter } colorScheme: hifi.colorSchemes.dark } + SpinBox { id: spinner decimals: preference.decimals @@ -83,26 +83,29 @@ Preference { anchors { right: button.left rightMargin: 10 + verticalCenter: parent.verticalCenter } colorScheme: hifi.colorSchemes.dark } - GlyphButton { - id: button - onClicked: { - if(spinner.maximumValue >= 1) { - spinner.value = 1 - slider.value = 1 - } else { - spinner.value = spinner.maximumValue - slider.value = spinner.maximumValue - } - } - width: 30 - glyph: hifi.glyphs.reload - anchors { - right: parent.right - } + + GlyphButton { + id: button + onClicked: { + if (spinner.maximumValue >= 1) { + spinner.value = 1 + slider.value = 1 + } else { + spinner.value = spinner.maximumValue + slider.value = spinner.maximumValue + } + } + width: 30 + glyph: hifi.glyphs.reload + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + } colorScheme: hifi.colorSchemes.dark - } + } } } \ No newline at end of file diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 4db83aab7a..802bb2fd84 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -188,6 +188,9 @@ void setupPreferences() { preference->setStep(0.1f); preference->setDecimals(2); preferences->addPreference(preference); + + // When the Interface is first loaded, this section setupPreferences(); is loaded - causing the myAvatar->getDomainMinScale() and myAvatar->getDomainMaxScale() to get set to incorrect values which can't be changed across domain switches. + // Having these values loaded up when you load the Dialog each time is a way around this, therefore they're not specified here but in the QML. } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; From c60c8c7b13238c03f3f94945b11dfc3720071019 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 14 Jul 2017 00:59:25 +0100 Subject: [PATCH 11/13] Missed two tabs.. --- .../qml/dialogs/preferences/SpinnerSliderPreference.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index f5dfa3a795..3cba67bc82 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -64,12 +64,12 @@ Preference { } anchors { right: spinner.left - rightMargin: 10 + rightMargin: 10 verticalCenter: parent.verticalCenter } colorScheme: hifi.colorSchemes.dark } - + SpinBox { id: spinner decimals: preference.decimals @@ -82,7 +82,7 @@ Preference { } anchors { right: button.left - rightMargin: 10 + rightMargin: 10 verticalCenter: parent.verticalCenter } colorScheme: hifi.colorSchemes.dark From ee1f37190280bd2a095605a84e66c013618d7b9f Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 14 Jul 2017 16:29:19 +0100 Subject: [PATCH 12/13] Fixed step counted not resetting to 1 properly --- interface/src/ui/PreferencesDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 802bb2fd84..bbdbb0187b 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -185,7 +185,7 @@ void setupPreferences() { auto getter = [=]()->float { return myAvatar->getUniformScale(); }; auto setter = [=](float value) { myAvatar->setTargetScale(value); }; auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); - preference->setStep(0.1f); + preference->setStep(0.05f); preference->setDecimals(2); preferences->addPreference(preference); From b7ed3ce65920bcdb4a9846dca006617d2079af0c Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 14 Jul 2017 17:33:17 +0100 Subject: [PATCH 13/13] Prettified the Comment --- interface/src/ui/PreferencesDialog.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index bbdbb0187b..87131e4f5c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -189,8 +189,10 @@ void setupPreferences() { preference->setDecimals(2); preferences->addPreference(preference); - // When the Interface is first loaded, this section setupPreferences(); is loaded - causing the myAvatar->getDomainMinScale() and myAvatar->getDomainMaxScale() to get set to incorrect values which can't be changed across domain switches. - // Having these values loaded up when you load the Dialog each time is a way around this, therefore they're not specified here but in the QML. + // When the Interface is first loaded, this section setupPreferences(); is loaded - + // causing the myAvatar->getDomainMinScale() and myAvatar->getDomainMaxScale() to get set to incorrect values + // which can't be changed across domain switches. Having these values loaded up when you load the Dialog each time + // is a way around this, therefore they're not specified here but in the QML. } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); };