PhysicsEngine doesn't need AvatarData

MyAvatar now owns its CharacterController
fix for bug of phantom collision obj when avatar's physics are disabled
This commit is contained in:
Andrew Meadows 2015-03-24 17:38:35 -07:00
parent 49f5de2b94
commit 44eca08fa4
8 changed files with 197 additions and 140 deletions

View file

@ -1903,8 +1903,6 @@ void Application::init() {
_physicsEngine.init(&_entityEditSender);
_physicsEngine.setAvatarData(_myAvatar);
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity,
@ -2191,6 +2189,7 @@ void Application::update(float deltaTime) {
{
PerformanceTimer perfTimer("physics");
_myAvatar->preSimulation();
_physicsEngine.stepSimulation();
}
@ -4207,7 +4206,7 @@ void Application::checkSkeleton() {
_myAvatar->setSkeletonModelURL(DEFAULT_BODY_MODEL_URL);
_myAvatar->sendIdentityPacket();
} else {
_myAvatar->updateLocalAABox();
_physicsEngine.setAvatarData(_myAvatar);
_myAvatar->updateCharacterController();
_physicsEngine.setCharacterController(_myAvatar->getCharacterController());
}
}

View file

@ -82,6 +82,8 @@ MyAvatar::MyAvatar() :
_scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE),
_scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME),
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
_characterController(this),
_enablePhysics(false),
_lookAtTargetAvatar(),
_shouldRender(true),
_billboardValid(false),
@ -954,15 +956,15 @@ glm::vec3 MyAvatar::getSkeletonPosition() const {
return Avatar::getPosition();
}
void MyAvatar::updateLocalAABox() {
void MyAvatar::updateCharacterController() {
// compute localAABox
const CapsuleShape& capsule = _skeletonModel.getBoundingShape();
float radius = capsule.getRadius();
float height = 2.0f * (capsule.getHalfHeight() + radius);
glm::vec3 offset = _skeletonModel.getBoundingShapeOffset();
glm::vec3 corner(-radius, -0.5f * height, -radius);
corner += offset;
corner += _skeletonModel.getBoundingShapeOffset();
glm::vec3 scale(2.0f * radius, height, 2.0f * radius);
_localAABox.setBox(corner, scale);
_characterController.setLocalBoundingBox(corner, scale);
}
QString MyAvatar::getScriptedMotorFrame() const {
@ -1580,6 +1582,10 @@ glm::vec3 MyAvatar::getLaserPointerTipPosition(const PalmData* palm) {
return palm->getPosition();
}
void MyAvatar::preSimulation() {
_characterController.setEnabled(_enablePhysics);
}
void MyAvatar::clearDriveKeys() {
for (int i = 0; i < MAX_DRIVE_KEYS; ++i) {
_driveKeys[i] = 0.0f;

View file

@ -13,6 +13,7 @@
#define hifi_MyAvatar_h
#include <SettingHandle.h>
#include <CharacterController.h>
#include "Avatar.h"
@ -122,6 +123,8 @@ public:
virtual glm::vec3 getSkeletonPosition() const;
void updateLocalAABox();
CharacterController* getCharacterController() { return &_characterController; }
void updateCharacterController();
void clearJointAnimationPriorities();
@ -145,6 +148,11 @@ public:
const RecorderPointer getRecorder() const { return _recorder; }
const PlayerPointer getPlayer() const { return _player; }
void togglePhysicsEnabled() { _enablePhysics = !_enablePhysics; }
bool isPhysicsEnabled() { return _enablePhysics; }
void setPhysicsEnabled(bool enablePhysics) { _enablePhysics = enablePhysics; }
void preSimulation();
public slots:
void increaseSize();
@ -202,6 +210,9 @@ private:
int _scriptedMotorFrame;
quint32 _motionBehaviors;
bool _enablePhysics;
CharacterController _characterController;
QWeakPointer<AvatarData> _lookAtTargetAvatar;
glm::vec3 _targetAvatarPosition;
bool _shouldRender;

View file

@ -300,16 +300,6 @@ public:
const AABox& getLocalAABox() const { return _localAABox; }
const Referential* getReferential() const { return _referential; }
void togglePhysicsEnabled() { _enablePhysics = !_enablePhysics; }
bool isPhysicsEnabled() { return _enablePhysics; }
void setPhysicsEnabled(bool enablePhysics) { _enablePhysics = enablePhysics; }
void lockForRead() { _lock.lockForRead(); }
bool tryLockForRead() { return _lock.tryLockForRead(); }
void lockForWrite() { _lock.lockForWrite(); }
bool tryLockForWrite() { return _lock.tryLockForWrite(); }
void unlock() { _lock.unlock(); }
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; }
@ -409,9 +399,6 @@ private:
// privatize the copy constructor and assignment operator so they cannot be called
AvatarData(const AvatarData&);
AvatarData& operator= (const AvatarData&);
QReadWriteLock _lock;
bool _enablePhysics = false;
};
Q_DECLARE_METATYPE(AvatarData*)

View file

@ -21,6 +21,12 @@ subject to the following restrictions:
#include "BulletUtil.h"
#include "CharacterController.h"
const uint32_t PENDING_FLAG_ADD_TO_SIMULATION = 1U << 0;
const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1;
//const uint32_t PENDING_FLAG_ENABLE = 1U << 0;
//const uint32_t PENDING_FLAG_DISABLE = 1U << 1;
const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2;
const uint32_t PENDING_FLAG_JUMP = 1U << 4;
// static helper method
static btVector3 getNormalizedVector(const btVector3& v) {
@ -223,11 +229,10 @@ CharacterController::CharacterController(AvatarData* avatarData) {
_avatarData = avatarData;
// cache the "PhysicsEnabled" state of _avatarData
_avatarData->lockForRead();
_enabled = _avatarData->isPhysicsEnabled();
_avatarData->unlock();
_enabled = false;
createShapeAndGhost();
_ghostObject = NULL;
_convexShape = NULL;
_addedMargin = 0.02f;
_walkDirection.setValue(0.0f,0.0f,0.0f);
@ -242,6 +247,7 @@ CharacterController::CharacterController(AvatarData* avatarData) {
_wasJumping = false;
setMaxSlope(btRadians(45.0f));
_lastStepUp = 0.0f;
_pendingFlags = 0;
}
CharacterController::~CharacterController() {
@ -349,7 +355,7 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
return penetration;
}
void CharacterController::stepUp( btCollisionWorld* world) {
void CharacterController::stepUp(btCollisionWorld* world) {
// phase 1: up
// compute start and end
@ -416,7 +422,7 @@ void CharacterController::updateTargetPositionBasedOnCollision(const btVector3&
}
}
void CharacterController::stepForward( btCollisionWorld* collisionWorld, const btVector3& movement) {
void CharacterController::stepForward(btCollisionWorld* collisionWorld, const btVector3& movement) {
// phase 2: forward
_targetPosition = _currentPosition + movement;
@ -472,7 +478,7 @@ void CharacterController::stepForward( btCollisionWorld* collisionWorld, const b
_convexShape->setMargin(margin);
}
void CharacterController::stepDown( btCollisionWorld* collisionWorld, btScalar dt) {
void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt) {
// phase 3: down
//
// The "stepDown" phase first makes a normal sweep down that cancels the lift from the "stepUp" phase.
@ -558,7 +564,7 @@ void CharacterController::setVelocityForTimeInterval(const btVector3& velocity,
_velocityTimeInterval += timeInterval;
}
void CharacterController::reset( btCollisionWorld* collisionWorld ) {
void CharacterController::reset(btCollisionWorld* collisionWorld) {
_verticalVelocity = 0.0;
_verticalOffset = 0.0;
_wasOnGround = false;
@ -583,7 +589,7 @@ void CharacterController::warp(const btVector3& origin) {
}
void CharacterController::preStep( btCollisionWorld* collisionWorld) {
void CharacterController::preStep(btCollisionWorld* collisionWorld) {
if (!_enabled) {
return;
}
@ -603,7 +609,7 @@ void CharacterController::preStep( btCollisionWorld* collisionWorld) {
_targetPosition = _currentPosition;
}
void CharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt) {
void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) {
if (!_enabled || (!_useWalkDirection && _velocityTimeInterval <= 0.0)) {
return; // no motion
}
@ -710,106 +716,163 @@ void CharacterController::setUpInterpolate(bool value) {
// (interpolate = true, and now default behavior) or happily penetrate objects above the avatar.
}
/*
// protected
void CharacterController::createShapeAndGhost() {
// get new dimensions from avatar
_avatarData->lockForRead();
AABox box = _avatarData->getLocalAABox();
// create new ghost
_ghostObject = new btPairCachingGhostObject();
_ghostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()),
glmToBullet(_avatarData->getPosition())));
_avatarData->unlock();
const glm::vec3& diagonal = box.getScale();
_radius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
_halfHeight = 0.5f * diagonal.y - _radius;
float x = _boxScale.x;
float z = _boxScale.z;
_radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
_halfHeight = 0.5f * _boxScale.y - _radius;
float MIN_HALF_HEIGHT = 0.1f;
if (_halfHeight < MIN_HALF_HEIGHT) {
_halfHeight = MIN_HALF_HEIGHT;
}
glm::vec3 offset = box.getCorner() + 0.5f * diagonal;
_shapeLocalOffset = offset;
// NOTE: _shapeLocalOffset is already computed
// stepHeight affects the heights of ledges that the character can ascend
// however the actual ledge height is some function of _stepHeight
// due to character shape and this CharacterController algorithm
// (the function is approximately 2*_stepHeight)
_stepHeight = 0.1f * (_radius + _halfHeight) + 0.1f;
// create new shape
_convexShape = new btCapsuleShape(_radius, 2.0f * _halfHeight);
_ghostObject->setCollisionShape(_convexShape);
_ghostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
if (_radius > 0.0f) {
// create new ghost
_ghostObject = new btPairCachingGhostObject();
_ghostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()),
glmToBullet(_avatarData->getPosition())));
// stepHeight affects the heights of ledges that the character can ascend
// however the actual ledge height is some function of _stepHeight
// due to character shape and this CharacterController algorithm
// (the function is approximately 2*_stepHeight)
_stepHeight = 0.1f * (_radius + _halfHeight) + 0.1f;
// create new shape
_convexShape = new btCapsuleShape(_radius, 2.0f * _halfHeight);
_ghostObject->setCollisionShape(_convexShape);
_ghostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
} else {
// TODO: handle this failure case
}
_pendingFlags &= ~ PENDING_FLAG_UPDATE_SHAPE;
}
*/
bool CharacterController::needsShapeUpdate() {
// get new dimensions from avatar
_avatarData->lockForRead();
AABox box = _avatarData->getLocalAABox();
_avatarData->unlock();
void CharacterController::setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale) {
_boxScale = scale;
const glm::vec3& diagonal = box.getScale();
float radius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
float halfHeight = 0.5f * diagonal.y - radius;
float x = _boxScale.x;
float z = _boxScale.z;
float radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
float halfHeight = 0.5f * _boxScale.y - radius;
float MIN_HALF_HEIGHT = 0.1f;
if (halfHeight < MIN_HALF_HEIGHT) {
halfHeight = MIN_HALF_HEIGHT;
}
glm::vec3 offset = box.getCorner() + 0.5f * diagonal;
// compare dimensions (and offset)
// compare dimensions
float radiusDelta = glm::abs(radius - _radius);
float heightDelta = glm::abs(halfHeight - _halfHeight);
if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) {
// shape hasn't changed --> nothing to do
float offsetDelta = glm::distance(offset, _shapeLocalOffset);
if (offsetDelta > FLT_EPSILON) {
// if only the offset changes then we can update it --> no need to rebuild shape
_shapeLocalOffset = offset;
}
return false;
} else {
// we need to: remove, update, add
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION
| PENDING_FLAG_UPDATE_SHAPE
| PENDING_FLAG_ADD_TO_SIMULATION;
}
// it's ok to change offset immediately -- there are no thread safety issues here
_shapeLocalOffset = corner + 0.5f * _boxScale;
}
bool CharacterController::needsAddition() const {
return (bool)(_pendingFlags & PENDING_FLAG_ADD_TO_SIMULATION);
}
bool CharacterController::needsRemoval() const {
return (bool)(_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION);
}
void CharacterController::setEnabled(bool enabled) {
if (enabled != _enabled) {
if (enabled) {
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
} else {
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
_pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION;
}
_enabled = enabled;
}
}
void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
if (_dynamicsWorld != world) {
if (_dynamicsWorld) {
_dynamicsWorld->removeCollisionObject(getGhostObject());
_dynamicsWorld->removeAction(this);
}
_dynamicsWorld = world;
if (_dynamicsWorld) {
_pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION;
_dynamicsWorld->addCollisionObject(getGhostObject(),
btBroadphaseProxy::CharacterFilter,
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
_dynamicsWorld->addAction(this);
reset(_dynamicsWorld);
} else {
_pendingFlags &= ~ PENDING_FLAG_REMOVE_FROM_SIMULATION;
}
} else {
_pendingFlags &= ~ (PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_ADD_TO_SIMULATION);
}
return true;
}
void CharacterController::updateShape() {
// DANGER: make sure this CharacterController and its GhostShape have been removed from
// the PhysicsEngine before calling this.
// delete shape and GhostObject
delete _ghostObject;
_ghostObject = NULL;
delete _convexShape;
_convexShape = NULL;
createShapeAndGhost();
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
assert(!(_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION));
_pendingFlags &= ~ PENDING_FLAG_UPDATE_SHAPE;
// make sure there is NO pending removal from simulation at this point
// (don't want to delete _ghostObject out from under the simulation)
// delete shape and GhostObject
delete _ghostObject;
_ghostObject = NULL;
delete _convexShape;
_convexShape = NULL;
// compute new dimensions from avatar's bounding box
float x = _boxScale.x;
float z = _boxScale.z;
_radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
_halfHeight = 0.5f * _boxScale.y - _radius;
float MIN_HALF_HEIGHT = 0.1f;
if (_halfHeight < MIN_HALF_HEIGHT) {
_halfHeight = MIN_HALF_HEIGHT;
}
// NOTE: _shapeLocalOffset is already computed
if (_radius > 0.0f) {
// create new ghost
_ghostObject = new btPairCachingGhostObject();
_ghostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()),
glmToBullet(_avatarData->getPosition())));
// stepHeight affects the heights of ledges that the character can ascend
// however the actual ledge height is some function of _stepHeight
// due to character shape and this CharacterController algorithm
// (the function is approximately 2*_stepHeight)
_stepHeight = 0.1f * (_radius + _halfHeight) + 0.1f;
// create new shape
_convexShape = new btCapsuleShape(_radius, 2.0f * _halfHeight);
_ghostObject->setCollisionShape(_convexShape);
_ghostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
} else {
// TODO: handle this failure case
}
}
}
void CharacterController::preSimulation(btScalar timeStep) {
bool wasEnabled = _enabled;
if (_enabled && _dynamicsWorld) {
glm::quat rotation = _avatarData->getOrientation();
glm::vec3 position = _avatarData->getPosition() + rotation * _shapeLocalOffset;
// TODO: harvest jump event here
btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity());
// lock avatarData, get everything we need from it ASAP, then unlock
_avatarData->lockForRead();
_enabled = _avatarData->isPhysicsEnabled();
glm::quat rotation = _avatarData->getOrientation();
glm::vec3 position = _avatarData->getPosition() + rotation * _shapeLocalOffset;
// TODO: Andrew to implement: harvest jump event here
btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity());
_avatarData->unlock();
if (wasEnabled != _enabled) {
if (_enabled) {
// TODO: Andrew to implement: add collision shape back into world
} else {
// TODO: Andrew to implement: remove collision shape from world,
// otherwise things will continue to collide with it
}
}
if (_enabled) {
_ghostObject->setWorldTransform(btTransform(glmToBullet(rotation), glmToBullet(position)));
setVelocityForTimeInterval(walkVelocity, timeStep);
}
@ -817,13 +880,11 @@ void CharacterController::preSimulation(btScalar timeStep) {
void CharacterController::postSimulation() {
if (_enabled) {
_avatarData->lockForWrite();
const btTransform& avatarTransform = _ghostObject->getWorldTransform();
glm::quat rotation = bulletToGLM(avatarTransform.getRotation());
glm::vec3 offset = rotation * _shapeLocalOffset;
_avatarData->setOrientation(rotation);
_avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset);
_avatarData->unlock();
}
}

View file

@ -37,13 +37,13 @@ class btPairCachingGhostObject;
///It uses a ghost object and convex sweep test to test for upcoming collisions. This is combined with discrete collision detection to recover from penetrations.
///Interaction between btKinematicCharacterController and dynamic rigid bodies needs to be explicity implemented by the user.
ATTRIBUTE_ALIGNED16(class) CharacterController : public btCharacterControllerInterface
{
protected:
AvatarData* _avatarData = NULL;
btPairCachingGhostObject* _ghostObject;
glm::vec3 _shapeLocalOffset;
btConvexShape* _convexShape;//is also in _ghostObject, but it needs to be convex, so we store it here to avoid upcast
btScalar _radius;
@ -83,6 +83,12 @@ protected:
bool _wasJumping;
bool _useWalkDirection;
btScalar _velocityTimeInterval;
uint32_t _pendingFlags;
glm::vec3 _shapeLocalOffset;
glm::vec3 _boxScale; // used to compute capsule shape
btDynamicsWorld* _dynamicsWorld = NULL;
btVector3 computeReflectionDirection(const btVector3& direction, const btVector3& normal);
btVector3 parallelComponent(const btVector3& direction, const btVector3& normal);
@ -152,7 +158,13 @@ public:
bool onGround() const;
void setUpInterpolate(bool value);
bool needsShapeUpdate();
bool needsRemoval() const;
bool needsAddition() const;
void setEnabled(bool enabled);
void setDynamicsWorld(btDynamicsWorld* world);
void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale);
bool needsShapeUpdate() const;
void updateShape();
void preSimulation(btScalar timeStep);

View file

@ -280,12 +280,12 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
void PhysicsEngine::stepSimulation() {
lock();
// NOTE: the grand order of operations is:
// (1) relay incoming changes
// (1) pull incoming changes
// (2) step simulation
// (3) synchronize outgoing motion states
// (4) send outgoing packets
// This is step (1).
// This is step (1) pull incoming changes
relayIncomingChangesToSimulation();
const int MAX_NUM_SUBSTEPS = 4;
@ -296,10 +296,17 @@ void PhysicsEngine::stepSimulation() {
// TODO: move character->preSimulation() into relayIncomingChanges
if (_characterController) {
if (_characterController->needsRemoval()) {
_characterController->setDynamicsWorld(NULL);
}
_characterController->updateShape();
if (_characterController->needsAddition()) {
_characterController->setDynamicsWorld(_dynamicsWorld);
}
_characterController->preSimulation(timeStep);
}
// This is step (2).
// This is step (2) step simulation
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
_numSubsteps += (uint32_t)numSubsteps;
stepNonPhysicalKinematics(usecTimestampNow());
@ -600,34 +607,10 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
return true;
}
void PhysicsEngine::setAvatarData(AvatarData *avatarData) {
if (_characterController) {
bool needsShapeUpdate = _characterController->needsShapeUpdate();
if (needsShapeUpdate) {
lock();
// remove old info
_dynamicsWorld->removeCollisionObject(_characterController->getGhostObject());
_dynamicsWorld->removeAction(_characterController);
// update shape
_characterController->updateShape();
// insert new info
_dynamicsWorld->addCollisionObject(_characterController->getGhostObject(),
btBroadphaseProxy::CharacterFilter,
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
_dynamicsWorld->addAction(_characterController);
_characterController->reset(_dynamicsWorld);
unlock();
}
} else {
// initialize _characterController
assert(avatarData); // don't pass NULL argument
void PhysicsEngine::setCharacterController(CharacterController* character) {
if (!_characterController) {
lock();
_characterController = new CharacterController(avatarData);
_dynamicsWorld->addCollisionObject(_characterController->getGhostObject(),
btBroadphaseProxy::CharacterFilter,
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
_dynamicsWorld->addAction(_characterController);
_characterController->reset(_dynamicsWorld);
_characterController = character;
unlock();
}
}

View file

@ -17,9 +17,7 @@
#include <QSet>
#include <btBulletDynamicsCommon.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
//#include <BulletCollision/CollisionShapes/btCapsuleShape.h>
#include <AvatarData.h>
#include <EntityItem.h>
#include <EntitySimulation.h>
@ -86,7 +84,7 @@ public:
/// process queue of changed from external sources
void relayIncomingChangesToSimulation();
void setAvatarData(AvatarData *avatarData);
void setCharacterController(CharacterController* character);
private:
/// \param motionState pointer to Object's MotionState