diff --git a/examples/hydraMove.js b/examples/hydraMove.js
index ed6a5a4f44..f211a450a3 100644
--- a/examples/hydraMove.js
+++ b/examples/hydraMove.js
@@ -78,7 +78,7 @@ function createDebugOverlay() {
position: defaultPosition,
size: RADIUS,
color: GRAY_COLOR,
- alpha: 1,
+ alpha: 0.75,
visible: true,
solid: true,
anchor: "MyAvatar"
@@ -87,7 +87,7 @@ function createDebugOverlay() {
position: defaultPosition,
size: RADIUS,
color: RED_COLOR,
- alpha: 1,
+ alpha: 0.5,
visible: true,
solid: true,
anchor: "MyAvatar"
@@ -111,7 +111,7 @@ function displayDebug() {
}
} else {
// update debug indicator
- if (greenSphere == -1) {
+ if (greenSphere == -1) {
createDebugOverlay();
}
@@ -149,8 +149,8 @@ function getGrabRotation() {
// When move button is pressed, process results
function handleGrabBehavior(deltaTime) {
// check for and handle grab behaviors
- grabbingWithRightHand = Controller.isButtonPressed(RIGHT_BUTTON_FWD) || Controller.isButtonPressed(RIGHT_BUTTON_4);
- grabbingWithLeftHand = Controller.isButtonPressed(LEFT_BUTTON_FWD) || Controller.isButtonPressed(LEFT_BUTTON_4);
+ grabbingWithRightHand = Controller.isButtonPressed(RIGHT_BUTTON_4);
+ grabbingWithLeftHand = Controller.isButtonPressed(LEFT_BUTTON_4);
stoppedGrabbingWithLeftHand = false;
stoppedGrabbingWithRightHand = false;
@@ -201,20 +201,22 @@ function handleGrabBehavior(deltaTime) {
printVector("grabDelta: ", grabDelta, 3);
}
- var THRUST_GRAB_SCALING = 300000.0;
+ var thrust = Vec3.multiply(grabDelta, Math.abs(Vec3.length(grabDelta)));
+
+ var THRUST_GRAB_SCALING = 100000.0;
- var thrustFront = Vec3.multiply(front, MyAvatar.scale * -grabDelta.z * THRUST_GRAB_SCALING * deltaTime);
+ var thrustFront = Vec3.multiply(front, MyAvatar.scale * -thrust.z * THRUST_GRAB_SCALING * deltaTime);
MyAvatar.addThrust(thrustFront);
- var thrustRight = Vec3.multiply(right, MyAvatar.scale * grabDelta.x * THRUST_GRAB_SCALING * deltaTime);
+ var thrustRight = Vec3.multiply(right, MyAvatar.scale * thrust.x * THRUST_GRAB_SCALING * deltaTime);
MyAvatar.addThrust(thrustRight);
- var thrustUp = Vec3.multiply(up, MyAvatar.scale * grabDelta.y * THRUST_GRAB_SCALING * deltaTime);
+ var thrustUp = Vec3.multiply(up, MyAvatar.scale * thrust.y * THRUST_GRAB_SCALING * deltaTime);
MyAvatar.addThrust(thrustUp);
// add some rotation...
var deltaRotation = getGrabRotation();
- var PITCH_SCALING = 2.0;
+ var PITCH_SCALING = 2.5;
var PITCH_DEAD_ZONE = 2.0;
- var YAW_SCALING = 2.0;
+ var YAW_SCALING = 2.5;
var ROLL_SCALING = 2.0;
var euler = Quat.safeEulerAngles(deltaRotation);
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 96bfa106f4..856e1efae7 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -200,7 +200,8 @@ Menu::Menu() :
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false,
avatar, SLOT(updateMotionBehaviorsFromMenu()));
-
+ addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::StandOnNearbyFloors, 0, true,
+ avatar, SLOT(updateMotionBehaviorsFromMenu()));
addAvatarCollisionSubMenu(editMenu);
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index b12f989ed6..279d2151c9 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -376,6 +376,7 @@ namespace MenuOption {
const QString ShowBordersModelNodes = "Show Model Nodes";
const QString ShowBordersParticleNodes = "Show Particle Nodes";
const QString ShowIKConstraints = "Show IK Constraints";
+ const QString StandOnNearbyFloors = "Stand on nearby floors";
const QString Stars = "Stars";
const QString Stats = "Stats";
const QString StopAllScripts = "Stop All Scripts";
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 042d2cdc7c..c59df941b0 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -134,45 +134,7 @@ void MyAvatar::simulate(float deltaTime) {
_handState = HAND_STATE_NULL;
updateOrientation(deltaTime);
-
- float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) +
- fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) +
- fabsf(_driveKeys[UP] - _driveKeys[DOWN]);
-
- bool walkingOnFloor = false;
- float gravityLength = glm::length(_gravity);
- if (gravityLength > EPSILON) {
- const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape();
- glm::vec3 startCap;
- boundingShape.getStartPoint(startCap);
- glm::vec3 bottomOfBoundingCapsule = startCap + (boundingShape.getRadius() / gravityLength) * _gravity;
-
- float fallThreshold = 2.0f * deltaTime * gravityLength;
- walkingOnFloor = (glm::distance(bottomOfBoundingCapsule, _lastFloorContactPoint) < fallThreshold);
- }
-
- if (keyboardInput > 0.0f || glm::length2(_velocity) > 0.0f || glm::length2(_thrust) > 0.0f ||
- ! walkingOnFloor) {
- // apply gravity
- _velocity += _scale * _gravity * (GRAVITY_EARTH * deltaTime);
-
- // update motor and thrust
- updateMotorFromKeyboard(deltaTime, walkingOnFloor);
- applyMotor(deltaTime);
- applyThrust(deltaTime);
-
- // update position
- if (glm::length2(_velocity) < EPSILON) {
- _velocity = glm::vec3(0.0f);
- } else {
- _position += _velocity * deltaTime;
- }
- }
-
- // update moving flag based on speed
- const float MOVING_SPEED_THRESHOLD = 0.01f;
- _moving = glm::length(_velocity) > MOVING_SPEED_THRESHOLD;
- updateChatCircle(deltaTime);
+ updatePosition(deltaTime);
// update avatar skeleton and simulate hand and head
getHand()->collideAgainstOurself();
@@ -206,19 +168,17 @@ void MyAvatar::simulate(float deltaTime) {
radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.0f));
radius *= COLLISION_RADIUS_SCALAR;
}
- if (_collisionGroups) {
- updateShapePositions();
- if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) {
- updateCollisionWithEnvironment(deltaTime, radius);
- }
- if (_collisionGroups & COLLISION_GROUP_VOXELS) {
- updateCollisionWithVoxels(deltaTime, radius);
- } else {
- _trapDuration = 0.0f;
- }
- if (_collisionGroups & COLLISION_GROUP_AVATARS) {
- updateCollisionWithAvatars(deltaTime);
- }
+ updateShapePositions();
+ if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) {
+ updateCollisionWithEnvironment(deltaTime, radius);
+ }
+ if (_collisionGroups & COLLISION_GROUP_VOXELS) {
+ updateCollisionWithVoxels(deltaTime, radius);
+ } else {
+ _trapDuration = 0.0f;
+ }
+ if (_collisionGroups & COLLISION_GROUP_AVATARS) {
+ updateCollisionWithAvatars(deltaTime);
}
}
@@ -423,9 +383,9 @@ void MyAvatar::setGravity(const glm::vec3& gravity) {
float gravityLength = glm::length(gravity);
if (gravityLength > EPSILON) {
_worldUpDirection = _gravity / -gravityLength;
- } else {
- _worldUpDirection = DEFAULT_UP_DIRECTION;
}
+ // NOTE: the else case here it to leave _worldUpDirection unchanged
+ // so it continues to point opposite to the previous gravity setting.
}
AnimationHandlePointer MyAvatar::addAnimationHandle() {
@@ -890,8 +850,7 @@ void MyAvatar::updateOrientation(float deltaTime) {
// We must adjust the body orientation using a delta rotation (rather than
// doing yaw math) because the body's yaw ranges are not the same
// as what the Oculus API provides.
- glm::vec3 UP_AXIS = glm::vec3(0.0f, 1.0f, 0.0f);
- glm::quat bodyCorrection = glm::angleAxis(glm::radians(delta), UP_AXIS);
+ glm::quat bodyCorrection = glm::angleAxis(glm::radians(delta), _worldUpDirection);
orientation = orientation * bodyCorrection;
}
Head* head = getHead();
@@ -905,6 +864,89 @@ void MyAvatar::updateOrientation(float deltaTime) {
setOrientation(orientation);
}
+void MyAvatar::updatePosition(float deltaTime) {
+ float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) +
+ fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) +
+ fabsf(_driveKeys[UP] - _driveKeys[DOWN]);
+
+ bool walkingOnFloor = false;
+ float gravityLength = glm::length(_gravity) * GRAVITY_EARTH;
+
+ const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape();
+ glm::vec3 startCap;
+ boundingShape.getStartPoint(startCap);
+ glm::vec3 bottomOfBoundingCapsule = startCap - boundingShape.getRadius() * _worldUpDirection;
+
+ if (gravityLength > EPSILON) {
+ float speedFromGravity = _scale * deltaTime * gravityLength;
+ float distanceToFall = glm::distance(bottomOfBoundingCapsule, _lastFloorContactPoint);
+ walkingOnFloor = (distanceToFall < 2.0f * deltaTime * speedFromGravity);
+
+ if (walkingOnFloor) {
+ // BEGIN HACK: to prevent the avatar from bouncing on a floor surface
+ if (distanceToFall < deltaTime * speedFromGravity) {
+ float verticalSpeed = glm::dot(_velocity, _worldUpDirection);
+ if (fabs(verticalSpeed) < speedFromGravity) {
+ // we're standing on a floor, and nearly at rest so we zero the vertical velocity component
+ _velocity -= verticalSpeed * _worldUpDirection;
+ }
+ } else {
+ // fall with gravity against floor
+ _velocity -= speedFromGravity * _worldUpDirection;
+ }
+ // END HACK
+ } else {
+ _velocity -= speedFromGravity * _worldUpDirection;
+ }
+ if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) {
+ const float MAX_VERTICAL_FLOOR_DETECTION_SPEED = _scale * MAX_WALKING_SPEED;
+ if (keyboardInput && glm::dot(_motorVelocity, _worldUpDirection) > 0.0f &&
+ glm::dot(_velocity, _worldUpDirection) > MAX_VERTICAL_FLOOR_DETECTION_SPEED) {
+ // disable gravity because we're pushing with keyboard
+ setLocalGravity(glm::vec3(0.0f));
+ }
+ }
+ } else {
+ if ((_collisionGroups & COLLISION_GROUP_VOXELS) &&
+ _motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) {
+ const float MIN_FLOOR_DETECTION_SPEED = _scale * 1.0f;
+ if (glm::length(_velocity) < MIN_FLOOR_DETECTION_SPEED ) {
+ // scan for floor
+ glm::vec3 direction = -_worldUpDirection;
+ OctreeElement* elementHit; // output from findRayIntersection
+ float distance; // output from findRayIntersection
+ BoxFace face; // output from findRayIntersection
+ Application::getInstance()->getVoxelTree()->findRayIntersection(bottomOfBoundingCapsule, direction, elementHit, distance, face);
+ const float NEARBY_FLOOR_THRESHOLD = _scale * 2.0f;
+ if (elementHit && distance < NEARBY_FLOOR_THRESHOLD) {
+ // turn on local gravity
+ setLocalGravity(-_worldUpDirection);
+ }
+ }
+ }
+ }
+
+ if (keyboardInput > 0.0f || glm::length2(_velocity) > 0.0f || glm::length2(_thrust) > 0.0f || ! walkingOnFloor) {
+ // update motor and thrust
+ updateMotorFromKeyboard(deltaTime, walkingOnFloor);
+ applyMotor(deltaTime);
+ applyThrust(deltaTime);
+
+ // update position
+ if (glm::length2(_velocity) < EPSILON) {
+ _velocity = glm::vec3(0.0f);
+ } else {
+ _position += _velocity * deltaTime;
+ }
+ }
+
+ // update moving flag based on speed
+ const float MOVING_SPEED_THRESHOLD = 0.01f;
+ _moving = glm::length(_velocity) > MOVING_SPEED_THRESHOLD;
+
+ updateChatCircle(deltaTime);
+}
+
void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) {
// Increase motor velocity until its length is equal to _maxMotorSpeed.
if (!(_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED)) {
@@ -930,12 +972,12 @@ void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) {
if (directionLength > EPSILON) {
direction /= directionLength;
// the finalMotorSpeed depends on whether we are walking or not
- float finalMaxMotorSpeed = walking ? MAX_WALKING_SPEED : _maxMotorSpeed;
+ float finalMaxMotorSpeed = walking ? _scale * MAX_WALKING_SPEED : _scale * _maxMotorSpeed;
float motorLength = glm::length(_motorVelocity);
- if (motorLength < MIN_KEYBOARD_CONTROL_SPEED) {
+ if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) {
// an active keyboard motor should never be slower than this
- _motorVelocity = MIN_KEYBOARD_CONTROL_SPEED * direction;
+ _motorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction;
} else {
float MOTOR_LENGTH_TIMESCALE = 1.5f;
float tau = glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f);
@@ -1178,6 +1220,8 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
const float MIN_STEP_HEIGHT = 0.0f;
glm::vec3 footBase = boundingShape.getPosition() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection;
float highestStep = 0.0f;
+ float lowestStep = MAX_STEP_HEIGHT;
+ glm::vec3 floorPoint;
glm::vec3 stepPenetration(0.0f);
glm::vec3 totalPenetration(0.0f);
@@ -1211,8 +1255,15 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
highestStep = stepHeight;
stepPenetration = collision->_penetration;
}
+ if (stepHeight < lowestStep) {
+ lowestStep = stepHeight;
+ floorPoint = collision->_contactPoint - collision->_penetration;
+ }
}
}
+ if (lowestStep < MAX_STEP_HEIGHT) {
+ _lastFloorContactPoint = floorPoint;
+ }
float penetrationLength = glm::length(totalPenetration);
if (penetrationLength < EPSILON) {
@@ -1251,8 +1302,9 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
}
- const float VOXEL_COLLISION_FREQUENCY = 0.5f;
- updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY);
+ // Don't make a collision sound against voxlels by default -- too annoying when walking
+ //const float VOXEL_COLLISION_FREQUENCY = 0.5f;
+ //updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY);
}
_trapDuration = isTrapped ? _trapDuration + deltaTime : 0.0f;
}
@@ -1598,7 +1650,8 @@ void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) {
}
void MyAvatar::updateMotionBehaviorsFromMenu() {
- if (Menu::getInstance()->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) {
+ Menu* menu = Menu::getInstance();
+ if (menu->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) {
_motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY;
// Environmental and Local gravities are incompatible. Environmental setting trumps local.
_motionBehaviors &= ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
@@ -1608,6 +1661,14 @@ void MyAvatar::updateMotionBehaviorsFromMenu() {
if (! (_motionBehaviors & (AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY))) {
setGravity(glm::vec3(0.0f));
}
+ if (menu->isOptionChecked(MenuOption::StandOnNearbyFloors)) {
+ _motionBehaviors |= AVATAR_MOTION_STAND_ON_NEARBY_FLOORS;
+ // standing on floors requires collision with voxels
+ _collisionGroups |= COLLISION_GROUP_VOXELS;
+ menu->setIsOptionChecked(MenuOption::CollideWithVoxels, true);
+ } else {
+ _motionBehaviors &= ~AVATAR_MOTION_STAND_ON_NEARBY_FLOORS;
+ }
}
void MyAvatar::renderAttachments(RenderMode renderMode) {
@@ -1634,6 +1695,11 @@ void MyAvatar::setCollisionGroups(quint32 collisionGroups) {
menu->setIsOptionChecked(MenuOption::CollideWithAvatars, (bool)(_collisionGroups & COLLISION_GROUP_AVATARS));
menu->setIsOptionChecked(MenuOption::CollideWithVoxels, (bool)(_collisionGroups & COLLISION_GROUP_VOXELS));
menu->setIsOptionChecked(MenuOption::CollideWithParticles, (bool)(_collisionGroups & COLLISION_GROUP_PARTICLES));
+ if (! (_collisionGroups & COLLISION_GROUP_VOXELS)) {
+ // no collision with voxels --> disable standing on floors
+ _motionBehaviors &= ~AVATAR_MOTION_STAND_ON_NEARBY_FLOORS;
+ menu->setIsOptionChecked(MenuOption::StandOnNearbyFloors, false);
+ }
}
void MyAvatar::setMotionBehaviorsByScript(quint32 flags) {
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index a8eb3babd0..f8e9fc2e28 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -150,7 +150,6 @@ private:
bool _shouldJump;
float _driveKeys[MAX_DRIVE_KEYS];
glm::vec3 _gravity;
- glm::vec3 _environmentGravity;
float _distanceToNearestAvatar; // How close is the nearest avatar?
bool _wasPushing;
@@ -175,6 +174,7 @@ private:
// private methods
void updateOrientation(float deltaTime);
+ void updatePosition(float deltaTime);
void updateMotorFromKeyboard(float deltaTime, bool walking);
float computeMotorTimescale();
void applyMotor(float deltaTime);
diff --git a/interface/src/ui/SnapshotShareDialog.cpp b/interface/src/ui/SnapshotShareDialog.cpp
index b5694b3e48..617d5e7101 100644
--- a/interface/src/ui/SnapshotShareDialog.cpp
+++ b/interface/src/ui/SnapshotShareDialog.cpp
@@ -31,6 +31,10 @@ const QString FORUM_REPLY_TO_TOPIC = "244";
const QString FORUM_POST_TEMPLATE = "
%2
"; const QString SHARE_DEFAULT_ERROR = "The server isn't responding. Please try again in a few minutes."; const QString SUCCESS_LABEL_TEMPLATE = "Success!!! Go check out your image ...