mirror of
https://github.com/overte-org/overte.git
synced 2025-08-07 19:10:38 +02:00
Merge pull request #2003 from AndrewMeadows/avatar-interaction
Particle vs Avatar collisions work again
This commit is contained in:
commit
26d076f166
16 changed files with 324 additions and 196 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,42 +303,56 @@ 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)) {
|
||||||
|
// the Model doesn't have velocity info, so we have to set it for each new collision
|
||||||
|
int postNumCollisions = collisions.size();
|
||||||
|
for (int i = preNumCollisions; i < postNumCollisions; ++i) {
|
||||||
|
CollisionInfo* collision = collisions.getCollision(i);
|
||||||
|
collision->_penetration /= (float)(TREE_SCALE);
|
||||||
|
collision->_addedVelocity = getVelocity();
|
||||||
|
}
|
||||||
|
collided = true;
|
||||||
|
}
|
||||||
|
return collided;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* adebug TODO: make this work again
|
|
||||||
bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
|
|
||||||
int jointIndex = _skeletonModel.findSphereCollision(sphereCenter, sphereRadius, collision._penetration);
|
|
||||||
if (jointIndex != -1) {
|
|
||||||
collision._penetration /= (float)(TREE_SCALE);
|
|
||||||
collision._addedVelocity = getVelocity();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
|
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
|
||||||
AvatarData::setFaceModelURL(faceModelURL);
|
AvatarData::setFaceModelURL(faceModelURL);
|
||||||
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst");
|
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst");
|
||||||
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
if (collision._type == MODEL_COLLISION) {
|
||||||
// the joint is pokable by a collision if it exists and is free to move
|
// the joint is pokable by a collision if it exists and is free to move
|
||||||
const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._jointIndex];
|
const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._flags];
|
||||||
if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
|
if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// an empty freeLineage means the joint can't move
|
// an empty freeLineage means the joint can't move
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
const QVector<int>& freeLineage = geometry.joints.at(collision._jointIndex).freeLineage;
|
int jointIndex = collision._flags;
|
||||||
|
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
|
||||||
return !freeLineage.isEmpty();
|
return !freeLineage.isEmpty();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -791,7 +791,6 @@ const char* OctreeSceneStats::getItemValue(Item item) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
sprintf(_itemValueBuffer, "");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return _itemValueBuffer;
|
return _itemValueBuffer;
|
||||||
|
|
|
@ -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,42 +183,56 @@ 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;
|
// use a very generous bounding radius since the arms can stretch
|
||||||
collisionInfo._elasticity = ELASTICITY;
|
float totalRadius = 2.f * avatar->getBoundingRadius() + radius;
|
||||||
if (avatar->findSphereCollisionWithHands(center, radius, collisionInfo)) {
|
glm::vec3 relativePosition = center - avatar->getPosition();
|
||||||
// TODO: Andrew to resurrect particles-vs-avatar body collisions
|
if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) {
|
||||||
//avatar->findSphereCollisionWithSkeleton(center, radius, collisionInfo)) {
|
continue;
|
||||||
collisionInfo._addedVelocity /= (float)(TREE_SCALE);
|
}
|
||||||
glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity();
|
|
||||||
if (glm::dot(relativeVelocity, collisionInfo._penetration) < 0.f) {
|
if (avatar->findParticleCollisions(center, radius, _collisions)) {
|
||||||
|
int numCollisions = _collisions.size();
|
||||||
|
for (int i = 0; i < numCollisions; ++i) {
|
||||||
|
CollisionInfo* collision = _collisions.getCollision(i);
|
||||||
|
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
|
// only collide when particle and collision point are moving toward each other
|
||||||
// (doing this prevents some "collision snagging" when particle penetrates the object)
|
// (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.
|
// 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.
|
// 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
|
// TODO: make this less hacky when we have more per-collision details
|
||||||
float elasticity = ELASTICITY;
|
float elasticity = ELASTICITY;
|
||||||
float attenuationFactor = glm::length(collisionInfo._addedVelocity) / HALTING_SPEED;
|
float attenuationFactor = glm::length(collision->_addedVelocity) / HALTING_SPEED;
|
||||||
float damping = DAMPING;
|
float damping = DAMPING;
|
||||||
if (attenuationFactor < 1.f) {
|
if (attenuationFactor < 1.f) {
|
||||||
collisionInfo._addedVelocity *= attenuationFactor;
|
collision->_addedVelocity *= attenuationFactor;
|
||||||
elasticity *= attenuationFactor;
|
elasticity *= attenuationFactor;
|
||||||
// NOTE: the math below keeps the damping piecewise continuous,
|
// NOTE: the math below keeps the damping piecewise continuous,
|
||||||
// while ramping it up to 1.0 when attenuationFactor = 0
|
// while ramping it up to 1 when attenuationFactor = 0
|
||||||
damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING);
|
damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// HACK END
|
// HACK END
|
||||||
|
|
||||||
updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY);
|
updateCollisionSound(particle, collision->_penetration, COLLISION_FREQUENCY);
|
||||||
collisionInfo._penetration /= (float)(TREE_SCALE);
|
collision->_penetration /= (float)(TREE_SCALE);
|
||||||
particle->applyHardCollision(collisionInfo);
|
particle->applyHardCollision(*collision);
|
||||||
queueParticlePropertiesUpdate(particle);
|
queueParticlePropertiesUpdate(particle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParticleCollisionSystem::queueParticlePropertiesUpdate(Particle* particle) {
|
void ParticleCollisionSystem::queueParticlePropertiesUpdate(Particle* particle) {
|
||||||
|
|
|
@ -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__) */
|
||||||
|
|
42
libraries/shared/src/CollisionInfo.cpp
Normal file
42
libraries/shared/src/CollisionInfo.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
|
@ -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__) */
|
||||||
|
|
Loading…
Reference in a new issue