Merge pull request #8883 from AndrewMeadows/drive-hmd

driving HMD forward stops at walls
This commit is contained in:
Anthony Thibault 2016-10-24 15:20:43 -07:00 committed by GitHub
commit c78f2b5409
4 changed files with 179 additions and 32 deletions

View file

@ -1324,8 +1324,7 @@ void MyAvatar::updateMotors() {
}
if (qApp->isHMDMode()) {
// OUTOFBODY_HACK: only apply vertical component of _actionMotorVelocity to the characterController
_characterController.addMotor(glm::vec3(0.0f, _actionMotorVelocity.y, 0.0f), motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE);
// OUTOFBODY_HACK: motors are applied differently in HMDMode
} else {
if (_isPushing || _isBraking || !_isBeingPushed) {
_characterController.addMotor(_actionMotorVelocity, motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE);
@ -1346,8 +1345,7 @@ void MyAvatar::updateMotors() {
motorRotation = glm::quat();
}
if (qApp->isHMDMode()) {
// OUTOFBODY_HACK: only apply vertical component of _scriptedMotorVelocity to the characterController
_characterController.addMotor(glm::vec3(0, _scriptedMotorVelocity.y, 0), motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE);
// OUTOFBODY_HACK: motors are applied differently in HMDMode
} else {
_characterController.addMotor(_scriptedMotorVelocity, motorRotation, _scriptedMotorTimescale);
}
@ -1901,10 +1899,12 @@ void MyAvatar::updatePosition(float deltaTime) {
if (qApp->isHMDMode()) {
// Apply _actionMotorVelocity directly to the sensorToWorld matrix.
glm::quat motorRotation;
glm::quat liftRotation;
swingTwistDecomposition(glmExtractRotation(_sensorToWorldMatrix * getHMDSensorMatrix()), _worldUpDirection, liftRotation, motorRotation);
glm::vec3 worldVelocity = motorRotation * _actionMotorVelocity;
applyVelocityToSensorToWorldMatrix(worldVelocity, deltaTime);
glm::vec3 worldVelocity = glm::vec3(0.0f);
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
glm::quat liftRotation;
swingTwistDecomposition(glmExtractRotation(_sensorToWorldMatrix * getHMDSensorMatrix()), _worldUpDirection, liftRotation, motorRotation);
worldVelocity = motorRotation * _actionMotorVelocity;
}
// Apply _scriptedMotorVelocity to the sensorToWorld matrix.
if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) {
@ -1916,13 +1916,20 @@ void MyAvatar::updatePosition(float deltaTime) {
// world-frame
motorRotation = glm::quat();
}
worldVelocity = motorRotation * _scriptedMotorVelocity;
applyVelocityToSensorToWorldMatrix(worldVelocity, deltaTime);
worldVelocity += motorRotation * _scriptedMotorVelocity;
}
// OUTOFBODY_HACK: apply scaling factor to _thrust, to get the same behavior as an periodically applied motor.
const float THRUST_DAMPING_FACTOR = 0.25f;
applyVelocityToSensorToWorldMatrix(THRUST_DAMPING_FACTOR * _thrust, deltaTime);
worldVelocity += THRUST_DAMPING_FACTOR * _thrust;
if (glm::length2(worldVelocity) > FLT_EPSILON) {
glm::mat4 worldBodyMatrix = _sensorToWorldMatrix * _bodySensorMatrix;
glm::vec3 position = extractTranslation(worldBodyMatrix);
glm::vec3 step = deltaTime * worldVelocity;
glm::vec3 newVelocity = _characterController.computeHMDStep(position, step) / deltaTime;
applyVelocityToSensorToWorldMatrix(newVelocity, deltaTime);
}
}
}

166
interface/src/avatar/MyCharacterController.cpp Normal file → Executable file
View file

@ -92,6 +92,13 @@ void MyCharacterController::updateShapeIfNecessary() {
}
bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result) {
btVector3 rayDirection = glmToBullet(step);
btScalar stepLength = rayDirection.length();
if (stepLength < FLT_EPSILON) {
return false;
}
rayDirection /= stepLength;
// get _ghost ready for ray traces
btTransform transform = _rigidBody->getWorldTransform();
btVector3 newPosition = glmToBullet(position);
@ -104,14 +111,6 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm:
CharacterRayResult closestRayResult(&_ghost);
btVector3 rayStart;
btVector3 rayEnd;
btVector3 rayDirection = glmToBullet(step);
btScalar stepLength = rayDirection.length();
if (stepLength < FLT_EPSILON) {
return false;
}
rayDirection /= stepLength;
const btScalar backSlop = 0.04f;
// compute rotation that will orient local ray start points to face step direction
btVector3 forward = rotation * btVector3(0.0f, 0.0f, -1.0f);
@ -150,6 +149,7 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm:
forwardSlop = 0.0f;
}
const btScalar backSlop = 0.04f;
for (int32_t i = 0; i < _topPoints.size(); ++i) {
rayStart = newPosition + rotation * _topPoints[i] - backSlop * rayDirection;
rayEnd = rayStart + (backSlop + stepLength + forwardSlop) * rayDirection;
@ -191,6 +191,156 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm:
return result.hitFraction < 1.0f;
}
glm::vec3 MyCharacterController::computeHMDStep(const glm::vec3& position, const glm::vec3& step) {
btVector3 stepDirection = glmToBullet(step);
btScalar stepLength = stepDirection.length();
if (stepLength < FLT_EPSILON) {
return glm::vec3(0.0f);
}
stepDirection /= stepLength;
// get _ghost ready for ray traces
btTransform transform = _rigidBody->getWorldTransform();
btVector3 newPosition = glmToBullet(position);
transform.setOrigin(newPosition);
btMatrix3x3 rotation = transform.getBasis();
_ghost.setWorldTransform(transform);
_ghost.refreshOverlappingPairCache();
// compute rotation that will orient local ray start points to face stepDirection
btVector3 forward = rotation * btVector3(0.0f, 0.0f, -1.0f);
btVector3 horizontalDirection = stepDirection - stepDirection.dot(_currentUp) * _currentUp;
btVector3 axis = forward.cross(horizontalDirection);
btScalar lengthAxis = axis.length();
if (lengthAxis > FLT_EPSILON) {
// non-zero sideways component
btScalar angle = acosf(lengthAxis / horizontalDirection.length());
if (stepDirection.dot(forward) < 0.0f) {
angle = PI - angle;
}
axis /= lengthAxis;
rotation = btMatrix3x3(btQuaternion(axis, angle)) * rotation;
} else if (stepDirection.dot(forward) < 0.0f) {
// backwards
rotation = btMatrix3x3(btQuaternion(_currentUp, PI)) * rotation;
}
// scan the top
// NOTE: if we scan an extra distance forward we can detect flat surfaces that are too steep to walk on.
// The approximate extra distance can be derived with trigonometry.
//
// minimumForward = [ (maxStepHeight + radius / cosTheta - radius) * (cosTheta / sinTheta) - radius ]
//
// where: theta = max angle between floor normal and vertical
//
// if stepLength is not long enough we can add the difference.
//
btScalar cosTheta = _minFloorNormalDotUp;
btScalar sinTheta = sqrtf(1.0f - cosTheta * cosTheta);
const btScalar MIN_FORWARD_SLOP = 0.12f; // HACK: not sure why this is necessary to detect steepest walkable slope
btScalar forwardSlop = (_maxStepHeight + _radius / cosTheta - _radius) * (cosTheta / sinTheta) - (_radius + stepLength) + MIN_FORWARD_SLOP;
if (forwardSlop < 0.0f) {
// BIG step, no slop necessary
forwardSlop = 0.0f;
}
// we push the step forward by stepMargin to help reduce accidental penetration
btScalar stepMargin = glm::max(_radius, 0.4f);
btScalar expandedStepLength = stepLength + forwardSlop + stepMargin;
bool slideOnWalls = false; // HACK: hard coded for now, maybe we'll make it optional
// loop
CharacterRayResult rayResult(&_ghost);
btVector3 rayStart;
btVector3 rayEnd;
btVector3 penetration = btVector3(0.0f, 0.0f, 0.0f);
int32_t numPenetrations = 0;
btScalar closestHitFraction = 1.0f;
bool walkable = true;
for (int32_t i = 0; i < _topPoints.size(); ++i) {
rayStart = newPosition + rotation * _topPoints[i];
rayEnd = rayStart + expandedStepLength * stepDirection;
rayResult.m_closestHitFraction = 1.0f; // reset rayResult for next test
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
// check if walkable
if (walkable) {
if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) {
walkable = false;
}
}
btScalar adjustedHitFraction = (rayResult.m_closestHitFraction * expandedStepLength - stepMargin) / stepLength;
if (adjustedHitFraction < 1.0f) {
if (slideOnWalls) {
// sum penetration
btScalar depth = ((1.0f - adjustedHitFraction) * stepLength) * stepDirection.dot(rayResult.m_hitNormalWorld);
penetration -= depth * rayResult.m_hitNormalWorld;
++numPenetrations;
} else if (adjustedHitFraction < 0.0f) {
// evidence suggests we need to back out of penetration however there is a
// literal corner case where backing out may put us in deeper penetration,
// so we check for it by casting another ray straight out from center
rayEnd = rayStart;
rayStart = rayStart.dot(_currentUp) * _currentUp;
btVector3 segment = rayEnd - rayStart;
btScalar radius = segment.length();
if (radius > FLT_EPSILON) {
rayEnd += (stepMargin / radius) * segment;
CharacterRayResult checkResult(&_ghost);
if (_ghost.rayTest(rayStart, rayEnd, checkResult)) {
if (checkResult.m_hitNormalWorld.dot(stepDirection) > 0.0f) {
// the second test says backing out would be a bad idea
continue;
}
}
}
}
if (adjustedHitFraction < closestHitFraction) {
closestHitFraction = adjustedHitFraction;
const btScalar STEEP_ENOUGH_TO_BACK_OUT = -0.3f;
if (adjustedHitFraction < 0.0f && stepDirection.dot(rayResult.m_hitNormalWorld) < STEEP_ENOUGH_TO_BACK_OUT) {
closestHitFraction = 0.0f;
}
}
}
}
}
/* TODO: implement sliding along sloped floors
bool steppingUp = false;
if (walkable && closestHitFraction > 0.0f) {
// scan the bottom
for (int32_t i = 0; i < _bottomPoints.size(); ++i) {
rayStart = newPosition + rotation * _bottomPoints[i];
rayEnd = rayStart + (stepLength + stepMargin) * stepDirection;
rayResult.m_closestHitFraction = 1.0f; // reset rayResult for next test
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
btScalar adjustedHitFraction = (rayResult.m_closestHitFraction * expandedStepLength - stepMargin) / stepLength;
if (adjustedHitFraction < 1.0f) {
steppingUp = true;
break;
}
}
}
}
*/
btVector3 finalStep = stepLength * stepDirection;
if (slideOnWalls) {
if (numPenetrations > 1) {
penetration /= (btScalar)numPenetrations;
}
finalStep += penetration;
} else {
finalStep *= closestHitFraction;
}
/* TODO: implement sliding along sloped floors
if (steppingUp) {
finalStep += stepLength * _currentUp;
}
*/
return bulletToGLM(finalStep);
}
btConvexHullShape* MyCharacterController::computeShape() const {
// HACK: the avatar collides using convex hull with a collision margin equal to
// the old capsule radius. Two points define a capsule and additional points are

View file

@ -43,6 +43,8 @@ public:
/// return true if RayShotgun hits anything
bool testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result);
glm::vec3 computeHMDStep(const glm::vec3& position, const glm::vec3& step);
protected:
void initRayShotgun(const btCollisionWorld* world);

View file

@ -15,9 +15,6 @@ var inTeleportMode = false;
var SMOOTH_ARRIVAL_SPACING = 33;
var NUMBER_OF_STEPS_FOR_TELEPORT = 6;
var AVATAR_DRAG_SPACING = 33;
var NUMBER_OF_STEPS_FOR_AVATAR_DRAG = 12;
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport-destination.fbx");
var TOO_CLOSE_MODEL_URL = Script.resolvePath("../assets/models/teleport-cancel.fbx");
var TARGET_MODEL_DIMENSIONS = {
@ -91,10 +88,7 @@ function Teleporter() {
this.cancelOverlay = null;
this.updateConnected = null;
this.smoothArrivalInterval = null;
this.dragAvatarInterval = null;
this.oldAvatarCollisionsEnabled = MyAvatar.avatarCollisionsEnabled;
this.teleportHand = null;
this.distance = 0.0;
this.teleportMode = "HMDAndAvatarTogether";
this.tooClose = false;
this.inCoolIn = false;
@ -136,10 +130,6 @@ function Teleporter() {
if (this.smoothArrivalInterval !== null) {
Script.clearInterval(this.smoothArrivalInterval);
}
if (this.dragAvatarInterval !== null) {
Script.clearInterval(this.dragAvatarInterval);
MyAvatar.avatarCollisionsEnabled = _this.oldAvatarCollisionsEnabled;
}
if (activationTimeout !== null) {
Script.clearInterval(activationTimeout);
}
@ -503,7 +493,6 @@ function Teleporter() {
z: intersection.intersection.z
};
this.distance = Vec3.distance(MyAvatar.position, position);
this.tooClose = isValidTeleportLocation(position, intersection.surfaceNormal);
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
@ -526,7 +515,6 @@ function Teleporter() {
z: intersection.intersection.z
};
this.distance = Vec3.distance(MyAvatar.position, position);
this.tooClose = isValidTeleportLocation(position, intersection.surfaceNormal);
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
@ -558,7 +546,7 @@ function Teleporter() {
this.intersection.intersection.y += offset;
this.exitTeleportMode();
if (MyAvatar.hmdLeanRecenterEnabled) {
if (this.distance > MAX_HMD_AVATAR_SEPARATION) {
if (Vec3.distance(MyAvatar.position, _this.intersection.intersection) > MAX_HMD_AVATAR_SEPARATION) {
this.teleportMode = "HMDAndAvatarTogether";
} else {
this.teleportMode = "HMDFirstAvatarWillFollow";