Merge pull request #2003 from AndrewMeadows/avatar-interaction

Particle vs Avatar collisions work again
This commit is contained in:
ZappoMan 2014-02-17 12:15:53 -08:00
commit 26d076f166
16 changed files with 324 additions and 196 deletions

View file

@ -169,14 +169,7 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly); addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly);
QMenu* collisionsOptionsMenu = editMenu->addMenu("Collision Options"); addAvatarCollisionSubMenu(editMenu);
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, 0, false, avatar, SLOT(updateCollisionFlags()));
addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, 0, false, avatar, SLOT(updateCollisionFlags()));
addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, 0, false, avatar, SLOT(updateCollisionFlags()));
// TODO: make this option work
//addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, 0, false, avatar, SLOT(updateCollisionFlags()));
QMenu* toolsMenu = addMenu("Tools"); QMenu* toolsMenu = addMenu("Tools");
@ -345,6 +338,8 @@ Menu::Menu() :
SLOT(setTCPEnabled(bool))); SLOT(setTCPEnabled(bool)));
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
addAvatarCollisionSubMenu(avatarOptionsMenu);
QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options"); QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
addCheckableActionToQMenuAndActionHash(handOptionsMenu, addCheckableActionToQMenuAndActionHash(handOptionsMenu,
@ -519,6 +514,11 @@ void Menu::loadSettings(QSettings* settings) {
Application::getInstance()->getProfile()->loadData(settings); Application::getInstance()->getProfile()->loadData(settings);
Application::getInstance()->updateWindowTitle(); Application::getInstance()->updateWindowTitle();
NodeList::getInstance()->loadData(settings); NodeList::getInstance()->loadData(settings);
// MyAvatar caches some menu options, so we have to update them whenever we load settings.
// TODO: cache more settings in MyAvatar that are checked with very high frequency.
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
myAvatar->updateCollisionFlags();
} }
void Menu::saveSettings(QSettings* settings) { void Menu::saveSettings(QSettings* settings) {
@ -1232,6 +1232,22 @@ void Menu::updateFrustumRenderModeAction() {
} }
} }
void Menu::addAvatarCollisionSubMenu(QMenu* overMenu) {
// add avatar collisions subMenu to overMenu
QMenu* subMenu = overMenu->addMenu("Collision Options");
Application* appInstance = Application::getInstance();
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment,
0, false, avatar, SLOT(updateCollisionFlags()));
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars,
0, true, avatar, SLOT(updateCollisionFlags()));
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels,
0, false, avatar, SLOT(updateCollisionFlags()));
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles,
0, true, avatar, SLOT(updateCollisionFlags()));
}
QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string) { QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string) {
int lastIndex; int lastIndex;
lastIndex = string.lastIndexOf(search); lastIndex = string.lastIndexOf(search);
@ -1242,4 +1258,3 @@ QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string)
return string; return string;
} }

View file

@ -152,6 +152,8 @@ private:
void updateFrustumRenderModeAction(); void updateFrustumRenderModeAction();
void addAvatarCollisionSubMenu(QMenu* overMenu);
QHash<QString, QAction*> _actionHash; QHash<QString, QAction*> _actionHash;
int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback
BandwidthDialog* _bandwidthDialog; BandwidthDialog* _bandwidthDialog;

View file

@ -273,27 +273,19 @@ bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
} }
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
ModelCollisionList& collisions, int skeletonSkipIndex) { CollisionList& collisions, int skeletonSkipIndex) {
bool didPenetrate = false; // Temporarily disabling collisions against the skeleton because the collision proxies up
glm::vec3 skeletonPenetration; // near the neck are bad and prevent the hand from hitting the face.
ModelCollisionInfo collisionInfo; //return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions, 1.0f, skeletonSkipIndex);
/* Temporarily disabling collisions against the skeleton because the collision proxies up return _head.getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
* near the neck are bad and prevent the hand from hitting the face.
if (_skeletonModel.findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo, 1.0f, skeletonSkipIndex)) {
collisionInfo._model = &_skeletonModel;
collisions.push_back(collisionInfo);
didPenetrate = true;
}
*/
if (_head.getFaceModel().findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo)) {
collisionInfo._model = &(_head.getFaceModel());
collisions.push_back(collisionInfo);
didPenetrate = true;
}
return didPenetrate;
} }
bool Avatar::findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) { bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
if (_collisionFlags & COLLISION_GROUP_PARTICLES) {
return false;
}
bool collided = false;
// first do the hand collisions
const HandData* handData = getHandData(); const HandData* handData = getHandData();
if (handData) { if (handData) {
for (int i = 0; i < NUM_HANDS; i++) { for (int i = 0; i < NUM_HANDS; i++) {
@ -311,41 +303,55 @@ bool Avatar::findSphereCollisionWithHands(const glm::vec3& sphereCenter, float s
break; break;
} }
} }
int jointIndex = -1;
glm::vec3 handPosition; glm::vec3 handPosition;
if (i == 0) { if (i == 0) {
_skeletonModel.getLeftHandPosition(handPosition); _skeletonModel.getLeftHandPosition(handPosition);
jointIndex = _skeletonModel.getLeftHandJointIndex();
} }
else { else {
_skeletonModel.getRightHandPosition(handPosition); _skeletonModel.getRightHandPosition(handPosition);
jointIndex = _skeletonModel.getRightHandJointIndex();
} }
glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis; glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis;
glm::vec3 diskNormal = palm->getNormal(); glm::vec3 diskNormal = palm->getNormal();
float diskThickness = 0.08f; const float DISK_THICKNESS = 0.08f;
// collide against the disk // collide against the disk
if (findSphereDiskPenetration(sphereCenter, sphereRadius, glm::vec3 penetration;
diskCenter, HAND_PADDLE_RADIUS, diskThickness, diskNormal, if (findSphereDiskPenetration(particleCenter, particleRadius,
collision._penetration)) { diskCenter, HAND_PADDLE_RADIUS, DISK_THICKNESS, diskNormal,
collision._addedVelocity = palm->getVelocity(); penetration)) {
return true; CollisionInfo* collision = collisions.getNewCollision();
if (collision) {
collision->_type = PADDLE_HAND_COLLISION;
collision->_flags = jointIndex;
collision->_penetration = penetration;
collision->_addedVelocity = palm->getVelocity();
collided = true;
} else {
// collisions are full, so we might as well bail now
return collided;
}
} }
} }
} }
} }
return false; // then collide against the models
} int preNumCollisions = collisions.size();
if (_skeletonModel.findSphereCollisions(particleCenter, particleRadius, collisions)) {
/* adebug TODO: make this work again // the Model doesn't have velocity info, so we have to set it for each new collision
bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) { int postNumCollisions = collisions.size();
int jointIndex = _skeletonModel.findSphereCollision(sphereCenter, sphereRadius, collision._penetration); for (int i = preNumCollisions; i < postNumCollisions; ++i) {
if (jointIndex != -1) { CollisionInfo* collision = collisions.getCollision(i);
collision._penetration /= (float)(TREE_SCALE); collision->_penetration /= (float)(TREE_SCALE);
collision._addedVelocity = getVelocity(); collision->_addedVelocity = getVelocity();
return true; }
collided = true;
} }
return false; return collided;
} }
*/
void Avatar::setFaceModelURL(const QUrl &faceModelURL) { void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL); AvatarData::setFaceModelURL(faceModelURL);
@ -430,9 +436,9 @@ void Avatar::updateCollisionFlags() {
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithVoxels)) { if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithVoxels)) {
_collisionFlags |= COLLISION_GROUP_VOXELS; _collisionFlags |= COLLISION_GROUP_VOXELS;
} }
//if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) { if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) {
// _collisionFlags |= COLLISION_GROUP_PARTICLES; _collisionFlags |= COLLISION_GROUP_PARTICLES;
//} }
} }
void Avatar::setScale(float scale) { void Avatar::setScale(float scale) {
@ -449,34 +455,34 @@ float Avatar::getHeight() const {
return extents.maximum.y - extents.minimum.y; return extents.maximum.y - extents.minimum.y;
} }
bool Avatar::collisionWouldMoveAvatar(ModelCollisionInfo& collision) const { bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
// ATM only the Skeleton is pokeable if (!collision._data || collision._type != MODEL_COLLISION) {
// TODO: make poke affect head
if (!collision._model) {
return false; return false;
} }
if (collision._model == &_skeletonModel && collision._jointIndex != -1) { Model* model = static_cast<Model*>(collision._data);
int jointIndex = collision._flags;
if (model == &(_skeletonModel) && jointIndex != -1) {
// collision response of skeleton is temporarily disabled // collision response of skeleton is temporarily disabled
return false; return false;
//return _skeletonModel.collisionHitsMoveableJoint(collision); //return _skeletonModel.collisionHitsMoveableJoint(collision);
} }
if (collision._model == &(_head.getFaceModel())) { if (model == &(_head.getFaceModel())) {
// ATM we always handle MODEL_COLLISIONS against the face.
return true; return true;
} }
return false; return false;
} }
void Avatar::applyCollision(ModelCollisionInfo& collision) { void Avatar::applyCollision(CollisionInfo& collision) {
if (!collision._model) { if (!collision._data || collision._type != MODEL_COLLISION) {
return; return;
} }
if (collision._model == &(_head.getFaceModel())) { // TODO: make skeleton also respond to collisions
Model* model = static_cast<Model*>(collision._data);
if (model == &(_head.getFaceModel())) {
_head.applyCollision(collision); _head.applyCollision(collision);
} }
// TODO: make skeleton respond to collisions
//if (collision._model == &_skeletonModel && collision._jointIndex != -1) {
// _skeletonModel.applyCollision(collision);
//}
} }
float Avatar::getPelvisFloatingHeight() const { float Avatar::getPelvisFloatingHeight() const {

View file

@ -57,8 +57,6 @@ enum ScreenTintLayer {
NUM_SCREEN_TINT_LAYERS NUM_SCREEN_TINT_LAYERS
}; };
typedef QVector<ModelCollisionInfo> ModelCollisionList;
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found) // Where one's own Avatar begins in the world (will be overwritten if avatar data file is found)
// this is basically in the center of the ground plane. Slightly adjusted. This was asked for by // this is basically in the center of the ground plane. Slightly adjusted. This was asked for by
// Grayson as he's building a street around here for demo dinner 2 // Grayson as he's building a street around here for demo dinner 2
@ -97,25 +95,18 @@ public:
/// Checks for penetration between the described sphere and the avatar. /// Checks for penetration between the described sphere and the avatar.
/// \param penetratorCenter the center of the penetration test sphere /// \param penetratorCenter the center of the penetration test sphere
/// \param penetratorRadius the radius of the penetration test sphere /// \param penetratorRadius the radius of the penetration test sphere
/// \param collisions[out] a list of collisions /// \param collisions[out] a list to which collisions get appended
/// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model /// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
/// \return whether or not the sphere penetrated /// \return whether or not the sphere penetrated
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
ModelCollisionList& collisions, int skeletonSkipIndex = -1); CollisionList& collisions, int skeletonSkipIndex = -1);
/// Checks for collision between the a sphere and the avatar's (paddle) hands. /// Checks for collision between the a spherical particle and the avatar (including paddle hands)
/// \param collisionCenter the center of the penetration test sphere /// \param collisionCenter the center of particle's bounding sphere
/// \param collisionRadius the radius of the penetration test sphere /// \param collisionRadius the radius of particle's bounding sphere
/// \param collision[out] the details of the collision point /// \param collisions[out] a list to which collisions get appended
/// \return whether or not the sphere collided /// \return whether or not the particle collided
bool findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision); bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions);
/// Checks for collision between the a sphere and the avatar's skeleton (including hand capsules).
/// \param collisionCenter the center of the penetration test sphere
/// \param collisionRadius the radius of the penetration test sphere
/// \param collision[out] the details of the collision point
/// \return whether or not the sphere collided
//bool findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision);
virtual bool isMyAvatar() { return false; } virtual bool isMyAvatar() { return false; }
@ -126,13 +117,13 @@ public:
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2); static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
float getHeight() const;
/// \return true if we expect the avatar would move as a result of the collision /// \return true if we expect the avatar would move as a result of the collision
bool collisionWouldMoveAvatar(ModelCollisionInfo& collision) const; bool collisionWouldMoveAvatar(CollisionInfo& collision) const;
/// \param collision a data structure for storing info about collisions against Models /// \param collision a data structure for storing info about collisions against Models
void applyCollision(ModelCollisionInfo& collision); void applyCollision(CollisionInfo& collision);
float getBoundingRadius() const { return 0.5f * getHeight(); }
public slots: public slots:
void updateCollisionFlags(); void updateCollisionFlags();
@ -164,6 +155,7 @@ protected:
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const; glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
void setScale(float scale); void setScale(float scale);
float getHeight() const;
float getPelvisFloatingHeight() const; float getPelvisFloatingHeight() const;
float getPelvisToHeadLength() const; float getPelvisToHeadLength() const;

View file

@ -125,6 +125,10 @@ void Hand::simulate(float deltaTime, bool isMine) {
} }
} }
// We create a static CollisionList that is recycled for each collision test.
const float MAX_COLLISIONS_PER_AVATAR = 32;
static CollisionList handCollisions(MAX_COLLISIONS_PER_AVATAR);
void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) { void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
if (!avatar || avatar == _owningAvatar) { if (!avatar || avatar == _owningAvatar) {
// don't collide with our own hands (that is done elsewhere) // don't collide with our own hands (that is done elsewhere)
@ -137,7 +141,6 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
continue; continue;
} }
glm::vec3 totalPenetration; glm::vec3 totalPenetration;
ModelCollisionList collisions;
if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) { if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
// Check for palm collisions // Check for palm collisions
glm::vec3 myPalmPosition = palm.getPosition(); glm::vec3 myPalmPosition = palm.getPosition();
@ -171,20 +174,22 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
} }
} }
} }
if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions)) { handCollisions.clear();
for (int j = 0; j < collisions.size(); ++j) { if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions)) {
for (int j = 0; j < handCollisions.size(); ++j) {
CollisionInfo* collision = handCollisions.getCollision(j);
if (isMyHand) { if (isMyHand) {
if (!avatar->collisionWouldMoveAvatar(collisions[j])) { if (!avatar->collisionWouldMoveAvatar(*collision)) {
// we resolve the hand from collision when it belongs to MyAvatar AND the other Avatar is // we resolve the hand from collision when it belongs to MyAvatar AND the other Avatar is
// not expected to respond to the collision (hand hit unmovable part of their Avatar) // not expected to respond to the collision (hand hit unmovable part of their Avatar)
totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration); totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
} }
} else { } else {
// when !isMyHand then avatar is MyAvatar and we apply the collision // when !isMyHand then avatar is MyAvatar and we apply the collision
// which might not do anything (hand hit unmovable part of MyAvatar) however // which might not do anything (hand hit unmovable part of MyAvatar) however
// we don't resolve the hand's penetration because we expect the remote // we don't resolve the hand's penetration because we expect the remote
// simulation to do the right thing. // simulation to do the right thing.
avatar->applyCollision(collisions[j]); avatar->applyCollision(*collision);
} }
} }
} }
@ -200,7 +205,6 @@ void Hand::collideAgainstOurself() {
return; return;
} }
ModelCollisionList collisions;
int leftPalmIndex, rightPalmIndex; int leftPalmIndex, rightPalmIndex;
getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex); getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale(); float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
@ -210,16 +214,18 @@ void Hand::collideAgainstOurself() {
if (!palm.isActive()) { if (!palm.isActive()) {
continue; continue;
} }
glm::vec3 totalPenetration;
// and the current avatar (ignoring everything below the parent of the parent of the last free joint)
collisions.clear();
const Model& skeletonModel = _owningAvatar->getSkeletonModel(); const Model& skeletonModel = _owningAvatar->getSkeletonModel();
// ignoring everything below the parent of the parent of the last free joint
int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex( int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex(
skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() : skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() :
(i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1))); (i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1)));
if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions, skipIndex)) {
for (int j = 0; j < collisions.size(); ++j) { handCollisions.clear();
totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration); glm::vec3 totalPenetration;
if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions, skipIndex)) {
for (int j = 0; j < handCollisions.size(); ++j) {
CollisionInfo* collision = handCollisions.getCollision(j);
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
} }
} }
// resolve penetration // resolve penetration

View file

@ -219,7 +219,7 @@ float Head::getTweakedRoll() const {
return glm::clamp(_roll + _tweakedRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); return glm::clamp(_roll + _tweakedRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL);
} }
void Head::applyCollision(ModelCollisionInfo& collisionInfo) { void Head::applyCollision(CollisionInfo& collision) {
// HACK: the collision proxies for the FaceModel are bad. As a temporary workaround // HACK: the collision proxies for the FaceModel are bad. As a temporary workaround
// we collide against a hard coded collision proxy. // we collide against a hard coded collision proxy.
// TODO: get a better collision proxy here. // TODO: get a better collision proxy here.
@ -229,7 +229,7 @@ void Head::applyCollision(ModelCollisionInfo& collisionInfo) {
// collide the contactPoint against the collision proxy to obtain a new penetration // collide the contactPoint against the collision proxy to obtain a new penetration
// NOTE: that penetration is in opposite direction (points the way out for the point, not the sphere) // NOTE: that penetration is in opposite direction (points the way out for the point, not the sphere)
glm::vec3 penetration; glm::vec3 penetration;
if (findPointSpherePenetration(collisionInfo._contactPoint, HEAD_CENTER, HEAD_RADIUS, penetration)) { if (findPointSpherePenetration(collision._contactPoint, HEAD_CENTER, HEAD_RADIUS, penetration)) {
// compute lean angles // compute lean angles
Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar); Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar);
glm::quat bodyRotation = owningAvatar->getOrientation(); glm::quat bodyRotation = owningAvatar->getOrientation();
@ -239,8 +239,8 @@ void Head::applyCollision(ModelCollisionInfo& collisionInfo) {
glm::vec3 zAxis = bodyRotation * glm::vec3(0.f, 0.f, 1.f); glm::vec3 zAxis = bodyRotation * glm::vec3(0.f, 0.f, 1.f);
float neckLength = glm::length(_position - neckPosition); float neckLength = glm::length(_position - neckPosition);
if (neckLength > 0.f) { if (neckLength > 0.f) {
float forward = glm::dot(collisionInfo._penetration, zAxis) / neckLength; float forward = glm::dot(collision._penetration, zAxis) / neckLength;
float sideways = - glm::dot(collisionInfo._penetration, xAxis) / neckLength; float sideways = - glm::dot(collision._penetration, xAxis) / neckLength;
addLean(sideways, forward); addLean(sideways, forward);
} }
} }

View file

@ -80,7 +80,7 @@ public:
float getTweakedYaw() const; float getTweakedYaw() const;
float getTweakedRoll() const; float getTweakedRoll() const;
void applyCollision(ModelCollisionInfo& collisionInfo); void applyCollision(CollisionInfo& collisionInfo);
private: private:
// disallow copies of the Head, copy of owning Avatar is disallowed too // disallow copies of the Head, copy of owning Avatar is disallowed too

View file

@ -955,7 +955,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
// no need to compute a bunch of stuff if we have one or fewer avatars // no need to compute a bunch of stuff if we have one or fewer avatars
return; return;
} }
float myBoundingRadius = 0.5f * getHeight(); float myBoundingRadius = getBoundingRadius();
// HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis // HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis
// TODO: make the collision work without assuming avatar orientation // TODO: make the collision work without assuming avatar orientation
@ -975,7 +975,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
if (_distanceToNearestAvatar > distance) { if (_distanceToNearestAvatar > distance) {
_distanceToNearestAvatar = distance; _distanceToNearestAvatar = distance;
} }
float theirBoundingRadius = 0.5f * avatar->getHeight(); float theirBoundingRadius = avatar->getBoundingRadius();
if (distance < myBoundingRadius + theirBoundingRadius) { if (distance < myBoundingRadius + theirBoundingRadius) {
Extents theirStaticExtents = _skeletonModel.getStaticExtents(); Extents theirStaticExtents = _skeletonModel.getStaticExtents();
glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum; glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum;

View file

@ -457,9 +457,9 @@ bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct
return false; return false;
} }
bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius, bool Model::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
ModelCollisionInfo& collisionInfo, float boneScale, int skipIndex) const { CollisionList& collisions, float boneScale, int skipIndex) const {
int jointIndex = -1; bool collided = false;
const glm::vec3 relativeCenter = penetratorCenter - _translation; const glm::vec3 relativeCenter = penetratorCenter - _translation;
const FBXGeometry& geometry = _geometry->getFBXGeometry(); const FBXGeometry& geometry = _geometry->getFBXGeometry();
glm::vec3 totalPenetration; glm::vec3 totalPenetration;
@ -488,22 +488,22 @@ bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetra
if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end, if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end,
startRadius, endRadius, bonePenetration)) { startRadius, endRadius, bonePenetration)) {
totalPenetration = addPenetrations(totalPenetration, bonePenetration); totalPenetration = addPenetrations(totalPenetration, bonePenetration);
// BUG: we currently overwrite the jointIndex with the last one found CollisionInfo* collision = collisions.getNewCollision();
// which can cause incorrect collisions when colliding against more than if (collision) {
// one joint. collision->_type = MODEL_COLLISION;
// TODO: fix this. collision->_data = (void*)(this);
jointIndex = i; collision->_flags = i;
collision->_contactPoint = penetratorCenter + penetratorRadius * glm::normalize(totalPenetration);
collision->_penetration = totalPenetration;
collided = true;
} else {
// collisions are full, so we might as well break
break;
}
} }
outerContinue: ; outerContinue: ;
} }
if (jointIndex != -1) { return collided;
// don't store collisionInfo._model at this stage, let the outer context do that
collisionInfo._penetration = totalPenetration;
collisionInfo._jointIndex = jointIndex;
collisionInfo._contactPoint = penetratorCenter + penetratorRadius * glm::normalize(totalPenetration);
return true;
}
return false;
} }
void Model::updateJointState(int index) { void Model::updateJointState(int index) {
@ -736,24 +736,30 @@ void Model::renderCollisionProxies(float alpha) {
glPopMatrix(); glPopMatrix();
} }
bool Model::collisionHitsMoveableJoint(ModelCollisionInfo& collision) const { bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const {
// the joint is pokable by a collision if it exists and is free to move if (collision._type == MODEL_COLLISION) {
const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._jointIndex]; // the joint is pokable by a collision if it exists and is free to move
if (joint.parentIndex == -1 || _jointStates.isEmpty()) { const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._flags];
return false; if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
return false;
}
// an empty freeLineage means the joint can't move
const FBXGeometry& geometry = _geometry->getFBXGeometry();
int jointIndex = collision._flags;
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
return !freeLineage.isEmpty();
} }
// an empty freeLineage means the joint can't move return false;
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<int>& freeLineage = geometry.joints.at(collision._jointIndex).freeLineage;
return !freeLineage.isEmpty();
} }
void Model::applyCollision(ModelCollisionInfo& collision) { void Model::applyCollision(CollisionInfo& collision) {
// This needs work. At the moment it can wiggle joints that are free to move (such as arms) if (collision._type != MODEL_COLLISION) {
// but unmovable joints (such as torso) cannot be influenced at all. return;
}
glm::vec3 jointPosition(0.f); glm::vec3 jointPosition(0.f);
if (getJointPosition(collision._jointIndex, jointPosition)) { int jointIndex = collision._flags;
int jointIndex = collision._jointIndex; if (getJointPosition(jointIndex, jointPosition)) {
const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex]; const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex];
if (joint.parentIndex != -1) { if (joint.parentIndex != -1) {
// compute the approximate distance (travel) that the joint needs to move // compute the approximate distance (travel) that the joint needs to move

View file

@ -17,16 +17,6 @@
#include "ProgramObject.h" #include "ProgramObject.h"
#include "TextureCache.h" #include "TextureCache.h"
class Model;
// TODO: Andrew to move this into its own file
class ModelCollisionInfo : public CollisionInfo {
public:
ModelCollisionInfo() : CollisionInfo(), _model(NULL), _jointIndex(-1) {}
Model* _model;
int _jointIndex;
};
/// A generic 3D model displaying geometry loaded from a URL. /// A generic 3D model displaying geometry loaded from a URL.
class Model : public QObject { class Model : public QObject {
Q_OBJECT Q_OBJECT
@ -162,17 +152,18 @@ public:
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
bool findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius, bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
ModelCollisionInfo& collision, float boneScale = 1.0f, int skipIndex = -1) const; CollisionList& collisions, float boneScale = 1.0f, int skipIndex = -1) const;
void renderCollisionProxies(float alpha); void renderCollisionProxies(float alpha);
/// \param collision details about the collisions
/// \return true if the collision is against a moveable joint /// \return true if the collision is against a moveable joint
bool collisionHitsMoveableJoint(ModelCollisionInfo& collision) const; bool collisionHitsMoveableJoint(CollisionInfo& collision) const;
/// \param collisionInfo info about the collision /// \param collision details about the collision
/// Use the collisionInfo to affect the model /// Use the collision to affect the model
void applyCollision(ModelCollisionInfo& collisionInfo); void applyCollision(CollisionInfo& collision);
protected: protected:

View file

@ -135,11 +135,7 @@ public:
virtual const glm::vec3& getVelocity() const { return vec3Zero; } virtual const glm::vec3& getVelocity() const { return vec3Zero; }
virtual bool findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) { virtual bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
return false;
}
virtual bool findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
return false; return false;
} }
@ -151,6 +147,8 @@ public:
virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual float getBoundingRadius() const { return 1.f; }
protected: protected:
glm::vec3 _position; glm::vec3 _position;
glm::vec3 _handPosition; glm::vec3 _handPosition;

View file

@ -791,7 +791,6 @@ const char* OctreeSceneStats::getItemValue(Item item) {
break; break;
} }
default: default:
sprintf(_itemValueBuffer, "");
break; break;
} }
return _itemValueBuffer; return _itemValueBuffer;

View file

@ -19,9 +19,11 @@
#include "ParticleEditPacketSender.h" #include "ParticleEditPacketSender.h"
#include "ParticleTree.h" #include "ParticleTree.h"
const int MAX_COLLISIONS_PER_PARTICLE = 16;
ParticleCollisionSystem::ParticleCollisionSystem(ParticleEditPacketSender* packetSender, ParticleCollisionSystem::ParticleCollisionSystem(ParticleEditPacketSender* packetSender,
ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio, ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio,
AvatarHashMap* avatars) { AvatarHashMap* avatars) : _collisions(MAX_COLLISIONS_PER_PARTICLE) {
init(packetSender, particles, voxels, audio, avatars); init(packetSender, particles, voxels, audio, avatars);
} }
@ -181,39 +183,53 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
const float COLLISION_FREQUENCY = 0.5f; const float COLLISION_FREQUENCY = 0.5f;
glm::vec3 penetration; glm::vec3 penetration;
_collisions.clear();
foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) { foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
AvatarData* avatar = avatarPointer.data(); AvatarData* avatar = avatarPointer.data();
CollisionInfo collisionInfo;
collisionInfo._damping = DAMPING;
collisionInfo._elasticity = ELASTICITY;
if (avatar->findSphereCollisionWithHands(center, radius, collisionInfo)) {
// TODO: Andrew to resurrect particles-vs-avatar body collisions
//avatar->findSphereCollisionWithSkeleton(center, radius, collisionInfo)) {
collisionInfo._addedVelocity /= (float)(TREE_SCALE);
glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity();
if (glm::dot(relativeVelocity, collisionInfo._penetration) < 0.f) {
// only collide when particle and collision point are moving toward each other
// (doing this prevents some "collision snagging" when particle penetrates the object)
// HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against the avatar. // use a very generous bounding radius since the arms can stretch
// NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle. float totalRadius = 2.f * avatar->getBoundingRadius() + radius;
// TODO: make this less hacky when we have more per-collision details glm::vec3 relativePosition = center - avatar->getPosition();
float elasticity = ELASTICITY; if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) {
float attenuationFactor = glm::length(collisionInfo._addedVelocity) / HALTING_SPEED; continue;
float damping = DAMPING; }
if (attenuationFactor < 1.f) {
collisionInfo._addedVelocity *= attenuationFactor; if (avatar->findParticleCollisions(center, radius, _collisions)) {
elasticity *= attenuationFactor; int numCollisions = _collisions.size();
// NOTE: the math below keeps the damping piecewise continuous, for (int i = 0; i < numCollisions; ++i) {
// while ramping it up to 1.0 when attenuationFactor = 0 CollisionInfo* collision = _collisions.getCollision(i);
damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING); collision->_damping = DAMPING;
collision->_elasticity = ELASTICITY;
collision->_addedVelocity /= (float)(TREE_SCALE);
glm::vec3 relativeVelocity = collision->_addedVelocity - particle->getVelocity();
if (glm::dot(relativeVelocity, collision->_penetration) <= 0.f) {
// only collide when particle and collision point are moving toward each other
// (doing this prevents some "collision snagging" when particle penetrates the object)
// HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against them.
if (collision->_type == PADDLE_HAND_COLLISION) {
// NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle.
// TODO: make this less hacky when we have more per-collision details
float elasticity = ELASTICITY;
float attenuationFactor = glm::length(collision->_addedVelocity) / HALTING_SPEED;
float damping = DAMPING;
if (attenuationFactor < 1.f) {
collision->_addedVelocity *= attenuationFactor;
elasticity *= attenuationFactor;
// NOTE: the math below keeps the damping piecewise continuous,
// while ramping it up to 1 when attenuationFactor = 0
damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING);
}
}
// HACK END
updateCollisionSound(particle, collision->_penetration, COLLISION_FREQUENCY);
collision->_penetration /= (float)(TREE_SCALE);
particle->applyHardCollision(*collision);
queueParticlePropertiesUpdate(particle);
} }
// HACK END
updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY);
collisionInfo._penetration /= (float)(TREE_SCALE);
particle->applyHardCollision(collisionInfo);
queueParticlePropertiesUpdate(particle);
} }
} }
} }

View file

@ -66,6 +66,7 @@ private:
VoxelTree* _voxels; VoxelTree* _voxels;
AbstractAudioInterface* _audio; AbstractAudioInterface* _audio;
AvatarHashMap* _avatars; AvatarHashMap* _avatars;
CollisionList _collisions;
}; };
#endif /* defined(__hifi__ParticleCollisionSystem__) */ #endif /* defined(__hifi__ParticleCollisionSystem__) */

View file

@ -0,0 +1,42 @@
//
// CollisionInfo.cpp
// hifi
//
// Created by Andrew Meadows on 2014.02.14
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include "CollisionInfo.h"
CollisionList::CollisionList(int maxSize) :
_maxSize(maxSize),
_size(0) {
_collisions.resize(_maxSize);
}
CollisionInfo* CollisionList::getNewCollision() {
// return pointer to existing CollisionInfo, or NULL of list is full
return (_size < _maxSize) ? &(_collisions[++_size]) : NULL;
}
CollisionInfo* CollisionList::getCollision(int index) {
return (index > -1 && index < _size) ? &(_collisions[index]) : NULL;
}
void CollisionList::clear() {
for (int i = 0; i < _size; ++i) {
// we only clear the important stuff
CollisionInfo& collision = _collisions[i];
collision._type = BASE_COLLISION;
collision._data = NULL; // CollisionInfo does not own whatever this points to.
collision._flags = 0;
// we rely on the consumer to properly overwrite these fields when the collision is "created"
//collision._damping;
//collision._elasticity;
//collision._contactPoint;
//collision._penetration;
//collision._addedVelocity;
}
_size = 0;
}

View file

@ -11,15 +11,42 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
const uint32_t COLLISION_GROUP_ENVIRONMENT = 1U << 0; #include <QVector>
const uint32_t COLLISION_GROUP_AVATARS = 1U << 1;
const uint32_t COLLISION_GROUP_VOXELS = 1U << 2; enum CollisionType {
const uint32_t COLLISION_GROUP_PARTICLES = 1U << 3; BASE_COLLISION = 0,
PADDLE_HAND_COLLISION,
MODEL_COLLISION,
};
const quint32 COLLISION_GROUP_ENVIRONMENT = 1U << 0;
const quint32 COLLISION_GROUP_AVATARS = 1U << 1;
const quint32 COLLISION_GROUP_VOXELS = 1U << 2;
const quint32 COLLISION_GROUP_PARTICLES = 1U << 3;
// CollisionInfo contains details about the collision between two things: BodyA and BodyB.
// The assumption is that the context that analyzes the collision knows about BodyA but
// does not necessarily know about BodyB. Hence the data storred in the CollisionInfo
// is expected to be relative to BodyA (for example the penetration points from A into B).
class CollisionInfo { class CollisionInfo {
public: public:
CollisionInfo() CollisionInfo()
: _damping(0.f), : _type(0),
_data(NULL),
_flags(0),
_damping(0.f),
_elasticity(1.f),
_contactPoint(0.f),
_penetration(0.f),
_addedVelocity(0.f) {
}
CollisionInfo(qint32 type)
: _type(type),
_data(NULL),
_flags(0),
_damping(0.f),
_elasticity(1.f), _elasticity(1.f),
_contactPoint(0.f), _contactPoint(0.f),
_penetration(0.f), _penetration(0.f),
@ -28,13 +55,40 @@ public:
~CollisionInfo() {} ~CollisionInfo() {}
//glm::vec3 _normal; qint32 _type; // type of Collision (will determine what is supposed to be in _data and _flags)
float _damping; void* _data; // pointer to user supplied data
float _elasticity; quint32 _flags; // 32 bits for whatever
glm::vec3 _contactPoint; // world-frame point on bodyA that is deepest into bodyB
glm::vec3 _penetration; // depth that bodyA penetrates into bodyB float _damping; // range [0,1] of friction coeficient
glm::vec3 _addedVelocity; float _elasticity; // range [0,1] of energy conservation
glm::vec3 _contactPoint; // world-frame point on BodyA that is deepest into BodyB
glm::vec3 _penetration; // depth that BodyA penetrates into BodyB
glm::vec3 _addedVelocity; // velocity of BodyB
}; };
// CollisionList is intended to be a recycled container. Fill the CollisionInfo's,
// use them, and then clear them for the next frame or context.
class CollisionList {
public:
CollisionList(int maxSize);
/// \return pointer to next collision. NULL if list is full.
CollisionInfo* getNewCollision();
/// \return pointer to collision by index. NULL if index out of bounds.
CollisionInfo* getCollision(int index);
/// \return number of valid collisions
int size() const { return _size; }
/// Clear valid collisions.
void clear();
private:
int _maxSize;
int _size;
QVector<CollisionInfo> _collisions;
};
#endif /* defined(__hifi__CollisionInfo__) */ #endif /* defined(__hifi__CollisionInfo__) */