mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 01:17:14 +02:00
Merge pull request #8626 from AndrewMeadows/oobe3
out of body experience: avatar follows HMD position using velocity motor instead of micro teleports
This commit is contained in:
commit
fdb6110c46
6 changed files with 249 additions and 233 deletions
|
@ -1229,6 +1229,8 @@ void MyAvatar::rebuildCollisionShape() {
|
||||||
float scale = getUniformScale();
|
float scale = getUniformScale();
|
||||||
float radius = scale * _skeletonModel->getBoundingCapsuleRadius();
|
float radius = scale * _skeletonModel->getBoundingCapsuleRadius();
|
||||||
float height = scale * _skeletonModel->getBoundingCapsuleHeight() + 2.0f * radius;
|
float height = scale * _skeletonModel->getBoundingCapsuleHeight() + 2.0f * radius;
|
||||||
|
const float CANONICAL_AVATAR_HEIGHT = 2.0f;
|
||||||
|
_canonicalScale = height / CANONICAL_AVATAR_HEIGHT;
|
||||||
glm::vec3 corner(-radius, -0.5f * height, -radius);
|
glm::vec3 corner(-radius, -0.5f * height, -radius);
|
||||||
corner += scale * _skeletonModel->getBoundingCapsuleOffset();
|
corner += scale * _skeletonModel->getBoundingCapsuleOffset();
|
||||||
glm::vec3 diagonal(2.0f * radius, height, 2.0f * radius);
|
glm::vec3 diagonal(2.0f * radius, height, 2.0f * radius);
|
||||||
|
@ -1384,9 +1386,7 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
|
||||||
//_bodySensorMatrix = deriveBodyFromHMDSensor();
|
//_bodySensorMatrix = deriveBodyFromHMDSensor();
|
||||||
|
|
||||||
if (_characterController.isEnabledAndReady()) {
|
if (_characterController.isEnabledAndReady()) {
|
||||||
setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
|
setVelocity(_characterController.getLinearVelocity());
|
||||||
} else {
|
|
||||||
setVelocity(getVelocity() + _characterController.getFollowVelocity());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_follow.postPhysicsUpdate(*this);
|
_follow.postPhysicsUpdate(*this);
|
||||||
|
|
|
@ -500,6 +500,7 @@ private:
|
||||||
|
|
||||||
bool _hmdLeanRecenterEnabled = true;
|
bool _hmdLeanRecenterEnabled = true;
|
||||||
bool _moveKinematically { false }; // KINEMATIC_CONTROLLER_HACK
|
bool _moveKinematically { false }; // KINEMATIC_CONTROLLER_HACK
|
||||||
|
float _canonicalScale { 1.0f };
|
||||||
|
|
||||||
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
||||||
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
||||||
|
|
|
@ -75,9 +75,6 @@ CharacterController::CharacterController() {
|
||||||
_takeoffToInAirStartTime = 0;
|
_takeoffToInAirStartTime = 0;
|
||||||
_jumpButtonDownStartTime = 0;
|
_jumpButtonDownStartTime = 0;
|
||||||
_jumpButtonDownCount = 0;
|
_jumpButtonDownCount = 0;
|
||||||
_followTime = 0.0f;
|
|
||||||
_followLinearDisplacement = btVector3(0, 0, 0);
|
|
||||||
_followAngularDisplacement = btQuaternion::getIdentity();
|
|
||||||
_hasSupport = false;
|
_hasSupport = false;
|
||||||
|
|
||||||
_pendingFlags = PENDING_FLAG_UPDATE_SHAPE;
|
_pendingFlags = PENDING_FLAG_UPDATE_SHAPE;
|
||||||
|
@ -130,11 +127,12 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
||||||
// KINEMATIC_CONTROLLER_HACK
|
// KINEMATIC_CONTROLLER_HACK
|
||||||
_ghost.setCollisionGroupAndMask(_collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ _collisionGroup));
|
_ghost.setCollisionGroupAndMask(_collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ _collisionGroup));
|
||||||
_ghost.setCollisionWorld(_dynamicsWorld);
|
_ghost.setCollisionWorld(_dynamicsWorld);
|
||||||
_ghost.setDistanceToFeet(_radius + _halfHeight);
|
_ghost.setRadiusAndHalfHeight(_radius, _halfHeight);
|
||||||
_ghost.setMaxStepHeight(0.75f * (_radius + _halfHeight)); // HACK
|
_ghost.setMaxStepHeight(0.75f * (_radius + _halfHeight)); // HACK
|
||||||
_ghost.setMinWallAngle(PI / 4.0f); // HACK
|
_ghost.setMinWallAngle(PI / 4.0f); // HACK
|
||||||
_ghost.setUpDirection(_currentUp);
|
_ghost.setUpDirection(_currentUp);
|
||||||
_ghost.setGravity(DEFAULT_CHARACTER_GRAVITY);
|
_ghost.setGravity(DEFAULT_CHARACTER_GRAVITY);
|
||||||
|
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
|
||||||
}
|
}
|
||||||
if (_dynamicsWorld) {
|
if (_dynamicsWorld) {
|
||||||
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
||||||
|
@ -176,10 +174,10 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) cons
|
||||||
|
|
||||||
void CharacterController::preStep(btCollisionWorld* collisionWorld) {
|
void CharacterController::preStep(btCollisionWorld* collisionWorld) {
|
||||||
// trace a ray straight down to see if we're standing on the ground
|
// trace a ray straight down to see if we're standing on the ground
|
||||||
const btTransform& xform = _rigidBody->getWorldTransform();
|
const btTransform& transform = _rigidBody->getWorldTransform();
|
||||||
|
|
||||||
// rayStart is at center of bottom sphere
|
// rayStart is at center of bottom sphere
|
||||||
btVector3 rayStart = xform.getOrigin() - _halfHeight * _currentUp;
|
btVector3 rayStart = transform.getOrigin() - _halfHeight * _currentUp;
|
||||||
|
|
||||||
// rayEnd is some short distance outside bottom sphere
|
// rayEnd is some short distance outside bottom sphere
|
||||||
const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius;
|
const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius;
|
||||||
|
@ -202,6 +200,58 @@ const btScalar MIN_TARGET_SPEED_SQUARED = MIN_TARGET_SPEED * MIN_TARGET_SPEED;
|
||||||
|
|
||||||
void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) {
|
void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) {
|
||||||
btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity;
|
btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity;
|
||||||
|
if (_following) {
|
||||||
|
// linear part uses a motor
|
||||||
|
const float MAX_WALKING_SPEED = 2.5f; // TODO: scale this stuff with avatar size
|
||||||
|
const float MAX_WALKING_SPEED_DISTANCE = 1.0f;
|
||||||
|
const float NORMAL_WALKING_SPEED = 0.5f * MAX_WALKING_SPEED;
|
||||||
|
const float NORMAL_WALKING_SPEED_DISTANCE = 0.5f * MAX_WALKING_SPEED_DISTANCE;
|
||||||
|
const float FEW_SUBSTEPS = 4.0f * dt;
|
||||||
|
btTransform bodyTransform = _rigidBody->getWorldTransform();
|
||||||
|
btVector3 startPos = bodyTransform.getOrigin();
|
||||||
|
btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos;
|
||||||
|
btScalar deltaDistance = deltaPos.length();
|
||||||
|
const float MIN_DELTA_DISTANCE = 0.01f; // TODO: scale by avatar size but cap at (NORMAL_WALKING_SPEED * FEW_SUBSTEPS)
|
||||||
|
if (deltaDistance > MIN_DELTA_DISTANCE) {
|
||||||
|
btVector3 vel = deltaPos;
|
||||||
|
if (deltaDistance > MAX_WALKING_SPEED_DISTANCE) {
|
||||||
|
// cap max speed
|
||||||
|
vel *= MAX_WALKING_SPEED / deltaDistance;
|
||||||
|
} else if (deltaDistance > NORMAL_WALKING_SPEED_DISTANCE) {
|
||||||
|
// linearly interpolate to NORMAL_WALKING_SPEED
|
||||||
|
btScalar alpha = (deltaDistance - NORMAL_WALKING_SPEED_DISTANCE) / (MAX_WALKING_SPEED_DISTANCE - NORMAL_WALKING_SPEED_DISTANCE);
|
||||||
|
vel *= NORMAL_WALKING_SPEED * (1.0f - alpha) + MAX_WALKING_SPEED * alpha;
|
||||||
|
} else {
|
||||||
|
// use exponential decay but cap at NORMAL_WALKING_SPEED
|
||||||
|
vel /= FEW_SUBSTEPS;
|
||||||
|
btScalar speed = vel.length();
|
||||||
|
if (speed > NORMAL_WALKING_SPEED) {
|
||||||
|
vel *= NORMAL_WALKING_SPEED / speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const float HORIZONTAL_FOLLOW_TIMESCALE = 0.1f;
|
||||||
|
const float VERTICAL_FOLLOW_TIMESCALE = (_state == State::Hover) ? HORIZONTAL_FOLLOW_TIMESCALE : 20.0f;
|
||||||
|
glm::quat worldFrameRotation; // identity
|
||||||
|
addMotor(bulletToGLM(vel), worldFrameRotation, HORIZONTAL_FOLLOW_TIMESCALE, VERTICAL_FOLLOW_TIMESCALE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// angular part uses incremental teleports
|
||||||
|
const float ANGULAR_FOLLOW_TIMESCALE = 0.8f;
|
||||||
|
const float MAX_ANGULAR_SPEED = (PI / 2.0f) / ANGULAR_FOLLOW_TIMESCALE;
|
||||||
|
btQuaternion startRot = bodyTransform.getRotation();
|
||||||
|
glm::vec2 currentFacing = getFacingDir2D(bulletToGLM(startRot));
|
||||||
|
glm::vec2 currentRight(currentFacing.y, - currentFacing.x);
|
||||||
|
glm::vec2 desiredFacing = getFacingDir2D(bulletToGLM(_followDesiredBodyTransform.getRotation()));
|
||||||
|
float deltaAngle = acosf(glm::clamp(glm::dot(currentFacing, desiredFacing), -1.0f, 1.0f));
|
||||||
|
float angularSpeed = deltaAngle / FEW_SUBSTEPS;
|
||||||
|
if (angularSpeed > MAX_ANGULAR_SPEED) {
|
||||||
|
angularSpeed *= MAX_ANGULAR_SPEED / angularSpeed;
|
||||||
|
}
|
||||||
|
float sign = copysignf(1.0f, glm::dot(desiredFacing, currentRight));
|
||||||
|
btQuaternion angularDisplacement = btQuaternion(btVector3(0.0f, 1.0f, 0.0f), sign * angularSpeed * dt);
|
||||||
|
btQuaternion endRot = angularDisplacement * startRot;
|
||||||
|
_rigidBody->setWorldTransform(btTransform(endRot, startPos));
|
||||||
|
}
|
||||||
computeNewVelocity(dt, velocity);
|
computeNewVelocity(dt, velocity);
|
||||||
|
|
||||||
if (_moveKinematically) {
|
if (_moveKinematically) {
|
||||||
|
@ -209,10 +259,11 @@ void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) {
|
||||||
btTransform transform = _rigidBody->getWorldTransform();
|
btTransform transform = _rigidBody->getWorldTransform();
|
||||||
transform.setOrigin(_ghost.getWorldTransform().getOrigin());
|
transform.setOrigin(_ghost.getWorldTransform().getOrigin());
|
||||||
_ghost.setWorldTransform(transform);
|
_ghost.setWorldTransform(transform);
|
||||||
_ghost.setMotorVelocity(_simpleMotorVelocity);
|
_ghost.setMotorVelocity(_targetVelocity);
|
||||||
float overshoot = 1.0f * _radius;
|
float overshoot = 1.0f * _radius;
|
||||||
_ghost.move(dt, overshoot);
|
_ghost.move(dt, overshoot);
|
||||||
_rigidBody->setWorldTransform(_ghost.getWorldTransform());
|
transform.setOrigin(_ghost.getWorldTransform().getOrigin());
|
||||||
|
_rigidBody->setWorldTransform(transform);
|
||||||
_rigidBody->setLinearVelocity(_ghost.getLinearVelocity());
|
_rigidBody->setLinearVelocity(_ghost.getLinearVelocity());
|
||||||
} else {
|
} else {
|
||||||
// Dynamicaly compute a follow velocity to move this body toward the _followDesiredBodyTransform.
|
// Dynamicaly compute a follow velocity to move this body toward the _followDesiredBodyTransform.
|
||||||
|
@ -220,50 +271,6 @@ void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) {
|
||||||
// This mirrors the computation done in MyAvatar::FollowHelper::postPhysicsUpdate().
|
// This mirrors the computation done in MyAvatar::FollowHelper::postPhysicsUpdate().
|
||||||
|
|
||||||
_rigidBody->setLinearVelocity(velocity + _parentVelocity);
|
_rigidBody->setLinearVelocity(velocity + _parentVelocity);
|
||||||
if (_following) {
|
|
||||||
// OUTOFBODY_HACK -- these consts were copied from elsewhere, and then tuned
|
|
||||||
const float NORMAL_WALKING_SPEED = 1.5f; // actual walk speed is 2.5 m/sec
|
|
||||||
const float FOLLOW_TIME = 0.8f;
|
|
||||||
const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f);
|
|
||||||
const float FOLLOW_FACTOR = 0.5f;
|
|
||||||
|
|
||||||
const float MAX_ANGULAR_SPEED = FOLLOW_ROTATION_THRESHOLD / FOLLOW_TIME;
|
|
||||||
|
|
||||||
btTransform bodyTransform = _rigidBody->getWorldTransform();
|
|
||||||
|
|
||||||
btVector3 startPos = bodyTransform.getOrigin();
|
|
||||||
btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos;
|
|
||||||
btVector3 vel = deltaPos * (FOLLOW_FACTOR / dt);
|
|
||||||
btScalar speed = vel.length();
|
|
||||||
if (speed > NORMAL_WALKING_SPEED) {
|
|
||||||
vel *= NORMAL_WALKING_SPEED / speed;
|
|
||||||
}
|
|
||||||
btVector3 linearDisplacement = vel * dt;
|
|
||||||
btVector3 endPos = startPos + linearDisplacement;
|
|
||||||
|
|
||||||
btQuaternion startRot = bodyTransform.getRotation();
|
|
||||||
glm::vec2 currentFacing = getFacingDir2D(bulletToGLM(startRot));
|
|
||||||
glm::vec2 currentRight(currentFacing.y, -currentFacing.x);
|
|
||||||
glm::vec2 desiredFacing = getFacingDir2D(bulletToGLM(_followDesiredBodyTransform.getRotation()));
|
|
||||||
float deltaAngle = acosf(glm::clamp(glm::dot(currentFacing, desiredFacing), -1.0f, 1.0f));
|
|
||||||
float angularSpeed = 0.5f * deltaAngle / dt;
|
|
||||||
if (angularSpeed > MAX_ANGULAR_SPEED) {
|
|
||||||
angularSpeed *= MAX_ANGULAR_SPEED / angularSpeed;
|
|
||||||
}
|
|
||||||
float sign = copysignf(1.0f, glm::dot(desiredFacing, currentRight));
|
|
||||||
btQuaternion angularDisplacement = btQuaternion(btVector3(0.0f, 1.0f, 0.0f), sign * angularSpeed * dt);
|
|
||||||
btQuaternion endRot = angularDisplacement * startRot;
|
|
||||||
|
|
||||||
// in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account.
|
|
||||||
btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset);
|
|
||||||
btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset);
|
|
||||||
|
|
||||||
_followLinearDisplacement = linearDisplacement + swingDisplacement + _followLinearDisplacement;
|
|
||||||
_followAngularDisplacement = angularDisplacement * _followAngularDisplacement;
|
|
||||||
|
|
||||||
_rigidBody->setWorldTransform(btTransform(endRot, endPos));
|
|
||||||
}
|
|
||||||
_followTime += dt;
|
|
||||||
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
|
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -399,9 +406,8 @@ void CharacterController::setPositionAndOrientation(
|
||||||
// TODO: update gravity if up has changed
|
// TODO: update gravity if up has changed
|
||||||
updateUpAxis(orientation);
|
updateUpAxis(orientation);
|
||||||
|
|
||||||
btQuaternion bodyOrientation = glmToBullet(orientation);
|
_rotation = glmToBullet(orientation);
|
||||||
btVector3 bodyPosition = glmToBullet(position + orientation * _shapeLocalOffset);
|
_position = glmToBullet(position + orientation * _shapeLocalOffset);
|
||||||
_characterBodyTransform = btTransform(bodyOrientation, bodyPosition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const {
|
void CharacterController::getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const {
|
||||||
|
@ -421,22 +427,6 @@ void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyM
|
||||||
_following = true;
|
_following = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 CharacterController::getFollowLinearDisplacement() const {
|
|
||||||
return bulletToGLM(_followLinearDisplacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::quat CharacterController::getFollowAngularDisplacement() const {
|
|
||||||
return bulletToGLM(_followAngularDisplacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 CharacterController::getFollowVelocity() const {
|
|
||||||
if (_followTime > 0.0f) {
|
|
||||||
return bulletToGLM(_followLinearDisplacement) / _followTime;
|
|
||||||
} else {
|
|
||||||
return glm::vec3();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 CharacterController::getLinearVelocity() const {
|
glm::vec3 CharacterController::getLinearVelocity() const {
|
||||||
glm::vec3 velocity(0.0f);
|
glm::vec3 velocity(0.0f);
|
||||||
if (_rigidBody) {
|
if (_rigidBody) {
|
||||||
|
@ -484,10 +474,11 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
|
||||||
if (tau > 1.0f) {
|
if (tau > 1.0f) {
|
||||||
tau = 1.0f;
|
tau = 1.0f;
|
||||||
}
|
}
|
||||||
velocity += (motor.velocity - velocity) * tau;
|
velocity += tau * (motor.velocity - velocity);
|
||||||
|
|
||||||
// rotate back into world-frame
|
// rotate back into world-frame
|
||||||
velocity = velocity.rotate(axis, angle);
|
velocity = velocity.rotate(axis, angle);
|
||||||
|
_targetVelocity += (tau * motor.velocity).rotate(axis, angle);
|
||||||
|
|
||||||
// store the velocity and weight
|
// store the velocity and weight
|
||||||
velocities.push_back(velocity);
|
velocities.push_back(velocity);
|
||||||
|
@ -525,7 +516,7 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
|
||||||
|
|
||||||
// add components back together and rotate into world-frame
|
// add components back together and rotate into world-frame
|
||||||
velocity = (hVelocity + vVelocity).rotate(axis, angle);
|
velocity = (hVelocity + vVelocity).rotate(axis, angle);
|
||||||
_simpleMotorVelocity += maxTau * (hTargetVelocity + vTargetVelocity).rotate(axis, angle);
|
_targetVelocity += maxTau * (hTargetVelocity + vTargetVelocity).rotate(axis, angle);
|
||||||
|
|
||||||
// store velocity and weights
|
// store velocity and weights
|
||||||
velocities.push_back(velocity);
|
velocities.push_back(velocity);
|
||||||
|
@ -543,7 +534,7 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) {
|
||||||
velocities.reserve(_motors.size());
|
velocities.reserve(_motors.size());
|
||||||
std::vector<btScalar> weights;
|
std::vector<btScalar> weights;
|
||||||
weights.reserve(_motors.size());
|
weights.reserve(_motors.size());
|
||||||
_simpleMotorVelocity = btVector3(0.0f, 0.0f, 0.0f);
|
_targetVelocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||||
for (int i = 0; i < (int)_motors.size(); ++i) {
|
for (int i = 0; i < (int)_motors.size(); ++i) {
|
||||||
applyMotor(i, dt, velocity, velocities, weights);
|
applyMotor(i, dt, velocity, velocities, weights);
|
||||||
}
|
}
|
||||||
|
@ -559,15 +550,18 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) {
|
||||||
for (size_t i = 0; i < velocities.size(); ++i) {
|
for (size_t i = 0; i < velocities.size(); ++i) {
|
||||||
velocity += (weights[i] / totalWeight) * velocities[i];
|
velocity += (weights[i] / totalWeight) * velocities[i];
|
||||||
}
|
}
|
||||||
_simpleMotorVelocity /= totalWeight;
|
_targetVelocity /= totalWeight;
|
||||||
}
|
}
|
||||||
if (velocity.length2() < MIN_TARGET_SPEED_SQUARED) {
|
if (velocity.length2() < MIN_TARGET_SPEED_SQUARED) {
|
||||||
velocity = btVector3(0.0f, 0.0f, 0.0f);
|
velocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'thrust' is applied at the very end
|
// 'thrust' is applied at the very end
|
||||||
|
_targetVelocity += dt * _linearAcceleration;
|
||||||
velocity += dt * _linearAcceleration;
|
velocity += dt * _linearAcceleration;
|
||||||
_targetVelocity = velocity;
|
// Note the differences between these two variables:
|
||||||
|
// _targetVelocity = ideal final velocity according to input
|
||||||
|
// velocity = real final velocity after motors are applied to current velocity
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::computeNewVelocity(btScalar dt, glm::vec3& velocity) {
|
void CharacterController::computeNewVelocity(btScalar dt, glm::vec3& velocity) {
|
||||||
|
@ -576,45 +570,40 @@ void CharacterController::computeNewVelocity(btScalar dt, glm::vec3& velocity) {
|
||||||
velocity = bulletToGLM(btVelocity);
|
velocity = bulletToGLM(btVelocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::preSimulation() {
|
void CharacterController::updateState() {
|
||||||
if (_dynamicsWorld) {
|
|
||||||
quint64 now = usecTimestampNow();
|
|
||||||
|
|
||||||
// slam body to where it is supposed to be
|
|
||||||
_rigidBody->setWorldTransform(_characterBodyTransform);
|
|
||||||
btVector3 velocity = _rigidBody->getLinearVelocity();
|
|
||||||
_preSimulationVelocity = velocity;
|
|
||||||
|
|
||||||
// scan for distant floor
|
|
||||||
// rayStart is at center of bottom sphere
|
|
||||||
btVector3 rayStart = _characterBodyTransform.getOrigin();
|
|
||||||
|
|
||||||
// rayEnd is straight down MAX_FALL_HEIGHT
|
|
||||||
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
|
|
||||||
btVector3 rayEnd = rayStart - rayLength * _currentUp;
|
|
||||||
|
|
||||||
const btScalar FLY_TO_GROUND_THRESHOLD = 0.1f * _radius;
|
const btScalar FLY_TO_GROUND_THRESHOLD = 0.1f * _radius;
|
||||||
const btScalar GROUND_TO_FLY_THRESHOLD = 0.8f * _radius + _halfHeight;
|
const btScalar GROUND_TO_FLY_THRESHOLD = 0.8f * _radius + _halfHeight;
|
||||||
const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND;
|
const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND;
|
||||||
const btScalar MIN_HOVER_HEIGHT = 2.5f;
|
const btScalar MIN_HOVER_HEIGHT = 2.5f;
|
||||||
const quint64 JUMP_TO_HOVER_PERIOD = 1100 * MSECS_PER_SECOND;
|
const quint64 JUMP_TO_HOVER_PERIOD = 1100 * MSECS_PER_SECOND;
|
||||||
const btScalar MAX_WALKING_SPEED = 2.5f;
|
|
||||||
const quint64 RAY_HIT_START_PERIOD = 500 * MSECS_PER_SECOND;
|
// scan for distant floor
|
||||||
|
// rayStart is at center of bottom sphere
|
||||||
|
btVector3 rayStart = _position;
|
||||||
|
|
||||||
|
// rayEnd is straight down MAX_FALL_HEIGHT
|
||||||
|
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
|
||||||
|
btVector3 rayEnd = rayStart - rayLength * _currentUp;
|
||||||
|
|
||||||
ClosestNotMe rayCallback(_rigidBody);
|
ClosestNotMe rayCallback(_rigidBody);
|
||||||
rayCallback.m_closestHitFraction = 1.0f;
|
rayCallback.m_closestHitFraction = 1.0f;
|
||||||
_dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
|
_dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
|
||||||
bool rayHasHit = rayCallback.hasHit();
|
bool rayHasHit = rayCallback.hasHit();
|
||||||
|
quint64 now = usecTimestampNow();
|
||||||
if (rayHasHit) {
|
if (rayHasHit) {
|
||||||
_rayHitStartTime = now;
|
_rayHitStartTime = now;
|
||||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - (_radius + _halfHeight);
|
_floorDistance = rayLength * rayCallback.m_closestHitFraction - (_radius + _halfHeight);
|
||||||
} else if ((now - _rayHitStartTime) < RAY_HIT_START_PERIOD) {
|
} else {
|
||||||
|
const quint64 RAY_HIT_START_PERIOD = 500 * MSECS_PER_SECOND;
|
||||||
|
if ((now - _rayHitStartTime) < RAY_HIT_START_PERIOD) {
|
||||||
rayHasHit = true;
|
rayHasHit = true;
|
||||||
} else {
|
} else {
|
||||||
_floorDistance = FLT_MAX;
|
_floorDistance = FLT_MAX;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// record a time stamp when the jump button was first pressed.
|
// record a time stamp when the jump button was first pressed.
|
||||||
|
bool jumpButtonHeld = _pendingFlags & PENDING_FLAG_JUMP;
|
||||||
if ((_previousFlags & PENDING_FLAG_JUMP) != (_pendingFlags & PENDING_FLAG_JUMP)) {
|
if ((_previousFlags & PENDING_FLAG_JUMP) != (_pendingFlags & PENDING_FLAG_JUMP)) {
|
||||||
if (_pendingFlags & PENDING_FLAG_JUMP) {
|
if (_pendingFlags & PENDING_FLAG_JUMP) {
|
||||||
_jumpButtonDownStartTime = now;
|
_jumpButtonDownStartTime = now;
|
||||||
|
@ -622,10 +611,7 @@ void CharacterController::preSimulation() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool jumpButtonHeld = _pendingFlags & PENDING_FLAG_JUMP;
|
btVector3 velocity = _preSimulationVelocity;
|
||||||
|
|
||||||
btVector3 actualHorizVelocity = velocity - velocity.dot(_currentUp) * _currentUp;
|
|
||||||
bool flyingFast = _state == State::Hover && actualHorizVelocity.length() > (MAX_WALKING_SPEED * 0.75f);
|
|
||||||
|
|
||||||
// OUTOFBODY_HACK -- disable normal state transitions while collisionless
|
// OUTOFBODY_HACK -- disable normal state transitions while collisionless
|
||||||
if (_collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) {
|
if (_collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) {
|
||||||
|
@ -668,6 +654,10 @@ void CharacterController::preSimulation() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case State::Hover:
|
case State::Hover:
|
||||||
|
btVector3 actualHorizVelocity = velocity - velocity.dot(_currentUp) * _currentUp;
|
||||||
|
const btScalar MAX_WALKING_SPEED = 2.5f;
|
||||||
|
bool flyingFast = _state == State::Hover && actualHorizVelocity.length() > (MAX_WALKING_SPEED * 0.75f);
|
||||||
|
|
||||||
if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
|
if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
|
||||||
SET_STATE(State::InAir, "near ground");
|
SET_STATE(State::InAir, "near ground");
|
||||||
} else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) {
|
} else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) {
|
||||||
|
@ -675,8 +665,11 @@ void CharacterController::preSimulation() {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (_moveKinematically && _ghost.isHovering()) {
|
||||||
|
SET_STATE(State::Hover, "kinematic motion"); // HACK
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// OUTOFBODY_HACK -- in collisionless state switch between Ground and Hover states
|
// OUTOFBODY_HACK -- in collisionless state switch only between Ground and Hover states
|
||||||
if (rayHasHit) {
|
if (rayHasHit) {
|
||||||
SET_STATE(State::Ground, "collisionless above ground");
|
SET_STATE(State::Ground, "collisionless above ground");
|
||||||
} else {
|
} else {
|
||||||
|
@ -685,22 +678,23 @@ void CharacterController::preSimulation() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CharacterController::preSimulation() {
|
||||||
|
if (_dynamicsWorld) {
|
||||||
|
// slam body transform and remember velocity
|
||||||
|
_rigidBody->setWorldTransform(btTransform(btTransform(_rotation, _position)));
|
||||||
|
_preSimulationVelocity = _rigidBody->getLinearVelocity();
|
||||||
|
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
_previousFlags = _pendingFlags;
|
_previousFlags = _pendingFlags;
|
||||||
_pendingFlags &= ~PENDING_FLAG_JUMP;
|
_pendingFlags &= ~PENDING_FLAG_JUMP;
|
||||||
|
|
||||||
_followTime = 0.0f;
|
|
||||||
_followLinearDisplacement = btVector3(0.0f, 0.0f, 0.0f);
|
|
||||||
_followAngularDisplacement = btQuaternion::getIdentity();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::postSimulation() {
|
void CharacterController::postSimulation() {
|
||||||
// postSimulation() exists for symmetry and just in case we need to do something here later
|
_velocityChange = _rigidBody->getLinearVelocity() - _preSimulationVelocity;
|
||||||
|
|
||||||
btVector3 velocity = _rigidBody->getLinearVelocity();
|
|
||||||
_velocityChange = velocity - _preSimulationVelocity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool CharacterController::getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation) {
|
bool CharacterController::getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation) {
|
||||||
if (!_rigidBody) {
|
if (!_rigidBody) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -88,10 +88,6 @@ public:
|
||||||
void setParentVelocity(const glm::vec3& parentVelocity);
|
void setParentVelocity(const glm::vec3& parentVelocity);
|
||||||
void setFollowParameters(const glm::mat4& desiredWorldBodyMatrix);
|
void setFollowParameters(const glm::mat4& desiredWorldBodyMatrix);
|
||||||
void disableFollow() { _following = false; }
|
void disableFollow() { _following = false; }
|
||||||
float getFollowTime() const { return _followTime; }
|
|
||||||
glm::vec3 getFollowLinearDisplacement() const;
|
|
||||||
glm::quat getFollowAngularDisplacement() const;
|
|
||||||
glm::vec3 getFollowVelocity() const;
|
|
||||||
|
|
||||||
glm::vec3 getLinearVelocity() const;
|
glm::vec3 getLinearVelocity() const;
|
||||||
glm::vec3 getVelocityChange() const;
|
glm::vec3 getVelocityChange() const;
|
||||||
|
@ -109,6 +105,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
State getState() const { return _state; }
|
State getState() const { return _state; }
|
||||||
|
void updateState();
|
||||||
|
|
||||||
void setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale);
|
void setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale);
|
||||||
|
|
||||||
|
@ -152,9 +149,9 @@ protected:
|
||||||
btVector3 _parentVelocity;
|
btVector3 _parentVelocity;
|
||||||
btVector3 _preSimulationVelocity;
|
btVector3 _preSimulationVelocity;
|
||||||
btVector3 _velocityChange;
|
btVector3 _velocityChange;
|
||||||
btVector3 _simpleMotorVelocity; // KINEMATIC_CONTROLLER_HACK
|
|
||||||
btTransform _followDesiredBodyTransform;
|
btTransform _followDesiredBodyTransform;
|
||||||
btTransform _characterBodyTransform;
|
btVector3 _position;
|
||||||
|
btQuaternion _rotation;
|
||||||
|
|
||||||
glm::vec3 _shapeLocalOffset;
|
glm::vec3 _shapeLocalOffset;
|
||||||
|
|
||||||
|
@ -175,9 +172,6 @@ protected:
|
||||||
btScalar _gravity;
|
btScalar _gravity;
|
||||||
|
|
||||||
btScalar _jumpSpeed;
|
btScalar _jumpSpeed;
|
||||||
btScalar _followTime;
|
|
||||||
btVector3 _followLinearDisplacement;
|
|
||||||
btQuaternion _followAngularDisplacement;
|
|
||||||
btVector3 _linearAcceleration;
|
btVector3 _linearAcceleration;
|
||||||
bool _following { false };
|
bool _following { false };
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <PhysicsHelpers.h>
|
||||||
|
|
||||||
#include "CharacterGhostShape.h"
|
#include "CharacterGhostShape.h"
|
||||||
#include "CharacterRayResult.h"
|
#include "CharacterRayResult.h"
|
||||||
|
|
||||||
|
@ -38,6 +40,10 @@ void CharacterGhostObject::getCollisionGroupAndMask(int16_t& group, int16_t& mas
|
||||||
mask = _collisionFilterMask;
|
mask = _collisionFilterMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CharacterGhostObject::setRadiusAndHalfHeight(btScalar radius, btScalar halfHeight) {
|
||||||
|
_radius = radius;
|
||||||
|
_halfHeight = halfHeight;
|
||||||
|
}
|
||||||
|
|
||||||
void CharacterGhostObject::setUpDirection(const btVector3& up) {
|
void CharacterGhostObject::setUpDirection(const btVector3& up) {
|
||||||
btScalar length = up.length();
|
btScalar length = up.length();
|
||||||
|
@ -99,10 +105,12 @@ void CharacterGhostObject::move(btScalar dt, btScalar overshoot) {
|
||||||
|
|
||||||
// TODO: figure out how to untrap character
|
// TODO: figure out how to untrap character
|
||||||
}
|
}
|
||||||
|
btTransform startTransform = getWorldTransform();
|
||||||
|
btVector3 startPosition = startTransform.getOrigin();
|
||||||
if (_onFloor) {
|
if (_onFloor) {
|
||||||
// a floor was identified during resolvePenetration()
|
// resolvePenetration() pushed the avatar out of a floor so
|
||||||
_hovering = false;
|
// we must updateTraction() before using _linearVelocity
|
||||||
updateTraction();
|
updateTraction(startPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
btVector3 forwardSweep = dt * _linearVelocity;
|
btVector3 forwardSweep = dt * _linearVelocity;
|
||||||
|
@ -110,7 +118,7 @@ void CharacterGhostObject::move(btScalar dt, btScalar overshoot) {
|
||||||
btScalar MIN_SWEEP_DISTANCE = 0.0001f;
|
btScalar MIN_SWEEP_DISTANCE = 0.0001f;
|
||||||
if (stepDistance < MIN_SWEEP_DISTANCE) {
|
if (stepDistance < MIN_SWEEP_DISTANCE) {
|
||||||
// not moving, no need to sweep
|
// not moving, no need to sweep
|
||||||
updateHoverState(getWorldTransform());
|
updateTraction(startPosition);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,22 +136,19 @@ void CharacterGhostObject::move(btScalar dt, btScalar overshoot) {
|
||||||
|
|
||||||
// step forward
|
// step forward
|
||||||
CharacterSweepResult result(this);
|
CharacterSweepResult result(this);
|
||||||
btTransform startTransform = getWorldTransform();
|
btTransform nextTransform = startTransform;
|
||||||
btTransform transform = startTransform;
|
nextTransform.setOrigin(startPosition + forwardSweep);
|
||||||
btTransform nextTransform = transform;
|
sweepTest(convexShape, startTransform, nextTransform, result); // forward
|
||||||
nextTransform.setOrigin(transform.getOrigin() + forwardSweep);
|
|
||||||
sweepTest(convexShape, transform, nextTransform, result); // forward
|
|
||||||
|
|
||||||
if (!result.hasHit()) {
|
if (!result.hasHit()) {
|
||||||
nextTransform.setOrigin(transform.getOrigin() + (stepDistance / longSweepDistance) * forwardSweep);
|
nextTransform.setOrigin(startPosition + (stepDistance / longSweepDistance) * forwardSweep);
|
||||||
setWorldTransform(nextTransform);
|
setWorldTransform(nextTransform);
|
||||||
updateHoverState(nextTransform);
|
updateTraction(nextTransform.getOrigin());
|
||||||
updateTraction();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if this hit is obviously unsteppable
|
// check if this hit is obviously unsteppable
|
||||||
btVector3 hitFromBase = result.m_hitPointWorld - (transform.getOrigin() - (_distanceToFeet * _upDirection));
|
btVector3 hitFromBase = result.m_hitPointWorld - (startPosition - ((_radius + _halfHeight) * _upDirection));
|
||||||
btScalar hitHeight = hitFromBase.dot(_upDirection);
|
btScalar hitHeight = hitFromBase.dot(_upDirection);
|
||||||
if (hitHeight > _maxStepHeight) {
|
if (hitHeight > _maxStepHeight) {
|
||||||
// capsule can't step over the obstacle so move forward as much as possible before we bail
|
// capsule can't step over the obstacle so move forward as much as possible before we bail
|
||||||
|
@ -152,8 +157,8 @@ void CharacterGhostObject::move(btScalar dt, btScalar overshoot) {
|
||||||
if (forwardDistance > stepDistance) {
|
if (forwardDistance > stepDistance) {
|
||||||
forwardTranslation *= stepDistance / forwardDistance;
|
forwardTranslation *= stepDistance / forwardDistance;
|
||||||
}
|
}
|
||||||
transform.setOrigin(transform.getOrigin() + forwardTranslation);
|
nextTransform.setOrigin(startPosition + forwardTranslation);
|
||||||
setWorldTransform(transform);
|
setWorldTransform(nextTransform);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// if we get here then we hit something that might be steppable
|
// if we get here then we hit something that might be steppable
|
||||||
|
@ -166,35 +171,37 @@ void CharacterGhostObject::move(btScalar dt, btScalar overshoot) {
|
||||||
|
|
||||||
// raise by availableStepHeight before sweeping forward
|
// raise by availableStepHeight before sweeping forward
|
||||||
result.resetHitHistory();
|
result.resetHitHistory();
|
||||||
transform.setOrigin(startTransform.getOrigin() + availableStepHeight * _upDirection);
|
startTransform.setOrigin(startPosition + availableStepHeight * _upDirection);
|
||||||
nextTransform.setOrigin(transform.getOrigin() + forwardSweep);
|
nextTransform.setOrigin(startTransform.getOrigin() + forwardSweep);
|
||||||
sweepTest(convexShape, transform, nextTransform, result);
|
sweepTest(convexShape, startTransform, nextTransform, result);
|
||||||
if (result.hasHit()) {
|
if (result.hasHit()) {
|
||||||
transform.setOrigin(transform.getOrigin() + result.m_closestHitFraction * forwardSweep);
|
startTransform.setOrigin(startTransform.getOrigin() + result.m_closestHitFraction * forwardSweep);
|
||||||
} else {
|
} else {
|
||||||
transform = nextTransform;
|
startTransform = nextTransform;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sweep down in search of future landing spot
|
// sweep down in search of future landing spot
|
||||||
result.resetHitHistory();
|
result.resetHitHistory();
|
||||||
btVector3 downSweep = (dt * _linearVelocity.dot(_upDirection) - availableStepHeight) * _upDirection;
|
btVector3 downSweep = (- availableStepHeight) * _upDirection;
|
||||||
nextTransform.setOrigin(transform.getOrigin() + downSweep);
|
nextTransform.setOrigin(startTransform.getOrigin() + downSweep);
|
||||||
sweepTest(convexShape, transform, nextTransform, result);
|
sweepTest(convexShape, startTransform, nextTransform, result);
|
||||||
if (result.hasHit() && result.m_hitNormalWorld.dot(_upDirection) > _maxWallNormalUpComponent) {
|
if (result.hasHit() && result.m_hitNormalWorld.dot(_upDirection) > _maxWallNormalUpComponent) {
|
||||||
// can stand on future landing spot, so we interpolate toward it
|
// can stand on future landing spot, so we interpolate toward it
|
||||||
_floorNormal = result.m_hitNormalWorld;
|
_floorNormal = result.m_hitNormalWorld;
|
||||||
|
_floorContact = result.m_hitPointWorld;
|
||||||
_onFloor = true;
|
_onFloor = true;
|
||||||
_hovering = false;
|
_hovering = false;
|
||||||
nextTransform.setOrigin(transform.getOrigin() + result.m_closestHitFraction * downSweep);
|
nextTransform.setOrigin(startTransform.getOrigin() + result.m_closestHitFraction * downSweep);
|
||||||
btVector3 totalStep = nextTransform.getOrigin() - startTransform.getOrigin();
|
btVector3 totalStep = nextTransform.getOrigin() - startPosition;
|
||||||
transform.setOrigin(startTransform.getOrigin() + (stepDistance / totalStep.length()) * totalStep);
|
nextTransform.setOrigin(startPosition + (stepDistance / totalStep.length()) * totalStep);
|
||||||
|
updateTraction(nextTransform.getOrigin());
|
||||||
} else {
|
} else {
|
||||||
// either there is no future landing spot, or there is but we can't stand on it
|
// either there is no future landing spot, or there is but we can't stand on it
|
||||||
// in any case: we go forward as much as possible
|
// in any case: we go forward as much as possible
|
||||||
transform.setOrigin(startTransform.getOrigin() + forwardSweepHitFraction * (stepDistance / longSweepDistance) * forwardSweep);
|
nextTransform.setOrigin(startPosition + forwardSweepHitFraction * (stepDistance / longSweepDistance) * forwardSweep);
|
||||||
|
updateTraction(nextTransform.getOrigin());
|
||||||
}
|
}
|
||||||
setWorldTransform(transform);
|
setWorldTransform(nextTransform);
|
||||||
updateTraction();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CharacterGhostObject::sweepTest(
|
bool CharacterGhostObject::sweepTest(
|
||||||
|
@ -297,6 +304,11 @@ bool CharacterGhostObject::resolvePenetration(int numTries) {
|
||||||
if (normalDotUp > _maxWallNormalUpComponent) {
|
if (normalDotUp > _maxWallNormalUpComponent) {
|
||||||
mostFloorPenetration = penetrationDepth;
|
mostFloorPenetration = penetrationDepth;
|
||||||
_floorNormal = normal;
|
_floorNormal = normal;
|
||||||
|
if (directionSign > 0.0f) {
|
||||||
|
_floorContact = pt.m_positionWorldOnA;
|
||||||
|
} else {
|
||||||
|
_floorContact = pt.m_positionWorldOnB;
|
||||||
|
}
|
||||||
_onFloor = true;
|
_onFloor = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -327,17 +339,36 @@ void CharacterGhostObject::refreshOverlappingPairCache() {
|
||||||
|
|
||||||
void CharacterGhostObject::updateVelocity(btScalar dt) {
|
void CharacterGhostObject::updateVelocity(btScalar dt) {
|
||||||
if (_hovering) {
|
if (_hovering) {
|
||||||
_linearVelocity *= 0.99f; // HACK damping
|
_linearVelocity *= 0.999f; // HACK damping
|
||||||
} else {
|
} else {
|
||||||
_linearVelocity += (dt * _gravity) * _upDirection;
|
_linearVelocity += (dt * _gravity) * _upDirection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterGhostObject::updateTraction() {
|
void CharacterGhostObject::updateHoverState(const btVector3& position) {
|
||||||
|
if (_onFloor) {
|
||||||
|
_hovering = false;
|
||||||
|
} else {
|
||||||
|
// cast a ray down looking for floor support
|
||||||
|
CharacterRayResult rayResult(this);
|
||||||
|
btScalar distanceToFeet = _radius + _halfHeight;
|
||||||
|
btScalar slop = 2.0f * getCollisionShape()->getMargin(); // slop to help ray start OUTSIDE the floor object
|
||||||
|
btVector3 startPos = position - ((distanceToFeet - slop) * _upDirection);
|
||||||
|
btVector3 endPos = startPos - (2.0f * distanceToFeet) * _upDirection;
|
||||||
|
rayTest(startPos, endPos, rayResult);
|
||||||
|
// we're hovering if the ray didn't hit anything or hit unstandable slope
|
||||||
|
_hovering = !rayResult.hasHit() || rayResult.m_hitNormalWorld.dot(_upDirection) < _maxWallNormalUpComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterGhostObject::updateTraction(const btVector3& position) {
|
||||||
|
updateHoverState(position);
|
||||||
if (_hovering) {
|
if (_hovering) {
|
||||||
_linearVelocity = _motorVelocity;
|
_linearVelocity = _motorVelocity;
|
||||||
} else if (_onFloor) {
|
} else if (_onFloor) {
|
||||||
btVector3 pathDirection = _floorNormal.cross(_motorVelocity).cross(_floorNormal);
|
// compute a velocity that swings the capsule around the _floorContact
|
||||||
|
btVector3 leverArm = _floorContact - position;
|
||||||
|
btVector3 pathDirection = leverArm.cross(_motorVelocity.cross(leverArm));
|
||||||
btScalar pathLength = pathDirection.length();
|
btScalar pathLength = pathDirection.length();
|
||||||
if (pathLength > FLT_EPSILON) {
|
if (pathLength > FLT_EPSILON) {
|
||||||
_linearVelocity = (_motorSpeed / pathLength) * pathDirection;
|
_linearVelocity = (_motorSpeed / pathLength) * pathDirection;
|
||||||
|
@ -360,13 +391,3 @@ btScalar CharacterGhostObject::measureAvailableStepHeight() const {
|
||||||
return result.m_closestHitFraction * _maxStepHeight;
|
return result.m_closestHitFraction * _maxStepHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterGhostObject::updateHoverState(const btTransform& transform) {
|
|
||||||
// cast a ray down looking for floor support
|
|
||||||
CharacterRayResult rayResult(this);
|
|
||||||
btVector3 startPos = transform.getOrigin() - ((_distanceToFeet - 0.1f) * _upDirection); // 0.1 HACK to make ray hit
|
|
||||||
btVector3 endPos = startPos - (2.0f * _distanceToFeet) * _upDirection;
|
|
||||||
rayTest(startPos, endPos, rayResult);
|
|
||||||
// we're hovering if the ray didn't hit an object we can stand on
|
|
||||||
_hovering = !(rayResult.hasHit() && rayResult.m_hitNormalWorld.dot(_upDirection) > _maxWallNormalUpComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ public:
|
||||||
void setCollisionGroupAndMask(int16_t group, int16_t mask);
|
void setCollisionGroupAndMask(int16_t group, int16_t mask);
|
||||||
void getCollisionGroupAndMask(int16_t& group, int16_t& mask) const;
|
void getCollisionGroupAndMask(int16_t& group, int16_t& mask) const;
|
||||||
|
|
||||||
void setDistanceToFeet(btScalar distance) { _distanceToFeet = distance; }
|
void setRadiusAndHalfHeight(btScalar radius, btScalar halfHeight);
|
||||||
void setUpDirection(const btVector3& up);
|
void setUpDirection(const btVector3& up);
|
||||||
void setMotorVelocity(const btVector3& velocity);
|
void setMotorVelocity(const btVector3& velocity);
|
||||||
void setGravity(btScalar gravity) { _gravity = gravity; } // NOTE: we expect _gravity to be negative (in _upDirection)
|
void setGravity(btScalar gravity) { _gravity = gravity; } // NOTE: we expect _gravity to be negative (in _upDirection)
|
||||||
|
@ -50,6 +50,9 @@ public:
|
||||||
const btTransform& start,
|
const btTransform& start,
|
||||||
const btTransform& end,
|
const btTransform& end,
|
||||||
CharacterSweepResult& result) const;
|
CharacterSweepResult& result) const;
|
||||||
|
|
||||||
|
bool isHovering() const { return _hovering; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void removeFromWorld();
|
void removeFromWorld();
|
||||||
void addToWorld();
|
void addToWorld();
|
||||||
|
@ -61,17 +64,20 @@ protected:
|
||||||
bool resolvePenetration(int numTries);
|
bool resolvePenetration(int numTries);
|
||||||
void refreshOverlappingPairCache();
|
void refreshOverlappingPairCache();
|
||||||
void updateVelocity(btScalar dt);
|
void updateVelocity(btScalar dt);
|
||||||
void updateTraction();
|
void updateTraction(const btVector3& position);
|
||||||
btScalar measureAvailableStepHeight() const;
|
btScalar measureAvailableStepHeight() const;
|
||||||
void updateHoverState(const btTransform& transform);
|
void updateHoverState(const btVector3& position);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
btVector3 _upDirection { 0.0f, 1.0f, 0.0f }; // input, up in world-frame
|
btVector3 _upDirection { 0.0f, 1.0f, 0.0f }; // input, up in world-frame
|
||||||
btVector3 _motorVelocity { 0.0f, 0.0f, 0.0f }; // input, velocity character is trying to achieve
|
btVector3 _motorVelocity { 0.0f, 0.0f, 0.0f }; // input, velocity character is trying to achieve
|
||||||
btVector3 _linearVelocity { 0.0f, 0.0f, 0.0f }; // internal, actual character velocity
|
btVector3 _linearVelocity { 0.0f, 0.0f, 0.0f }; // internal, actual character velocity
|
||||||
btVector3 _floorNormal { 0.0f, 0.0f, 0.0f }; // internal, probable floor normal
|
btVector3 _floorNormal { 0.0f, 0.0f, 0.0f }; // internal, probable floor normal
|
||||||
|
btVector3 _floorContact { 0.0f, 0.0f, 0.0f }; // internal, last floor contact point
|
||||||
btCollisionWorld* _world { nullptr }; // input, pointer to world
|
btCollisionWorld* _world { nullptr }; // input, pointer to world
|
||||||
btScalar _distanceToFeet { 0.0f }; // input, distance from object center to lowest point on shape
|
//btScalar _distanceToFeet { 0.0f }; // input, distance from object center to lowest point on shape
|
||||||
|
btScalar _halfHeight { 0.0f };
|
||||||
|
btScalar _radius { 0.0f };
|
||||||
btScalar _motorSpeed { 0.0f }; // internal, cached for speed
|
btScalar _motorSpeed { 0.0f }; // internal, cached for speed
|
||||||
btScalar _gravity { 0.0f }; // input, amplitude of gravity along _upDirection (should be negative)
|
btScalar _gravity { 0.0f }; // input, amplitude of gravity along _upDirection (should be negative)
|
||||||
btScalar _maxWallNormalUpComponent { 0.0f }; // input: max vertical component of wall normal
|
btScalar _maxWallNormalUpComponent { 0.0f }; // input: max vertical component of wall normal
|
||||||
|
|
Loading…
Reference in a new issue