From 612dc9d2263f0df13003801c97a2cafeacb8f49d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 16 Sep 2014 10:09:57 -0700 Subject: [PATCH 01/20] minor cleanup --- interface/src/avatar/MyAvatar.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6cad3d4296..a178ead3bb 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1197,8 +1197,6 @@ void MyAvatar::updatePosition(float deltaTime) { float motorEfficiency = glm::clamp(deltaTime / computeMotorTimescale(velocity), 0.0f, 1.0f); - // compute targetVelocity - glm::vec3 targetVelocity(0.0f); if (_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED) { float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + (fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) + @@ -1235,13 +1233,12 @@ void MyAvatar::updatePosition(float deltaTime) { } _isPushing = true; } - targetVelocity = _motorVelocity; } else { _motorVelocity = glm::vec3(0.0f); } } - targetVelocity = getHead()->getCameraOrientation() * targetVelocity; + glm::vec3 targetVelocity = getHead()->getCameraOrientation() * _motorVelocity; glm::vec3 deltaVelocity = targetVelocity - velocity; if (walkingOnFloor && !pushingUp) { From 489871d0d6f3917d59116ff567a2b03eeb04ac34 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 16 Sep 2014 17:09:32 -0700 Subject: [PATCH 02/20] added MyAvatar.setMotorVelocity() & friends for JS --- interface/src/Application.cpp | 5 +- interface/src/Menu.cpp | 10 +- interface/src/Menu.h | 2 + interface/src/avatar/MyAvatar.cpp | 241 ++++++++++++++++++----------- interface/src/avatar/MyAvatar.h | 20 ++- libraries/avatars/src/AvatarData.h | 13 +- 6 files changed, 187 insertions(+), 104 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 371feeb612..cfd7448fca 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3890,7 +3890,7 @@ void Application::stopAllScripts(bool restart) { // HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities // whenever a script stops in case it happened to have been setting joint rotations. // TODO: expose animation priorities and provide a layered animation control system. - _myAvatar->clearJointAnimationPriorities(); + _myAvatar->clearScriptableSettings(); } void Application::stopScript(const QString &scriptName) { @@ -3903,6 +3903,9 @@ void Application::stopScript(const QString &scriptName) { // TODO: expose animation priorities and provide a layered animation control system. _myAvatar->clearJointAnimationPriorities(); } + if (_scriptEnginesHash.empty()) { + _myAvatar->clearScriptableSettings(); + } } void Application::reloadAllScripts() { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index d3d1c8c0d0..bccb9a6fed 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -268,13 +268,17 @@ Menu::Menu() : SLOT(resetSize())); QObject* avatar = appInstance->getAvatar(); + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::KeyboardMotorControl, + Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar, SLOT(updateMotionBehavior())); + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ScriptedMotorControl, 0, true, + avatar, SLOT(updateMotionBehavior())); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ChatCircling, 0, false); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::GlowWhenSpeaking, 0, true); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false, - avatar, SLOT(updateMotionBehaviorsFromMenu())); + avatar, SLOT(updateMotionBehavior())); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::StandOnNearbyFloors, 0, true, - avatar, SLOT(updateMotionBehaviorsFromMenu())); + avatar, SLOT(updateMotionBehavior())); QMenu* collisionsMenu = avatarMenu->addMenu("Collide With..."); addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideAsRagdoll, 0, false, @@ -744,9 +748,11 @@ void Menu::loadSettings(QSettings* settings) { // MyAvatar caches some menu options, so we have to update them whenever we load settings. // TODO: cache more settings in MyAvatar that are checked with very high frequency. + setIsOptionChecked(MenuOption::KeyboardMotorControl , true); MyAvatar* myAvatar = Application::getInstance()->getAvatar(); myAvatar->updateCollisionGroups(); myAvatar->onToggleRagdoll(); + myAvatar->updateMotionBehavior(); if (lockedSettings) { Application::getInstance()->unlockSettings(); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b43e7cb75e..54b9c8fe57 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -399,6 +399,7 @@ namespace MenuOption { const QString HeadMouse = "Head Mouse"; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IncreaseVoxelSize = "Increase Voxel Size"; + const QString KeyboardMotorControl = "Enable Keyboard Motor Control"; const QString LoadScript = "Open and Run Script File..."; const QString LoadScriptURL = "Open and Run Script from URL..."; const QString LodTools = "LOD Tools"; @@ -437,6 +438,7 @@ namespace MenuOption { const QString RunningScripts = "Running Scripts"; const QString RunTimingTests = "Run Timing Tests"; const QString ScriptEditor = "Script Editor..."; + const QString ScriptedMotorControl = "Enable Scripted Motor Control"; const QString SettingsExport = "Export Settings"; const QString SettingsImport = "Import Settings"; const QString ShowBordersModelNodes = "Show Model Nodes"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a178ead3bb..3f24998566 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -54,9 +54,11 @@ const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED; // TODO: normalize avatar speed for standard avatar size, then scale all motion logic // to properly follow avatar size. -float DEFAULT_MOTOR_TIMESCALE = 0.25f; float MAX_AVATAR_SPEED = 300.0f; -float MAX_MOTOR_SPEED = MAX_AVATAR_SPEED; +float MAX_KEYBOARD_MOTOR_SPEED = MAX_AVATAR_SPEED; +float DEFAULT_KEYBOARD_MOTOR_TIMESCALE = 0.25f; +float MIN_SCRIPTED_MOTOR_TIMESCALE = 0.005f; +float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f; MyAvatar::MyAvatar() : Avatar(), @@ -71,9 +73,10 @@ MyAvatar::MyAvatar() : _isBraking(false), _trapDuration(0.0f), _thrust(0.0f), - _motorVelocity(0.0f), - _motorTimescale(DEFAULT_MOTOR_TIMESCALE), - _maxMotorSpeed(MAX_MOTOR_SPEED), + _keyboardMotorVelocity(0.0f), + _keyboardMotorTimescale(DEFAULT_KEYBOARD_MOTOR_TIMESCALE), + _scriptedMotorVelocity(0.0f), + _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), _motionBehaviors(AVATAR_MOTION_DEFAULTS), _lookAtTargetAvatar(), _shouldRender(true), @@ -1027,6 +1030,28 @@ void MyAvatar::setAttachmentData(const QVector& attachmentData) _billboardValid = false; } +void MyAvatar::setMotorVelocity(const glm::vec3& velocity) { + float MAX_SCRIPTED_MOTOR_SPEED = 500.0f; + _scriptedMotorVelocity = velocity; + float speed = glm::length(_scriptedMotorVelocity); + if (speed > MAX_SCRIPTED_MOTOR_SPEED) { + _scriptedMotorVelocity *= MAX_SCRIPTED_MOTOR_SPEED / speed; + } +} + +void MyAvatar::setMotorTimescale(float timescale) { + // we clamp the timescale on the large side (instead of just the low side) to prevent + // obnoxiously large values from introducing NaN into avatar's velocity + _scriptedMotorTimescale = glm::clamp(timescale, MIN_SCRIPTED_MOTOR_TIMESCALE, + DEFAULT_SCRIPTED_MOTOR_TIMESCALE); +} + +void MyAvatar::clearScriptableSettings() { + clearJointAnimationPriorities(); + _scriptedMotorVelocity = glm::vec3(0.0f); + _scriptedMotorTimescale = DEFAULT_SCRIPTED_MOTOR_TIMESCALE; +} + void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation, const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) { if (QThread::currentThread() != thread()) { @@ -1138,13 +1163,102 @@ void MyAvatar::updateOrientation(float deltaTime) { setOrientation(orientation); } +glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVelocity, bool walkingOnFloor) { + if (! (_motionBehaviors & AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED)) { + return localVelocity; + } + // compute motor efficiency + // The timescale of the motor is the approximate time it takes for the motor to + // accomplish its intended localVelocity. A short timescale makes the motor strong, + // and a long timescale makes it weak. The value of timescale to use depends + // on what the motor is doing: + // + // (1) braking --> short timescale (aggressive motor assertion) + // (2) pushing --> medium timescale (mild motor assertion) + // (3) inactive --> long timescale (gentle friction for low speeds) + float MIN_KEYBOARD_MOTOR_TIMESCALE = 0.125f; + float MAX_KEYBOARD_MOTOR_TIMESCALE = 0.4f; + float MIN_KEYBOARD_BRAKE_SPEED = 0.3f; + float timescale = MAX_KEYBOARD_MOTOR_TIMESCALE; + bool isThrust = (glm::length2(_thrust) > EPSILON); + if (_isPushing || isThrust || + (_scriptedMotorTimescale < MAX_KEYBOARD_MOTOR_TIMESCALE && + _motionBehaviors | AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED)) { + // we don't want to break if anything is pushing the avatar around + timescale = _keyboardMotorTimescale; + _isBraking = false; + } else { + float speed = glm::length(localVelocity); + _isBraking = _wasPushing || (_isBraking && speed > MIN_KEYBOARD_BRAKE_SPEED); + if (_isBraking) { + timescale = MIN_KEYBOARD_MOTOR_TIMESCALE; + } + } + _wasPushing = _isPushing || isThrust; + _isPushing = false; + float motorEfficiency = glm::clamp(deltaTime / timescale, 0.0f, 1.0f); + + float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + + (fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) + + fabsf(_driveKeys[UP] - _driveKeys[DOWN]); + if (keyboardInput) { + // Compute keyboard input + glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT; + glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT; + glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP; + + glm::vec3 direction = front + right + up; + float directionLength = glm::length(direction); + + // Compute motor magnitude + if (directionLength > EPSILON) { + direction /= directionLength; + + // Compute the target keyboard velocity (which ramps up slowly, and damps very quickly) + // the max magnitude of which depends on what we're doing: + float finalMaxMotorSpeed = walkingOnFloor ? _scale * MAX_WALKING_SPEED : _scale * MAX_KEYBOARD_MOTOR_SPEED; + float motorLength = glm::length(_keyboardMotorVelocity); + if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) { + // an active keyboard motor should never be slower than this + _keyboardMotorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction; + motorEfficiency = 1.0f; + } else { + float KEYBOARD_MOTOR_LENGTH_TIMESCALE = 2.0f; + float INCREASE_FACTOR = 1.8f; + motorLength *= 1.0f + glm::clamp(deltaTime / KEYBOARD_MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f) * INCREASE_FACTOR; + if (motorLength > finalMaxMotorSpeed) { + motorLength = finalMaxMotorSpeed; + } + _keyboardMotorVelocity = motorLength * direction; + } + _isPushing = true; + } + } else { + _keyboardMotorVelocity = glm::vec3(0.0f); + } + + // apply keyboard motor + return localVelocity + motorEfficiency * (_keyboardMotorVelocity - localVelocity); +} + +glm::vec3 MyAvatar::applyScriptedMotor(float deltaTime, const glm::vec3& localVelocity) { + if (! (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED)) { + return localVelocity; + } + float motorEfficiency = glm::clamp(deltaTime / _scriptedMotorTimescale, 0.0f, 1.0f); + return localVelocity + motorEfficiency * (_scriptedMotorVelocity - localVelocity); +} + const float NEARBY_FLOOR_THRESHOLD = 5.0f; void MyAvatar::updatePosition(float deltaTime) { // check for floor by casting a ray straight down from avatar's position float heightAboveFloor = FLT_MAX; + bool walkingOnFloor = false; const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); + const float maxFloorDistance = boundingShape.getBoundingRadius() * NEARBY_FLOOR_THRESHOLD; + RayIntersectionInfo intersection; // NOTE: avatar is center of PhysicsSimulation, so rayStart is the origin for the purposes of the raycast intersection._rayStart = glm::vec3(0.0f); @@ -1153,26 +1267,28 @@ void MyAvatar::updatePosition(float deltaTime) { if (_physicsSimulation.findFloorRayIntersection(intersection)) { // NOTE: heightAboveFloor is the distance between the bottom of the avatar and the floor heightAboveFloor = intersection._hitDistance - boundingShape.getBoundingRadius(); + if (heightAboveFloor < maxFloorDistance) { + walkingOnFloor = true; + } } // velocity is initialized to the measured _velocity but will be modified by friction, external thrust, etc glm::vec3 velocity = _velocity; - bool pushingUp = (_driveKeys[UP] - _driveKeys[DOWN] > 0.0f); - bool walkingOnFloor = false; + bool pushingUp = (_driveKeys[UP] - _driveKeys[DOWN] > 0.0f) || _scriptedMotorVelocity.y > 0.0f; if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) { const float MAX_SPEED_UNDER_GRAVITY = 2.0f * _scale * MAX_WALKING_SPEED; if (pushingUp || glm::length2(velocity) > MAX_SPEED_UNDER_GRAVITY * MAX_SPEED_UNDER_GRAVITY) { // we're pushing up or moving quickly, so disable gravity setLocalGravity(glm::vec3(0.0f)); + walkingOnFloor = false; } else { - const float maxFloorDistance = boundingShape.getBoundingRadius() * NEARBY_FLOOR_THRESHOLD; if (heightAboveFloor > maxFloorDistance) { // disable local gravity when floor is too far away setLocalGravity(glm::vec3(0.0f)); + walkingOnFloor = false; } else { // enable gravity - walkingOnFloor = true; setLocalGravity(-_worldUpDirection); } } @@ -1195,59 +1311,23 @@ void MyAvatar::updatePosition(float deltaTime) { velocity += (deltaTime * GRAVITY_EARTH) * _gravity; } - float motorEfficiency = glm::clamp(deltaTime / computeMotorTimescale(velocity), 0.0f, 1.0f); + // rotate velocity into camera frame + glm::quat rotation = getHead()->getCameraOrientation(); + glm::vec3 localVelocity = glm::inverse(rotation) * velocity; - if (_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED) { - float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + - (fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) + - fabsf(_driveKeys[UP] - _driveKeys[DOWN]); - if (keyboardInput) { - // Compute keyboard input - glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT; - glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT; - glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP; - - glm::vec3 direction = front + right + up; - float directionLength = glm::length(direction); - - // Compute motor magnitude - if (directionLength > EPSILON) { - direction /= directionLength; - - // Compute the target keyboard velocity (which ramps up slowly, and damps very quickly) - // the max magnitude of which depends on what we're doing: - float finalMaxMotorSpeed = walkingOnFloor ? _scale * MAX_WALKING_SPEED : _scale * _maxMotorSpeed; - float motorLength = glm::length(_motorVelocity); - if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) { - // an active keyboard motor should never be slower than this - _motorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction; - motorEfficiency = 1.0f; - } else { - float MOTOR_LENGTH_TIMESCALE = 2.0f; - float INCREASE_FACTOR = 1.8f; - motorLength *= 1.0f + glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f) * INCREASE_FACTOR; - if (motorLength > finalMaxMotorSpeed) { - motorLength = finalMaxMotorSpeed; - } - _motorVelocity = motorLength * direction; - } - _isPushing = true; - } - } else { - _motorVelocity = glm::vec3(0.0f); - } - } - - glm::vec3 targetVelocity = getHead()->getCameraOrientation() * _motorVelocity; - glm::vec3 deltaVelocity = targetVelocity - velocity; + // apply motors in camera frame + glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, walkingOnFloor); + newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity); if (walkingOnFloor && !pushingUp) { - // remove vertical component of deltaVelocity + // remove any delta component that points into floor + glm::vec3 deltaVelocity = newLocalVelocity - localVelocity; deltaVelocity -= glm::dot(deltaVelocity, _worldUpDirection) * _worldUpDirection; + newLocalVelocity = localVelocity + deltaVelocity; } - // apply motor - velocity += motorEfficiency * deltaVelocity; + // rotate back into world-frame + velocity = rotation * newLocalVelocity; // apply thrust velocity += _thrust * deltaTime; @@ -1282,37 +1362,6 @@ void MyAvatar::updatePosition(float deltaTime) { measureMotionDerivatives(deltaTime); } -float MyAvatar::computeMotorTimescale(const glm::vec3& velocity) { - // The timescale of the motor is the approximate time it takes for the motor to - // accomplish its intended velocity. A short timescale makes the motor strong, - // and a long timescale makes it weak. The value of timescale to use depends - // on what the motor is doing: - // - // (1) braking --> short timescale (aggressive motor assertion) - // (2) pushing --> medium timescale (mild motor assertion) - // (3) inactive --> long timescale (gentle friction for low speeds) - - float MIN_MOTOR_TIMESCALE = 0.125f; - float MAX_MOTOR_TIMESCALE = 0.4f; - float MIN_BRAKE_SPEED = 0.3f; - - float timescale = MAX_MOTOR_TIMESCALE; - bool isThrust = (glm::length2(_thrust) > EPSILON); - if (_isPushing || isThrust) { - timescale = _motorTimescale; - _isBraking = false; - } else { - float speed = glm::length(velocity); - _isBraking = _wasPushing || (_isBraking && speed > MIN_BRAKE_SPEED); - if (_isBraking) { - timescale = MIN_MOTOR_TIMESCALE; - } - } - _wasPushing = _isPushing || isThrust; - _isPushing = false; - return timescale; -} - void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) { glm::vec3 up = getBodyUpDirection(); const float ENVIRONMENT_SURFACE_ELASTICITY = 0.0f; @@ -1432,10 +1481,10 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { if (highestStep > MIN_STEP_HEIGHT && highestStep < MAX_STEP_HEIGHT && verticalPenetration <= 0.0f) { // we're colliding against an edge - // rotate _motorVelocity into world frame - glm::vec3 targetVelocity = _motorVelocity; + // rotate _keyboardMotorVelocity into world frame + glm::vec3 targetVelocity = _keyboardMotorVelocity; glm::quat rotation = getHead()->getCameraOrientation(); - targetVelocity = rotation * _motorVelocity; + targetVelocity = rotation * _keyboardMotorVelocity; if (_wasPushing && glm::dot(targetVelocity, totalPenetration) > EPSILON) { // we're puhing into the edge, so we want to lift @@ -1786,7 +1835,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, bool hasOrientation, c emit transformChanged(); } -void MyAvatar::updateMotionBehaviorsFromMenu() { +void MyAvatar::updateMotionBehavior() { Menu* menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) { _motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY; @@ -1809,6 +1858,16 @@ void MyAvatar::updateMotionBehaviorsFromMenu() { if (!(_collisionGroups | COLLISION_GROUP_VOXELS)) { _voxelShapeManager.clearShapes(); } + if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) { + _motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED; + } else { + _motionBehaviors &= ~AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED; + } + if (menu->isOptionChecked(MenuOption::ScriptedMotorControl)) { + _motionBehaviors |= AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; + } else { + _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; + } } void MyAvatar::onToggleRagdoll() { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d86829ea91..43b0f5fc2c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -131,6 +131,14 @@ public: void clearJointAnimationPriorities(); + Q_INVOKABLE glm::vec3 getMotorVelocity() const { return _scriptedMotorVelocity; } + Q_INVOKABLE float getTimescale() const { return _scriptedMotorTimescale; } + + Q_INVOKABLE void setMotorVelocity(const glm::vec3& velocity); + Q_INVOKABLE void setMotorTimescale(float timescale); + + void clearScriptableSettings(); + virtual void attach(const QString& modelURL, const QString& jointName = QString(), const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, bool allowDuplicates = false, bool useSaved = true); @@ -163,7 +171,7 @@ public slots: void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } - void updateMotionBehaviorsFromMenu(); + void updateMotionBehavior(); void onToggleRagdoll(); glm::vec3 getLeftPalmPosition(); @@ -202,9 +210,10 @@ private: float _trapDuration; // seconds that avatar has been trapped by collisions glm::vec3 _thrust; // impulse accumulator for outside sources - glm::vec3 _motorVelocity; // intended velocity of avatar motion (relative to what it's standing on) - float _motorTimescale; // timescale for avatar motor to achieve its desired velocity - float _maxMotorSpeed; + glm::vec3 _keyboardMotorVelocity; // target local-frame velocity of avatar (keyboard) + float _keyboardMotorTimescale; // timescale for avatar to achieve its target velocity + glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (script) + float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity quint32 _motionBehaviors; QWeakPointer _lookAtTargetAvatar; @@ -221,8 +230,9 @@ private: // private methods void updateOrientation(float deltaTime); + glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool walkingOnFloor); + glm::vec3 applyScriptedMotor(float deltaTime, const glm::vec3& velocity); void updatePosition(float deltaTime); - float computeMotorTimescale(const glm::vec3& velocity); void updateCollisionWithAvatars(float deltaTime); void updateCollisionWithEnvironment(float deltaTime, float radius); void updateCollisionWithVoxels(float deltaTime, float radius); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 5b0c6b97dd..b0b2690fbe 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -55,18 +55,21 @@ typedef unsigned long long quint64; #include "HandData.h" // avatar motion behaviors -const quint32 AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED = 1U << 0; +const quint32 AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED = 1U << 0; +const quint32 AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED = 1U << 1; -const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 1; -const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 2; -const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 3; +const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 2; +const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 3; +const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 4; const quint32 AVATAR_MOTION_DEFAULTS = - AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED | + AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED | + AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED | AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; // these bits will be expanded as features are exposed const quint32 AVATAR_MOTION_SCRIPTABLE_BITS = + AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED | AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY | AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; From cc79254a41b0b1dda858848ea35816ff3ee03af8 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 17 Sep 2014 09:40:48 -0700 Subject: [PATCH 03/20] have domain server make sure access token is present before IP update --- domain-server/src/DomainServer.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index e99e608906..e47c75a51a 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -309,8 +309,15 @@ void DomainServer::setupDynamicIPAddressUpdating() { // send public socket changes to the data server so nodes can find us at our new IP connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewPublicSocketToDataServer); - // check our IP address right away - requestCurrentIPAddressViaSTUN(); + if (!AccountManager::getInstance().hasValidAccessToken()) { + // we don't have an access token to talk to data-web yet, so + // check our IP address as soon as we get an AccountManager access token + connect(&AccountManager::getInstance(), &AccountManager::loginComplete, + this, &DomainServer::requestCurrentIPAddressViaSTUN); + } else { + // access token good to go, attempt to update our IP now + requestCurrentIPAddressViaSTUN(); + } } else { qDebug() << "Cannot enable dynamic domain-server IP address updating without a domain ID." From 801630e19c40bc15d38051915368f9a0a82fd692 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 17 Sep 2014 13:26:57 -0700 Subject: [PATCH 04/20] Normalize vertex weights if they don't add up to one; replace lowest weight if we can't find an empty slot. --- libraries/fbx/src/FBXReader.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 7d32d97752..3c65540e57 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1755,12 +1755,24 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) // look for an unused slot in the weights vector glm::vec4& weights = extracted.mesh.clusterWeights[it.value()]; - for (int k = 0; k < 4; k++) { + int lowestIndex = -1; + float lowestWeight = FLT_MAX; + int k = 0; + for (; k < 4; k++) { if (weights[k] == 0.0f) { extracted.mesh.clusterIndices[it.value()][k] = i; weights[k] = weight; break; } + if (weights[k] < lowestWeight) { + lowestIndex = k; + lowestWeight = weights[k]; + } + } + if (k == 4) { + // no space for an additional weight; we must replace the lowest + weights[lowestIndex] = weight; + extracted.mesh.clusterIndices[it.value()][lowestIndex] = i; } } } @@ -1769,6 +1781,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) maxJointIndex = jointIndex; } } + // normalize the weights if they don't add up to one + for (int i = 0; i < extracted.mesh.clusterWeights.size(); i++) { + glm::vec4& weights = extracted.mesh.clusterWeights[i]; + float total = weights.x + weights.y + weights.z + weights.w; + if (total != 1.0f && total != 0.0f) { + weights /= total; + } + } } else { int jointIndex = maxJointIndex; FBXJoint& joint = geometry.joints[jointIndex]; From 3751e05ce3ed10337f4eabc76616bf408c23f765 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 17 Sep 2014 13:37:16 -0700 Subject: [PATCH 05/20] Fixes crash in Model::scaleToFit() --- interface/src/renderer/Model.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 2e8fc32566..7f83147bb8 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -894,6 +894,10 @@ void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) { } void Model::setScaleToFit(bool scaleToFit, float largestDimension) { + if (!isActive()) { + return; + } + if (_scaleToFit != scaleToFit || glm::length(_scaleToFitDimensions) != largestDimension) { _scaleToFit = scaleToFit; From a3c107545ebf45d5b8ac42ebf898db8530dff1ad Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 17 Sep 2014 14:35:34 -0700 Subject: [PATCH 06/20] Allow rendering eye vectors even if there's no head model. --- interface/src/avatar/Head.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index b226b8ed31..9fb86c23ac 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -184,8 +184,8 @@ void Head::relaxLean(float deltaTime) { } void Head::render(float alpha, Model::RenderMode mode) { - if (_faceModel.render(alpha, mode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows)) && - _renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) { + _faceModel.render(alpha, mode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows)); + if (_renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) { renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition); } } From 7848c63fae10e4379a856f55676f1afaff896cd6 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 17 Sep 2014 12:14:38 -0700 Subject: [PATCH 07/20] handle name location shortcut explicitly to work around bug --- interface/src/Application.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 371feeb612..efa5a481f6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -288,6 +288,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // set the account manager's root URL and trigger a login request if we don't have the access token accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL); UserActivityLogger::getInstance().launch(applicationVersion()); + + // grab the location manager instance early so it lives in our thread + LocationManager::getInstance(); // once the event loop has started, check and signal for an access token QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); @@ -927,6 +930,13 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + + case Qt::Key_N: + if (isMeta) { + Menu::getInstance()->triggerOption(MenuOption::NameLocation); + } + + break; case Qt::Key_Up: if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { From e7b9e2b0608c9979a4034e852c2bba987574ad71 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 17 Sep 2014 12:14:53 -0700 Subject: [PATCH 08/20] have LocationManager upload snapshot after location creation --- interface/src/location/LocationManager.cpp | 71 ++++++++++++++++++++-- interface/src/location/LocationManager.h | 6 +- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/interface/src/location/LocationManager.cpp b/interface/src/location/LocationManager.cpp index 92e8616478..866e92be98 100644 --- a/interface/src/location/LocationManager.cpp +++ b/interface/src/location/LocationManager.cpp @@ -9,10 +9,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include +#include "Application.h" +#include "ui/Snapshot.h" + #include "LocationManager.h" const QString POST_LOCATION_CREATE = "/api/v1/locations/"; @@ -24,13 +28,17 @@ LocationManager& LocationManager::getInstance() { const QString UNKNOWN_ERROR_MESSAGE = "Unknown error creating named location. Please try again!"; -void LocationManager::namedLocationDataReceived(const QJsonObject& data) { - if (data.isEmpty()) { - return; - } +const QString LOCATION_OBJECT_KEY = "location"; +const QString LOCATION_ID_KEY = "id"; - if (data.contains("status") && data["status"].toString() == "success") { +void LocationManager::namedLocationDataReceived(const QJsonObject& rootObject) { + + if (rootObject.contains("status") && rootObject["status"].toString() == "success") { emit creationCompleted(QString()); + + // successfuly created a location - grab the ID from the response and create a snapshot to upload + QString locationIDString = rootObject[LOCATION_OBJECT_KEY].toObject()[LOCATION_ID_KEY].toString(); + updateSnapshotForExistingLocation(locationIDString); } else { emit creationCompleted(UNKNOWN_ERROR_MESSAGE); } @@ -87,3 +95,56 @@ void LocationManager::errorDataReceived(QNetworkReply& errorReply) { creationCompleted(UNKNOWN_ERROR_MESSAGE); } } + +void LocationManager::locationImageUpdateSuccess(const QJsonObject& rootObject) { + qDebug() << "Successfuly updated a location image."; +} + +void LocationManager::updateSnapshotForExistingLocation(const QString& locationID) { + // first create a snapshot and save it + Application* application = Application::getInstance(); + + QString filename = Snapshot::saveSnapshot(application->getGLWidget(), application->getAvatar()); + + AccountManager& accountManager = AccountManager::getInstance(); + + // setup a multipart that is in the AccountManager thread - we need this so it can be cleaned up after the QNetworkReply + QHttpMultiPart* imageFileMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + imageFileMultiPart->moveToThread(accountManager.thread()); + + // load the file at that filename, parent the mile to the QHttpMultipart + QFile* locationImageFile = new QFile(filename, imageFileMultiPart); + + if (!locationImageFile->open(QIODevice::ReadOnly)) { + qDebug() << "Couldn't open snapshot file to upload as location image. No location image will be stored."; + return; + } + + qDebug() << "Uploading a snapshot from" << filename << "as location image for" << locationID; + + + + const QString LOCATION_IMAGE_NAME = "location[image]"; + + QHttpPart imagePart; + imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"" + LOCATION_IMAGE_NAME + "\";")); + imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); + imagePart.setBodyDevice(locationImageFile); + + imageFileMultiPart->append(imagePart); + + const QString LOCATION_IMAGE_PUT_PATH = "api/v1/locations/%1/image"; + + JSONCallbackParameters imageCallbackParams; + imageCallbackParams.jsonCallbackReceiver = this; + imageCallbackParams.jsonCallbackMethod = "locationImageUpdateSuccess"; + + // make an authenticated request via account manager to upload the image + // don't do anything with error or success for now + AccountManager::getInstance().authenticatedRequest(LOCATION_IMAGE_PUT_PATH.arg(locationID), + QNetworkAccessManager::PutOperation, + JSONCallbackParameters(), QByteArray(), imageFileMultiPart); +} + + diff --git a/interface/src/location/LocationManager.h b/interface/src/location/LocationManager.h index b6a662e323..43431a83c9 100644 --- a/interface/src/location/LocationManager.h +++ b/interface/src/location/LocationManager.h @@ -35,8 +35,12 @@ signals: void creationCompleted(const QString& errorMessage); private slots: - void namedLocationDataReceived(const QJsonObject& data); + void namedLocationDataReceived(const QJsonObject& jsonObject); void errorDataReceived(QNetworkReply& errorReply); + void locationImageUpdateSuccess(const QJsonObject& jsonObject); + +private: + void updateSnapshotForExistingLocation(const QString& locationID); }; From 8dddbb9d4b5ec2d742984814c9483a95d8c773ac Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 17 Sep 2014 14:07:10 -0700 Subject: [PATCH 09/20] make snapshots uploaded for locations temp files that will be cleared --- interface/src/location/LocationManager.cpp | 75 +++++++++++----------- interface/src/ui/Snapshot.cpp | 60 ++++++++++++++--- interface/src/ui/Snapshot.h | 4 ++ 3 files changed, 93 insertions(+), 46 deletions(-) diff --git a/interface/src/location/LocationManager.cpp b/interface/src/location/LocationManager.cpp index 866e92be98..ed27d9e49d 100644 --- a/interface/src/location/LocationManager.cpp +++ b/interface/src/location/LocationManager.cpp @@ -104,47 +104,48 @@ void LocationManager::updateSnapshotForExistingLocation(const QString& locationI // first create a snapshot and save it Application* application = Application::getInstance(); - QString filename = Snapshot::saveSnapshot(application->getGLWidget(), application->getAvatar()); + QTemporaryFile* tempImageFile = Snapshot::saveTempSnapshot(application->getGLWidget(), application->getAvatar()); - AccountManager& accountManager = AccountManager::getInstance(); - - // setup a multipart that is in the AccountManager thread - we need this so it can be cleaned up after the QNetworkReply - QHttpMultiPart* imageFileMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - imageFileMultiPart->moveToThread(accountManager.thread()); - - // load the file at that filename, parent the mile to the QHttpMultipart - QFile* locationImageFile = new QFile(filename, imageFileMultiPart); - - if (!locationImageFile->open(QIODevice::ReadOnly)) { + if (tempImageFile && tempImageFile->open()) { + AccountManager& accountManager = AccountManager::getInstance(); + + // setup a multipart that is in the AccountManager thread - we need this so it can be cleaned up after the QNetworkReply + QHttpMultiPart* imageFileMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + imageFileMultiPart->moveToThread(accountManager.thread()); + + // parent the temp file to the QHttpMultipart after moving it to account manager thread + tempImageFile->moveToThread(accountManager.thread()); + tempImageFile->setParent(imageFileMultiPart); + + qDebug() << "Uploading a snapshot from" << QFileInfo(*tempImageFile).absoluteFilePath() + << "as location image for" << locationID; + + const QString LOCATION_IMAGE_NAME = "location[image]"; + + QHttpPart imagePart; + imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"" + LOCATION_IMAGE_NAME + "\";" + " filename=\"" + QFileInfo(tempImageFile->fileName()).fileName().toUtf8() + "\"")); + imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); + imagePart.setBodyDevice(tempImageFile); + + imageFileMultiPart->append(imagePart); + + const QString LOCATION_IMAGE_PUT_PATH = "api/v1/locations/%1/image"; + + JSONCallbackParameters imageCallbackParams; + imageCallbackParams.jsonCallbackReceiver = this; + imageCallbackParams.jsonCallbackMethod = "locationImageUpdateSuccess"; + + // make an authenticated request via account manager to upload the image + // don't do anything with error or success for now + AccountManager::getInstance().authenticatedRequest(LOCATION_IMAGE_PUT_PATH.arg(locationID), + QNetworkAccessManager::PutOperation, + JSONCallbackParameters(), QByteArray(), imageFileMultiPart); + } else { qDebug() << "Couldn't open snapshot file to upload as location image. No location image will be stored."; return; } - - qDebug() << "Uploading a snapshot from" << filename << "as location image for" << locationID; - - - - const QString LOCATION_IMAGE_NAME = "location[image]"; - - QHttpPart imagePart; - imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"" + LOCATION_IMAGE_NAME + "\";")); - imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - imagePart.setBodyDevice(locationImageFile); - - imageFileMultiPart->append(imagePart); - - const QString LOCATION_IMAGE_PUT_PATH = "api/v1/locations/%1/image"; - - JSONCallbackParameters imageCallbackParams; - imageCallbackParams.jsonCallbackReceiver = this; - imageCallbackParams.jsonCallbackMethod = "locationImageUpdateSuccess"; - - // make an authenticated request via account manager to upload the image - // don't do anything with error or success for now - AccountManager::getInstance().authenticatedRequest(LOCATION_IMAGE_PUT_PATH.arg(locationID), - QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), QByteArray(), imageFileMultiPart); } diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index d530be57d2..9fe1c332be 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -65,6 +65,24 @@ SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) { } QString Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) { + QFile* snapshotFile = savedFileForSnapshot(widget, avatar, false); + + // we don't need the snapshot file, so close it, grab its filename and delete it + snapshotFile->close(); + + QString snapshotPath = QFileInfo(*snapshotFile).absoluteFilePath(); + + delete snapshotFile; + + return snapshotPath; +} + +QTemporaryFile* Snapshot::saveTempSnapshot(QGLWidget* widget, Avatar* avatar) { + // return whatever we get back from saved file for snapshot + return static_cast(savedFileForSnapshot(widget, avatar, true));; +} + +QFile* Snapshot::savedFileForSnapshot(QGLWidget* widget, Avatar* avatar, bool isTemporary) { QImage shot = widget->grabFrameBuffer(); glm::vec3 location = avatar->getPosition(); @@ -91,16 +109,40 @@ QString Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) { username.replace(QRegExp("[^A-Za-z0-9_]"), "-"); QDateTime now = QDateTime::currentDateTime(); - QString fileName = Menu::getInstance()->getSnapshotsLocation(); - - if (!fileName.endsWith(QDir::separator())) { - fileName.append(QDir::separator()); - } - - fileName.append(QString(FILENAME_PATH_FORMAT.arg(username, now.toString(DATETIME_FORMAT), formattedLocation))); - shot.save(fileName, 0, 100); - return fileName; + QString filename = FILENAME_PATH_FORMAT.arg(username, now.toString(DATETIME_FORMAT), formattedLocation); + + const int IMAGE_QUALITY = 100; + + if (!isTemporary) { + QString snapshotFullPath = Menu::getInstance()->getSnapshotsLocation(); + + if (!snapshotFullPath.endsWith(QDir::separator())) { + snapshotFullPath.append(QDir::separator()); + } + + snapshotFullPath.append(filename); + + QFile* imageFile = new QFile(snapshotFullPath); + imageFile->open(QIODevice::WriteOnly); + + shot.save(imageFile, 0, IMAGE_QUALITY); + imageFile->close(); + + return imageFile; + } else { + QTemporaryFile* imageTempFile = new QTemporaryFile(QDir::tempPath() + "/XXXXXX-" + filename); + + if (!imageTempFile->open()) { + qDebug() << "Unable to open QTemporaryFile for temp snapshot. Will not save."; + return NULL; + } + + shot.save(imageTempFile, 0, IMAGE_QUALITY); + imageTempFile->close(); + + return imageTempFile; + } } diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index 2872b3fdcb..8f15532cb5 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -42,7 +42,11 @@ class Snapshot { public: static QString saveSnapshot(QGLWidget* widget, Avatar* avatar); + static QTemporaryFile* saveTempSnapshot(QGLWidget* widget, Avatar* avatar); static SnapshotMetaData* parseSnapshotData(QString snapshotPath); + +private: + static QFile* savedFileForSnapshot(QGLWidget* widget, Avatar* avatar, bool isTemporary); }; #endif // hifi_Snapshot_h From abafaffe44d9922c91b67475819b7abb6f55df7b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 17 Sep 2014 14:11:41 -0700 Subject: [PATCH 10/20] fix a missed Model->Entity rename that caused slot error --- interface/src/Menu.cpp | 4 ++-- interface/src/Menu.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index d3d1c8c0d0..34026398f8 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -328,9 +328,9 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersVoxelNodes, Qt::CTRL | Qt::SHIFT | Qt::Key_1, false, &nodeBounds, SLOT(setShowVoxelNodes(bool))); - addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersModelNodes, + addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersEntityNodes, Qt::CTRL | Qt::SHIFT | Qt::Key_2, false, - &nodeBounds, SLOT(setShowModelNodes(bool))); + &nodeBounds, SLOT(setShowEntityNodes(bool))); addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersParticleNodes, Qt::CTRL | Qt::SHIFT | Qt::Key_3, false, &nodeBounds, SLOT(setShowParticleNodes(bool))); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b43e7cb75e..f2df7a2885 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -439,7 +439,7 @@ namespace MenuOption { const QString ScriptEditor = "Script Editor..."; const QString SettingsExport = "Export Settings"; const QString SettingsImport = "Import Settings"; - const QString ShowBordersModelNodes = "Show Model Nodes"; + const QString ShowBordersEntityNodes = "Show Entity Nodes"; const QString ShowBordersParticleNodes = "Show Particle Nodes"; const QString ShowBordersVoxelNodes = "Show Voxel Nodes"; const QString ShowIKConstraints = "Show IK Constraints"; From 48e45265a376e49b488b6ae798cf2438c6f9d381 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 17 Sep 2014 14:15:07 -0700 Subject: [PATCH 11/20] only add a directory to the filesystem watcher if not empty --- interface/src/ScriptsModel.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/interface/src/ScriptsModel.cpp b/interface/src/ScriptsModel.cpp index 7b24587129..cbdbb5bf54 100644 --- a/interface/src/ScriptsModel.cpp +++ b/interface/src/ScriptsModel.cpp @@ -42,17 +42,15 @@ ScriptsModel::ScriptsModel(QObject* parent) : _localDirectory(), _fsWatcher(), _localFiles(), - _remoteFiles() { - - QString scriptPath = Menu::getInstance()->getScriptsLocation(); - - _localDirectory.setPath(scriptPath); + _remoteFiles() +{ + _localDirectory.setFilter(QDir::Files | QDir::Readable); _localDirectory.setNameFilters(QStringList("*.js")); - _fsWatcher.addPath(_localDirectory.absolutePath()); + updateScriptsLocation(Menu::getInstance()->getScriptsLocation()); + connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &ScriptsModel::reloadLocalFiles); - connect(Menu::getInstance(), &Menu::scriptLocationChanged, this, &ScriptsModel::updateScriptsLocation); reloadLocalFiles(); @@ -88,8 +86,13 @@ int ScriptsModel::rowCount(const QModelIndex& parent) const { void ScriptsModel::updateScriptsLocation(const QString& newPath) { _fsWatcher.removePath(_localDirectory.absolutePath()); + _localDirectory.setPath(newPath); - _fsWatcher.addPath(_localDirectory.absolutePath()); + + if (!_localDirectory.absolutePath().isEmpty()) { + _fsWatcher.addPath(_localDirectory.absolutePath()); + } + reloadLocalFiles(); } From 4a2e68fea41ea645aaef94555662e2e5a98d609f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 17 Sep 2014 15:36:22 -0700 Subject: [PATCH 12/20] Fix for eyes on skeleton (e.g., Mixamo) models. --- interface/src/avatar/FaceModel.cpp | 8 ++++---- interface/src/avatar/FaceModel.h | 2 +- interface/src/avatar/SkeletonModel.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index 521a4ddc57..d51973f88f 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -57,15 +57,15 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX * joint.rotation, DEFAULT_PRIORITY); } -void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { +void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, JointState& state) { // likewise with the eye joints // NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual. - glm::mat4 inverse = glm::inverse(glm::mat4_cast(_rotation) * parentState.getTransform() * + glm::mat4 inverse = glm::inverse(glm::mat4_cast(model->getRotation()) * parentState.getTransform() * glm::translate(state.getDefaultTranslationInConstrainedFrame()) * joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation)); glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f)); glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() + - _owningHead->getSaccade() - _translation, 1.0f)); + _owningHead->getSaccade() - model->getTranslation(), 1.0f)); glm::quat between = rotationBetween(front, lookAt); const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; state.setRotationInConstrainedFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) * @@ -82,7 +82,7 @@ void FaceModel::updateJointState(int index) { maybeUpdateNeckRotation(parentState, joint, state); } else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) { - maybeUpdateEyeRotation(parentState, joint, state); + maybeUpdateEyeRotation(this, parentState, joint, state); } } diff --git a/interface/src/avatar/FaceModel.h b/interface/src/avatar/FaceModel.h index c71bb9a8aa..eaaa07e635 100644 --- a/interface/src/avatar/FaceModel.h +++ b/interface/src/avatar/FaceModel.h @@ -27,7 +27,7 @@ public: virtual void simulate(float deltaTime, bool fullUpdate = true); virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); - virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); + virtual void maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, JointState& state); virtual void updateJointState(int index); /// Retrieve the positions of up to two eye meshes. diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index ebcf4520cb..86ca42b15e 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -281,7 +281,7 @@ void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const } void SkeletonModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { - _owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(parentState, joint, state); + _owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(this, parentState, joint, state); } void SkeletonModel::renderJointConstraints(int jointIndex) { From 9ee063bdc19e9905564b37289dc1a91da893a93c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 17 Sep 2014 16:00:20 -0700 Subject: [PATCH 13/20] remove the access token from API urls and put in Authorization header --- libraries/networking/src/AccountManager.cpp | 11 ++++++++--- libraries/networking/src/OAuthAccessToken.h | 2 ++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 1100371ac9..b24f720edf 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -25,6 +25,8 @@ const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; +const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; + AccountManager& AccountManager::getInstance() { static AccountManager sharedInstance; return sharedInstance; @@ -188,7 +190,8 @@ void AccountManager::invokedRequest(const QString& path, if (requiresAuthentication) { if (hasValidAccessToken()) { - requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token); + networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, + _accountInfo.getAccessToken().authorizationHeaderValue()); } else { qDebug() << "No valid access token present. Bailing on authenticated invoked request."; return; @@ -405,9 +408,11 @@ void AccountManager::requestProfile() { QUrl profileURL = _authURL; profileURL.setPath("/api/v1/users/profile"); - profileURL.setQuery("access_token=" + _accountInfo.getAccessToken().token); + + QNetworkRequest profileRequest(profileURL); + profileRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue()); - QNetworkReply* profileReply = networkAccessManager.get(QNetworkRequest(profileURL)); + QNetworkReply* profileReply = networkAccessManager.get(profileRequest); connect(profileReply, &QNetworkReply::finished, this, &AccountManager::requestProfileFinished); connect(profileReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestProfileError(QNetworkReply::NetworkError))); } diff --git a/libraries/networking/src/OAuthAccessToken.h b/libraries/networking/src/OAuthAccessToken.h index 36859b79f8..167bb824da 100644 --- a/libraries/networking/src/OAuthAccessToken.h +++ b/libraries/networking/src/OAuthAccessToken.h @@ -23,6 +23,8 @@ public: OAuthAccessToken(const QJsonObject& jsonObject); OAuthAccessToken(const OAuthAccessToken& otherToken); OAuthAccessToken& operator=(const OAuthAccessToken& otherToken); + + QByteArray authorizationHeaderValue() const { return QString("Bearer %1").arg(token).toUtf8(); } bool isExpired() const { return expiryTimestamp <= QDateTime::currentMSecsSinceEpoch(); } From 21a17823d45dbc827700ab3148b92672d0d280a5 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 17 Sep 2014 16:26:07 -0700 Subject: [PATCH 14/20] Fix for vertical jitter when walking on floor --- interface/src/avatar/MyAvatar.cpp | 49 +++++++++++++--------------- interface/src/avatar/SkeletonModel.h | 1 + 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3f24998566..cec6e55605 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1163,7 +1163,7 @@ void MyAvatar::updateOrientation(float deltaTime) { setOrientation(orientation); } -glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVelocity, bool walkingOnFloor) { +glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVelocity, bool hasFloor) { if (! (_motionBehaviors & AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED)) { return localVelocity; } @@ -1216,7 +1216,7 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe // Compute the target keyboard velocity (which ramps up slowly, and damps very quickly) // the max magnitude of which depends on what we're doing: - float finalMaxMotorSpeed = walkingOnFloor ? _scale * MAX_WALKING_SPEED : _scale * MAX_KEYBOARD_MOTOR_SPEED; + float finalMaxMotorSpeed = hasFloor ? _scale * MAX_WALKING_SPEED : _scale * MAX_KEYBOARD_MOTOR_SPEED; float motorLength = glm::length(_keyboardMotorVelocity); if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) { // an active keyboard motor should never be slower than this @@ -1255,7 +1255,7 @@ void MyAvatar::updatePosition(float deltaTime) { // check for floor by casting a ray straight down from avatar's position float heightAboveFloor = FLT_MAX; - bool walkingOnFloor = false; + bool hasFloor = false; const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); const float maxFloorDistance = boundingShape.getBoundingRadius() * NEARBY_FLOOR_THRESHOLD; @@ -1263,12 +1263,13 @@ void MyAvatar::updatePosition(float deltaTime) { // NOTE: avatar is center of PhysicsSimulation, so rayStart is the origin for the purposes of the raycast intersection._rayStart = glm::vec3(0.0f); intersection._rayDirection = - _worldUpDirection; - intersection._rayLength = 5.0f * boundingShape.getBoundingRadius(); + intersection._rayLength = 4.0f * boundingShape.getBoundingRadius(); if (_physicsSimulation.findFloorRayIntersection(intersection)) { // NOTE: heightAboveFloor is the distance between the bottom of the avatar and the floor - heightAboveFloor = intersection._hitDistance - boundingShape.getBoundingRadius(); + heightAboveFloor = intersection._hitDistance - boundingShape.getBoundingRadius() + + _skeletonModel.getBoundingShapeOffset().y; if (heightAboveFloor < maxFloorDistance) { - walkingOnFloor = true; + hasFloor = true; } } @@ -1281,12 +1282,12 @@ void MyAvatar::updatePosition(float deltaTime) { if (pushingUp || glm::length2(velocity) > MAX_SPEED_UNDER_GRAVITY * MAX_SPEED_UNDER_GRAVITY) { // we're pushing up or moving quickly, so disable gravity setLocalGravity(glm::vec3(0.0f)); - walkingOnFloor = false; + hasFloor = false; } else { if (heightAboveFloor > maxFloorDistance) { // disable local gravity when floor is too far away setLocalGravity(glm::vec3(0.0f)); - walkingOnFloor = false; + hasFloor = false; } else { // enable gravity setLocalGravity(-_worldUpDirection); @@ -1297,15 +1298,18 @@ void MyAvatar::updatePosition(float deltaTime) { bool zeroDownwardVelocity = false; bool gravityEnabled = (glm::length2(_gravity) > EPSILON); if (gravityEnabled) { + const float SLOP = 0.002f; if (heightAboveFloor < 0.0f) { - // Gravity is in effect so we assume that the avatar is colliding against the world and we need - // to lift avatar out of floor, but we don't want to do it too fast (keep it smooth). - float distanceToLift = glm::min(-heightAboveFloor, MAX_WALKING_SPEED * deltaTime); - - // We don't use applyPositionDelta() for this lift distance because we don't want the avatar - // to come flying out of the floor. Instead we update position directly, and set a boolean - // that will remind us later to zero any downward component of the velocity. - _position += (distanceToLift - EPSILON) * _worldUpDirection; + if (heightAboveFloor < -SLOP) { + // Gravity is in effect so we assume that the avatar is colliding against the world and we need + // to lift avatar out of floor, but we don't want to do it too fast (keep it smooth). + float distanceToLift = glm::min(SLOP - heightAboveFloor, MAX_WALKING_SPEED * deltaTime); + + // We don't use applyPositionDelta() for this lift distance because we don't want the avatar + // to come flying out of the floor. Instead we update position directly, and set a boolean + // that will remind us later to zero any downward component of the velocity. + _position += distanceToLift * _worldUpDirection; + } zeroDownwardVelocity = true; } velocity += (deltaTime * GRAVITY_EARTH) * _gravity; @@ -1316,16 +1320,9 @@ void MyAvatar::updatePosition(float deltaTime) { glm::vec3 localVelocity = glm::inverse(rotation) * velocity; // apply motors in camera frame - glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, walkingOnFloor); + glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, hasFloor); newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity); - if (walkingOnFloor && !pushingUp) { - // remove any delta component that points into floor - glm::vec3 deltaVelocity = newLocalVelocity - localVelocity; - deltaVelocity -= glm::dot(deltaVelocity, _worldUpDirection) * _worldUpDirection; - newLocalVelocity = localVelocity + deltaVelocity; - } - // rotate back into world-frame velocity = rotation * newLocalVelocity; @@ -1336,8 +1333,8 @@ void MyAvatar::updatePosition(float deltaTime) { // remove downward velocity so we don't push into floor if (zeroDownwardVelocity) { float verticalSpeed = glm::dot(velocity, _worldUpDirection); - if (verticalSpeed < 0.0f) { - velocity += verticalSpeed * _worldUpDirection; + if (verticalSpeed < 0.0f || !pushingUp) { + velocity -= verticalSpeed * _worldUpDirection; } } diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 9bd8df745a..ca0007ddb4 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -109,6 +109,7 @@ public: void renderJointCollisionShapes(float alpha); float getBoundingShapeRadius() const { return _boundingShape.getRadius(); } const CapsuleShape& getBoundingShape() const { return _boundingShape; } + const glm::vec3 getBoundingShapeOffset() const { return _boundingShapeLocalOffset; } void resetShapePositionsToDefaultPose(); // DEBUG method From 66a368348223c8a03a14cbc9f53396a5c407a7b2 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 17 Sep 2014 16:48:46 -0700 Subject: [PATCH 15/20] Fix timer bug with script engine Occurs when Script.clearInterval() called inside Script.setInterval callback --- libraries/script-engine/src/ScriptEngine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 0f285a0df3..29423b7116 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -614,6 +614,7 @@ void ScriptEngine::timerFired() { if (!callingTimer->isActive()) { // this timer is done, we can kill it + _timerFunctionMap.remove(callingTimer); delete callingTimer; } } From cefe8e026892a16ba4e5c850ac30e1dd1f8ab552 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 17 Sep 2014 17:55:10 -0700 Subject: [PATCH 16/20] scripted motor params are now Q_PROPERTIES --- interface/src/avatar/MyAvatar.cpp | 47 +++++++++++++++++-------------- interface/src/avatar/MyAvatar.h | 18 ++++++------ 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index cec6e55605..f4723e90fc 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -59,6 +59,9 @@ float MAX_KEYBOARD_MOTOR_SPEED = MAX_AVATAR_SPEED; float DEFAULT_KEYBOARD_MOTOR_TIMESCALE = 0.25f; float MIN_SCRIPTED_MOTOR_TIMESCALE = 0.005f; float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f; +const int SCRIPTED_MOTOR_AVATAR_FRAME = 0; +const int SCRIPTED_MOTOR_CAMERA_FRAME = 1; +const int SCRIPTED_MOTOR_WORLD_FRAME = 2; MyAvatar::MyAvatar() : Avatar(), @@ -77,6 +80,7 @@ MyAvatar::MyAvatar() : _keyboardMotorTimescale(DEFAULT_KEYBOARD_MOTOR_TIMESCALE), _scriptedMotorVelocity(0.0f), _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), + _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), _motionBehaviors(AVATAR_MOTION_DEFAULTS), _lookAtTargetAvatar(), _shouldRender(true), @@ -1030,7 +1034,17 @@ void MyAvatar::setAttachmentData(const QVector& attachmentData) _billboardValid = false; } -void MyAvatar::setMotorVelocity(const glm::vec3& velocity) { +QString MyAvatar::getScriptedMotorFrame() const { + QString frame = "avatar"; + if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { + frame = "camera"; + } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_WORLD_FRAME) { + frame = "world"; + } + return frame; +} + +void MyAvatar::setScriptedMotorVelocity(const glm::vec3& velocity) { float MAX_SCRIPTED_MOTOR_SPEED = 500.0f; _scriptedMotorVelocity = velocity; float speed = glm::length(_scriptedMotorVelocity); @@ -1039,13 +1053,23 @@ void MyAvatar::setMotorVelocity(const glm::vec3& velocity) { } } -void MyAvatar::setMotorTimescale(float timescale) { +void MyAvatar::setScriptedMotorTimescale(float timescale) { // we clamp the timescale on the large side (instead of just the low side) to prevent // obnoxiously large values from introducing NaN into avatar's velocity _scriptedMotorTimescale = glm::clamp(timescale, MIN_SCRIPTED_MOTOR_TIMESCALE, DEFAULT_SCRIPTED_MOTOR_TIMESCALE); } +void MyAvatar::setScriptedMotorFrame(QString frame) { + if (frame.toLower() == "avatar") { + _scriptedMotorFrame = SCRIPTED_MOTOR_AVATAR_FRAME; + } else if (frame.toLower() == "camera") { + _scriptedMotorFrame = SCRIPTED_MOTOR_CAMERA_FRAME; + } else if (frame.toLower() == "world") { + _scriptedMotorFrame = SCRIPTED_MOTOR_WORLD_FRAME; + } +} + void MyAvatar::clearScriptableSettings() { clearJointAnimationPriorities(); _scriptedMotorVelocity = glm::vec3(0.0f); @@ -1910,25 +1934,6 @@ void MyAvatar::setCollisionGroups(quint32 collisionGroups) { } } -void MyAvatar::setMotionBehaviorsByScript(quint32 flags) { - // start with the defaults - _motionBehaviors = AVATAR_MOTION_DEFAULTS; - - // add the set scriptable bits - _motionBehaviors += flags & AVATAR_MOTION_SCRIPTABLE_BITS; - - // reconcile incompatible settings from menu (if any) - Menu* menu = Menu::getInstance(); - menu->setIsOptionChecked(MenuOption::ObeyEnvironmentalGravity, (bool)(_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY)); - // Environmental and Local gravities are incompatible. Environmental setting trumps local. - if (_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY) { - _motionBehaviors &= ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY; - setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition())); - } else if (! (_motionBehaviors & (AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY))) { - setGravity(glm::vec3(0.0f)); - } -} - void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { glm::vec3 leverAxis = contactPoint - getPosition(); float leverLength = glm::length(leverAxis); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 43b0f5fc2c..0f723277bc 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -33,7 +33,9 @@ enum AvatarHandState class MyAvatar : public Avatar { Q_OBJECT Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally) - Q_PROPERTY(quint32 motionBehaviors READ getMotionBehaviorsForScript WRITE setMotionBehaviorsByScript) + Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity) + Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale) + Q_PROPERTY(QString motorReferenceFrame READ getScriptedMotorFrame WRITE setScriptedMotorFrame) Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setLocalGravity) public: @@ -131,11 +133,13 @@ public: void clearJointAnimationPriorities(); - Q_INVOKABLE glm::vec3 getMotorVelocity() const { return _scriptedMotorVelocity; } - Q_INVOKABLE float getTimescale() const { return _scriptedMotorTimescale; } + glm::vec3 getScriptedMotorVelocity() const { return _scriptedMotorVelocity; } + float getScriptedMotorTimescale() const { return _scriptedMotorTimescale; } + QString getScriptedMotorFrame() const; - Q_INVOKABLE void setMotorVelocity(const glm::vec3& velocity); - Q_INVOKABLE void setMotorTimescale(float timescale); + void setScriptedMotorVelocity(const glm::vec3& velocity); + void setScriptedMotorTimescale(float timescale); + void setScriptedMotorFrame(QString frame); void clearScriptableSettings(); @@ -145,9 +149,6 @@ public: virtual void setCollisionGroups(quint32 collisionGroups); - void setMotionBehaviorsByScript(quint32 flags); - quint32 getMotionBehaviorsForScript() const { return _motionBehaviors & AVATAR_MOTION_SCRIPTABLE_BITS; } - void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration); /// Renders a laser pointer for UI picking @@ -214,6 +215,7 @@ private: float _keyboardMotorTimescale; // timescale for avatar to achieve its target velocity glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (script) float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity + int _scriptedMotorFrame; quint32 _motionBehaviors; QWeakPointer _lookAtTargetAvatar; From e0a56b4a95c25eb459a1f0e3f5d1275a8b75ca04 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 17 Sep 2014 17:57:08 -0700 Subject: [PATCH 17/20] Rejiggered the blender handling: allow up to N blenders active at once so that with small numbers of avatars, the blenders can stack for lower latency. --- interface/src/renderer/GeometryCache.cpp | 30 ++++++- interface/src/renderer/GeometryCache.h | 9 +- interface/src/renderer/Model.cpp | 100 +++++++++++------------ interface/src/renderer/Model.h | 11 ++- 4 files changed, 91 insertions(+), 59 deletions(-) diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 1ec6ea8f23..7c3c2dc1ca 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -20,6 +20,10 @@ #include "Model.h" #include "world.h" +GeometryCache::GeometryCache() : + _pendingBlenders(0) { +} + GeometryCache::~GeometryCache() { foreach (const VerticesIndices& vbo, _hemisphereVBOs) { glDeleteBuffers(1, &vbo.first); @@ -296,10 +300,30 @@ QSharedPointer GeometryCache::getGeometry(const QUrl& url, cons return getResource(url, fallback, delayLoad).staticCast(); } -void GeometryCache::setBlendedVertices(const QPointer& model, const QWeakPointer& geometry, - const QVector& vertices, const QVector& normals) { +void GeometryCache::noteRequiresBlend(Model* model) { + if (_pendingBlenders < QThread::idealThreadCount()) { + if (model->maybeStartBlender()) { + _pendingBlenders++; + } + return; + } + if (!_modelsRequiringBlends.contains(model)) { + _modelsRequiringBlends.append(model); + } +} + +void GeometryCache::setBlendedVertices(const QPointer& model, int blendNumber, + const QWeakPointer& geometry, const QVector& vertices, const QVector& normals) { if (!model.isNull()) { - model->setBlendedVertices(geometry, vertices, normals); + model->setBlendedVertices(blendNumber, geometry, vertices, normals); + } + _pendingBlenders--; + while (!_modelsRequiringBlends.isEmpty()) { + Model* nextModel = _modelsRequiringBlends.takeFirst(); + if (nextModel && nextModel->maybeStartBlender()) { + _pendingBlenders++; + return; + } } } diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index 2e5725e1b1..647e0592fd 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -35,6 +35,7 @@ class GeometryCache : public ResourceCache { public: + GeometryCache(); virtual ~GeometryCache(); void renderHemisphere(int slices, int stacks); @@ -47,9 +48,12 @@ public: /// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested QSharedPointer getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false); + /// Adds the specified model to the list requiring vertex blends. + void noteRequiresBlend(Model* model); + public slots: - void setBlendedVertices(const QPointer& model, const QWeakPointer& geometry, + void setBlendedVertices(const QPointer& model, int blendNumber, const QWeakPointer& geometry, const QVector& vertices, const QVector& normals); protected: @@ -68,6 +72,9 @@ private: QHash _gridBuffers; QHash > _networkGeometry; + + QList > _modelsRequiringBlends; + int _pendingBlenders; }; /// Geometry loaded from the network. diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 7f83147bb8..19d711a69d 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -62,8 +62,8 @@ Model::Model(QObject* parent) : _lodDistance(0.0f), _pupilDilation(0.0f), _url("http://invalid.com"), - _blenderPending(false), - _blendRequired(false) { + _blendNumber(0), + _appliedBlendNumber(0) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); @@ -826,7 +826,7 @@ void Model::updateShapePositions() { class Blender : public QRunnable { public: - Blender(Model* model, const QWeakPointer& geometry, + Blender(Model* model, int blendNumber, const QWeakPointer& geometry, const QVector& meshes, const QVector& blendshapeCoefficients); virtual void run(); @@ -834,55 +834,55 @@ public: private: QPointer _model; + int _blendNumber; QWeakPointer _geometry; QVector _meshes; QVector _blendshapeCoefficients; }; -Blender::Blender(Model* model, const QWeakPointer& geometry, +Blender::Blender(Model* model, int blendNumber, const QWeakPointer& geometry, const QVector& meshes, const QVector& blendshapeCoefficients) : _model(model), + _blendNumber(blendNumber), _geometry(geometry), _meshes(meshes), _blendshapeCoefficients(blendshapeCoefficients) { } void Blender::run() { - // make sure the model still exists - if (_model.isNull()) { - return; - } QVector vertices, normals; - int offset = 0; - foreach (const FBXMesh& mesh, _meshes) { - if (mesh.blendshapes.isEmpty()) { - continue; - } - vertices += mesh.vertices; - normals += mesh.normals; - glm::vec3* meshVertices = vertices.data() + offset; - glm::vec3* meshNormals = normals.data() + offset; - offset += mesh.vertices.size(); - const float NORMAL_COEFFICIENT_SCALE = 0.01f; - for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { - float vertexCoefficient = _blendshapeCoefficients.at(i); - if (vertexCoefficient < EPSILON) { + if (!_model.isNull()) { + int offset = 0; + foreach (const FBXMesh& mesh, _meshes) { + if (mesh.blendshapes.isEmpty()) { continue; } - float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; - const FBXBlendshape& blendshape = mesh.blendshapes.at(i); - for (int j = 0; j < blendshape.indices.size(); j++) { - int index = blendshape.indices.at(j); - meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; - meshNormals[index] += blendshape.normals.at(j) * normalCoefficient; + vertices += mesh.vertices; + normals += mesh.normals; + glm::vec3* meshVertices = vertices.data() + offset; + glm::vec3* meshNormals = normals.data() + offset; + offset += mesh.vertices.size(); + const float NORMAL_COEFFICIENT_SCALE = 0.01f; + for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { + float vertexCoefficient = _blendshapeCoefficients.at(i); + if (vertexCoefficient < EPSILON) { + continue; + } + float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; + const FBXBlendshape& blendshape = mesh.blendshapes.at(i); + for (int j = 0; j < blendshape.indices.size(); j++) { + int index = blendshape.indices.at(j); + meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; + meshNormals[index] += blendshape.normals.at(j) * normalCoefficient; + } } } } - // post the result to the geometry cache, which will dispatch to the model if still alive QMetaObject::invokeMethod(Application::getInstance()->getGeometryCache(), "setBlendedVertices", - Q_ARG(const QPointer&, _model), Q_ARG(const QWeakPointer&, _geometry), - Q_ARG(const QVector&, vertices), Q_ARG(const QVector&, normals)); + Q_ARG(const QPointer&, _model), Q_ARG(int, _blendNumber), + Q_ARG(const QWeakPointer&, _geometry), Q_ARG(const QVector&, vertices), + Q_ARG(const QVector&, normals)); } void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) { @@ -1020,14 +1020,9 @@ void Model::simulateInternal(float deltaTime) { } // post the blender if we're not currently waiting for one to finish - if (geometry.hasBlendedMeshes()) { - if (_blenderPending) { - _blendRequired = true; - } else { - _blendRequired = false; - _blenderPending = true; - QThreadPool::globalInstance()->start(new Blender(this, _geometry, geometry.meshes, _blendshapeCoefficients)); - } + if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + _blendedBlendshapeCoefficients = _blendshapeCoefficients; + Application::getInstance()->getGeometryCache()->noteRequiresBlend(this); } } @@ -1290,22 +1285,23 @@ void Model::renderJointCollisionShapes(float alpha) { // implement this when we have shapes for regular models } -void Model::setBlendedVertices(const QWeakPointer& geometry, const QVector& vertices, - const QVector& normals) { - _blenderPending = false; - - // start the next blender if required +bool Model::maybeStartBlender() { const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry(); - if (_blendRequired) { - _blendRequired = false; - if (fbxGeometry.hasBlendedMeshes()) { - _blenderPending = true; - QThreadPool::globalInstance()->start(new Blender(this, _geometry, fbxGeometry.meshes, _blendshapeCoefficients)); - } + if (fbxGeometry.hasBlendedMeshes()) { + QThreadPool::globalInstance()->start(new Blender(this, ++_blendNumber, _geometry, + fbxGeometry.meshes, _blendshapeCoefficients)); + return true; } - if (_geometry != geometry || _blendedVertexBuffers.isEmpty()) { + return false; +} + +void Model::setBlendedVertices(int blendNumber, const QWeakPointer& geometry, + const QVector& vertices, const QVector& normals) { + if (_geometry != geometry || _blendedVertexBuffers.isEmpty() || blendNumber < _appliedBlendNumber) { return; } + _appliedBlendNumber = blendNumber; + const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry(); int index = 0; for (int i = 0; i < fbxGeometry.meshes.size(); i++) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); @@ -1358,6 +1354,8 @@ void Model::deleteGeometry() { if (_geometry) { _geometry->clearLoadPriority(this); } + + _blendedBlendshapeCoefficients.clear(); } void Model::renderMeshes(RenderMode mode, bool translucent, bool receiveShadows) { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index f2d98ac589..63b2058a20 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -165,9 +165,11 @@ public: virtual void renderJointCollisionShapes(float alpha); + bool maybeStartBlender(); + /// Sets blended vertices computed in a separate thread. - void setBlendedVertices(const QWeakPointer& geometry, const QVector& vertices, - const QVector& normals); + void setBlendedVertices(int blendNumber, const QWeakPointer& geometry, + const QVector& vertices, const QVector& normals); class LocalLight { public: @@ -285,8 +287,9 @@ private: glm::vec4 _localLightColors[MAX_LOCAL_LIGHTS]; glm::vec4 _localLightDirections[MAX_LOCAL_LIGHTS]; - bool _blenderPending; - bool _blendRequired; + QVector _blendedBlendshapeCoefficients; + int _blendNumber; + int _appliedBlendNumber; static ProgramObject _program; static ProgramObject _normalMapProgram; From 613b270d24a3bfa148f2d364af5f003715465377 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 17 Sep 2014 17:57:50 -0700 Subject: [PATCH 18/20] headMove.js using experimental avatar motor --- examples/headMove.js | 86 +++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/examples/headMove.js b/examples/headMove.js index 0a7686c569..0576b9ccbe 100644 --- a/examples/headMove.js +++ b/examples/headMove.js @@ -16,17 +16,27 @@ var debug = false; var movingWithHead = false; var headStartPosition, headStartDeltaPitch, headStartFinalPitch, headStartRoll, headStartYaw; -var HEAD_MOVE_DEAD_ZONE = 0.0; +var HEAD_MOVE_DEAD_ZONE = 0.10; var HEAD_STRAFE_DEAD_ZONE = 0.0; var HEAD_ROTATE_DEAD_ZONE = 0.0; -var HEAD_THRUST_FWD_SCALE = 12000.0; -var HEAD_THRUST_STRAFE_SCALE = 2000.0; +//var HEAD_THRUST_FWD_SCALE = 12000.0; +//var HEAD_THRUST_STRAFE_SCALE = 0.0; var HEAD_YAW_RATE = 1.0; var HEAD_PITCH_RATE = 1.0; -var HEAD_ROLL_THRUST_SCALE = 75.0; -var HEAD_PITCH_LIFT_THRUST = 3.0; +//var HEAD_ROLL_THRUST_SCALE = 75.0; +//var HEAD_PITCH_LIFT_THRUST = 3.0; var WALL_BOUNCE = 4000.0; +var HEAD_VELOCITY_FWD_FACTOR = 2.0; +var HEAD_VELOCITY_LEFT_FACTOR = 2.0; +var HEAD_VELOCITY_UP_FACTOR = 2.0; +var SHORT_TIMESCALE = 0.25; +var VERY_LARGE_TIMESCALE = 1000000.0; + +var xAxis = {x:1.0, y:0.0, z:0.0 }; +var yAxis = {x:0.0, y:1.0, z:0.0 }; +var zAxis = {x:0.0, y:0.0, z:1.0 }; + // If these values are set to something var maxVelocity = 1.25; var noFly = true; @@ -51,8 +61,8 @@ function isInRoom(position) { } function moveWithHead(deltaTime) { - var thrust = { x: 0, y: 0, z: 0 }; var position = MyAvatar.position; + var motorTimescale = VERY_LARGE_TIMESCALE; if (movingWithHead) { var deltaYaw = MyAvatar.getHeadFinalYaw() - headStartYaw; var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartDeltaPitch; @@ -64,44 +74,47 @@ function moveWithHead(deltaTime) { headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta); headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion - var forward = Quat.getFront(Camera.getOrientation()); - var right = Quat.getRight(Camera.getOrientation()); - var up = Quat.getUp(Camera.getOrientation()); - if (noFly) { - forward.y = 0.0; - forward = Vec3.normalize(forward); - right.y = 0.0; - right = Vec3.normalize(right); - up = { x: 0, y: 1, z: 0}; - } +// var forward = Quat.getFront(Camera.getOrientation()); +// var right = Quat.getRight(Camera.getOrientation()); +// var up = Quat.getUp(Camera.getOrientation()); +// if (noFly) { +// forward.y = 0.0; +// forward = Vec3.normalize(forward); +// right.y = 0.0; +// right = Vec3.normalize(right); +// up = { x: 0, y: 1, z: 0}; +// } // Thrust based on leaning forward and side-to-side + var targetVelocity = {x:0.0, y:0.0, z:0.0}; if (Math.abs(headDelta.z) > HEAD_MOVE_DEAD_ZONE) { - if (Math.abs(Vec3.dot(velocity, forward)) < maxVelocity) { - thrust = Vec3.sum(thrust, Vec3.multiply(forward, -headDelta.z * HEAD_THRUST_FWD_SCALE * deltaTime)); - } + targetVelocity = Vec3.multiply(zAxis, -headDelta.z * HEAD_VELOCITY_FWD_FACTOR); } if (Math.abs(headDelta.x) > HEAD_STRAFE_DEAD_ZONE) { - if (Math.abs(Vec3.dot(velocity, right)) < maxVelocity) { - thrust = Vec3.sum(thrust, Vec3.multiply(right, headDelta.x * HEAD_THRUST_STRAFE_SCALE * deltaTime)); - } + var deltaVelocity = Vec3.multiply(xAxis, -headDelta.x * HEAD_VELOCITY_LEFT_FACTOR); + targetVelocity = Vec3.sum(targetVelocity, deltaVelocity); } if (Math.abs(deltaYaw) > HEAD_ROTATE_DEAD_ZONE) { - var orientation = Quat.multiply(Quat.angleAxis((deltaYaw + deltaRoll) * HEAD_YAW_RATE * deltaTime, {x:0, y: 1, z:0}), MyAvatar.orientation); + var orientation = Quat.multiply(Quat.angleAxis((deltaYaw + deltaRoll) * HEAD_YAW_RATE * deltaTime, yAxis), MyAvatar.orientation); MyAvatar.orientation = orientation; } // Thrust Up/Down based on head pitch if (!noFly) { - if ((Math.abs(Vec3.dot(velocity, up)) < maxVelocity)) { - thrust = Vec3.sum(thrust, Vec3.multiply({ x:0, y:1, z:0 }, (MyAvatar.getHeadFinalPitch() - headStartFinalPitch) * HEAD_PITCH_LIFT_THRUST * deltaTime)); - } + var deltaVelocity = Vec3.multiply(yAxis, headDelta.y * HEAD_VELOCITY_UP_FACTOR); + targetVelocity = Vec3.sum(targetVelocity, deltaVelocity); } // For head trackers, adjust pitch by head pitch MyAvatar.headPitch += deltaPitch * HEAD_PITCH_RATE * deltaTime; + // apply the motor + MyAvatar.setMotorVelocity(targetVelocity); + motorTimescale = SHORT_TIMESCALE; } + + // Check against movement box limits if (isInRoom(position)) { - // Impose constraints to keep you in the space + var thrust = { x: 0, y: 0, z: 0 }; + // use thrust to constrain the avatar to the space if (position.x < roomLimits.xMin) { thrust.x += (roomLimits.xMin - position.x) * WALL_BOUNCE * deltaTime; } else if (position.x > roomLimits.xMax) { @@ -112,11 +125,13 @@ function moveWithHead(deltaTime) { } else if (position.z > roomLimits.zMax) { thrust.z += (roomLimits.zMax - position.z) * WALL_BOUNCE * deltaTime; } + MyAvatar.addThrust(thrust); + if (movingWithHead) { + // reduce the timescale of the motor so that it won't defeat the thrust code + motorTimescale = 2.0 * SHORT_TIMESCALE; + } } - - // Check against movement box limits - - MyAvatar.addThrust(thrust); + MyAvatar.setMotorTimescale(motorTimescale); } Controller.keyPressEvent.connect(function(event) { @@ -126,12 +141,19 @@ Controller.keyPressEvent.connect(function(event) { headStartDeltaPitch = MyAvatar.getHeadDeltaPitch(); headStartFinalPitch = MyAvatar.getHeadFinalPitch(); headStartRoll = MyAvatar.getHeadFinalRoll(); - headStartYaw = MyAvatar.getHeadFinalYaw(); + headStartYaw = MyAvatar.getHeadFinalYaw(); + // start with disabled motor -- it will be updated shortly + MyAvatar.motorTimescale = VERY_LARGE_TIMESCALE; + MyAvatar.motorVelocity = {x:0.0, y:0.0, z:0.0}; + MyAvatar.motorReferenceFrame = "camera"; // alternatives are: "avatar" and "world" } }); Controller.keyReleaseEvent.connect(function(event) { if (event.text == "SPACE") { movingWithHead = false; + // disable motor by giving it an obnoxiously large timescale + MyAvatar.motorTimescale = VERY_LARGE_TIMESCALE; + MyAvatar.motorVelocity = {x:0.0, y:0.0, z:0.0}; } }); From b8f49dc6ef2e0900a0a5367967b28103473f7823 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 17 Sep 2014 21:56:38 -0700 Subject: [PATCH 19/20] fix vertical jitter (for real this time) --- interface/src/avatar/MyAvatar.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6341b07e72..161dad9670 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1324,11 +1324,11 @@ void MyAvatar::updatePosition(float deltaTime) { bool gravityEnabled = (glm::length2(_gravity) > EPSILON); if (gravityEnabled) { const float SLOP = 0.002f; - if (heightAboveFloor < 0.0f) { - if (heightAboveFloor < -SLOP) { + if (heightAboveFloor < SLOP) { + if (heightAboveFloor < 0.0) { // Gravity is in effect so we assume that the avatar is colliding against the world and we need // to lift avatar out of floor, but we don't want to do it too fast (keep it smooth). - float distanceToLift = glm::min(SLOP - heightAboveFloor, MAX_WALKING_SPEED * deltaTime); + float distanceToLift = glm::min(-heightAboveFloor, MAX_WALKING_SPEED * deltaTime); // We don't use applyPositionDelta() for this lift distance because we don't want the avatar // to come flying out of the floor. Instead we update position directly, and set a boolean @@ -1337,7 +1337,9 @@ void MyAvatar::updatePosition(float deltaTime) { } zeroDownwardVelocity = true; } - velocity += (deltaTime * GRAVITY_EARTH) * _gravity; + if (!zeroDownwardVelocity) { + velocity += (deltaTime * GRAVITY_EARTH) * _gravity; + } } // rotate velocity into camera frame From b311333ac31b640b65be43414607a383feedf8b6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 17 Sep 2014 22:35:39 -0700 Subject: [PATCH 20/20] initial tuning of headMove.js for avatar motor --- examples/headMove.js | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/examples/headMove.js b/examples/headMove.js index 0576b9ccbe..359dfd8751 100644 --- a/examples/headMove.js +++ b/examples/headMove.js @@ -27,10 +27,13 @@ var HEAD_PITCH_RATE = 1.0; //var HEAD_PITCH_LIFT_THRUST = 3.0; var WALL_BOUNCE = 4000.0; -var HEAD_VELOCITY_FWD_FACTOR = 2.0; -var HEAD_VELOCITY_LEFT_FACTOR = 2.0; -var HEAD_VELOCITY_UP_FACTOR = 2.0; -var SHORT_TIMESCALE = 0.25; +// Modify these values to tweak the strength of the motion. +// A larger *FACTOR increases the speed. +// A lower SHORT_TIMESCALE makes the motor achieve full speed faster. +var HEAD_VELOCITY_FWD_FACTOR = 20.0; +var HEAD_VELOCITY_LEFT_FACTOR = 20.0; +var HEAD_VELOCITY_UP_FACTOR = 20.0; +var SHORT_TIMESCALE = 0.125; var VERY_LARGE_TIMESCALE = 1000000.0; var xAxis = {x:1.0, y:0.0, z:0.0 }; @@ -74,17 +77,6 @@ function moveWithHead(deltaTime) { headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta); headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion -// var forward = Quat.getFront(Camera.getOrientation()); -// var right = Quat.getRight(Camera.getOrientation()); -// var up = Quat.getUp(Camera.getOrientation()); -// if (noFly) { -// forward.y = 0.0; -// forward = Vec3.normalize(forward); -// right.y = 0.0; -// right = Vec3.normalize(right); -// up = { x: 0, y: 1, z: 0}; -// } - // Thrust based on leaning forward and side-to-side var targetVelocity = {x:0.0, y:0.0, z:0.0}; if (Math.abs(headDelta.z) > HEAD_MOVE_DEAD_ZONE) { @@ -107,7 +99,7 @@ function moveWithHead(deltaTime) { MyAvatar.headPitch += deltaPitch * HEAD_PITCH_RATE * deltaTime; // apply the motor - MyAvatar.setMotorVelocity(targetVelocity); + MyAvatar.motorVelocity = targetVelocity; motorTimescale = SHORT_TIMESCALE; } @@ -126,12 +118,13 @@ function moveWithHead(deltaTime) { thrust.z += (roomLimits.zMax - position.z) * WALL_BOUNCE * deltaTime; } MyAvatar.addThrust(thrust); - if (movingWithHead) { + if (movingWithHead && Vec3.length(thrust) > 0.0) { // reduce the timescale of the motor so that it won't defeat the thrust code - motorTimescale = 2.0 * SHORT_TIMESCALE; + Vec3.print("adebug room containment thrust = ", thrust); + motorTimescale = 4.0 * SHORT_TIMESCALE; } } - MyAvatar.setMotorTimescale(motorTimescale); + MyAvatar.motorTimescale = motorTimescale; } Controller.keyPressEvent.connect(function(event) { @@ -146,15 +139,16 @@ Controller.keyPressEvent.connect(function(event) { MyAvatar.motorTimescale = VERY_LARGE_TIMESCALE; MyAvatar.motorVelocity = {x:0.0, y:0.0, z:0.0}; MyAvatar.motorReferenceFrame = "camera"; // alternatives are: "avatar" and "world" - } + } }); + Controller.keyReleaseEvent.connect(function(event) { if (event.text == "SPACE") { movingWithHead = false; // disable motor by giving it an obnoxiously large timescale MyAvatar.motorTimescale = VERY_LARGE_TIMESCALE; MyAvatar.motorVelocity = {x:0.0, y:0.0, z:0.0}; - } + } }); Script.update.connect(moveWithHead);