mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 14:08:51 +02:00
Merge pull request #8883 from AndrewMeadows/drive-hmd
driving HMD forward stops at walls
This commit is contained in:
commit
c78f2b5409
4 changed files with 179 additions and 32 deletions
|
@ -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
166
interface/src/avatar/MyCharacterController.cpp
Normal file → Executable 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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in a new issue