From b7352335f23e2c61b4e3b7854f786136ca93f60a Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 14 Feb 2014 16:31:53 -0800 Subject: [PATCH 1/4] Adding collisions options to menu at a second location. And adding CollideWithParticle option which will be working soon... --- interface/src/Menu.cpp | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4a3733f760..6e2cf17c88 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -166,14 +166,19 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly); - QMenu* collisionsOptionsMenu = editMenu->addMenu("Collision Options"); - - 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())); + { + // add collision options to the general edit menu + QMenu* collisionsOptionsMenu = editMenu->addMenu("Collision Options"); + QObject* avatar = appInstance->getAvatar(); + addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, + 0, false, avatar, SLOT(updateCollisionFlags())); + addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, + 0, true, avatar, SLOT(updateCollisionFlags())); + addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, + 0, false, avatar, SLOT(updateCollisionFlags())); + addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, + 0, true, avatar, SLOT(updateCollisionFlags())); + } QMenu* toolsMenu = addMenu("Tools"); @@ -341,6 +346,20 @@ Menu::Menu() : SLOT(setTCPEnabled(bool))); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false); + { + // add collision options to the avatar menu + QMenu* collisionsOptionsMenu = avatarOptionsMenu->addMenu("Collision Options"); + QObject* avatar = appInstance->getAvatar(); + addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, + 0, false, avatar, SLOT(updateCollisionFlags())); + addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, + 0, true, avatar, SLOT(updateCollisionFlags())); + addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, + 0, false, avatar, SLOT(updateCollisionFlags())); + addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, + 0, true, avatar, SLOT(updateCollisionFlags())); + } + QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options"); addCheckableActionToQMenuAndActionHash(handOptionsMenu, @@ -515,6 +534,11 @@ void Menu::loadSettings(QSettings* settings) { Application::getInstance()->getProfile()->loadData(settings); Application::getInstance()->updateWindowTitle(); 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) { From 335141049cfb2d582b886f3ebbdac7d4bcb71d75 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 14 Feb 2014 16:33:19 -0800 Subject: [PATCH 2/4] Cleaning up collision check API's and re-enabling collisions with particles. --- interface/src/avatar/Avatar.cpp | 112 +++++++++--------- interface/src/avatar/Avatar.h | 34 ++---- interface/src/avatar/Hand.cpp | 32 +++-- interface/src/avatar/Head.cpp | 8 +- interface/src/avatar/Head.h | 2 +- interface/src/avatar/MyAvatar.cpp | 4 +- interface/src/renderer/Model.cpp | 66 ++++++----- interface/src/renderer/Model.h | 23 ++-- libraries/avatars/src/AvatarData.h | 8 +- .../particles/src/ParticleCollisionSystem.cpp | 76 +++++++----- .../particles/src/ParticleCollisionSystem.h | 1 + libraries/shared/src/CollisionInfo.cpp | 42 +++++++ libraries/shared/src/CollisionInfo.h | 76 ++++++++++-- 13 files changed, 298 insertions(+), 186 deletions(-) create mode 100644 libraries/shared/src/CollisionInfo.cpp diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index f36c03cba6..74a3838dc1 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -273,27 +273,19 @@ bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc } bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, - ModelCollisionList& collisions, int skeletonSkipIndex) { - bool didPenetrate = false; - glm::vec3 skeletonPenetration; - ModelCollisionInfo collisionInfo; - /* Temporarily disabling collisions against the skeleton because the collision proxies up - * 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; + CollisionList& collisions, int skeletonSkipIndex) { + // Temporarily disabling collisions against the skeleton because the collision proxies up + // near the neck are bad and prevent the hand from hitting the face. + //return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions, 1.0f, skeletonSkipIndex); + return _head.getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions); } -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(); if (handData) { for (int i = 0; i < NUM_HANDS; i++) { @@ -311,41 +303,55 @@ bool Avatar::findSphereCollisionWithHands(const glm::vec3& sphereCenter, float s break; } } + + int jointIndex = -1; glm::vec3 handPosition; if (i == 0) { _skeletonModel.getLeftHandPosition(handPosition); + jointIndex = _skeletonModel.getLeftHandJointIndex(); } else { _skeletonModel.getRightHandPosition(handPosition); + jointIndex = _skeletonModel.getRightHandJointIndex(); } glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis; glm::vec3 diskNormal = palm->getNormal(); - float diskThickness = 0.08f; + const float DISK_THICKNESS = 0.08f; // collide against the disk - if (findSphereDiskPenetration(sphereCenter, sphereRadius, - diskCenter, HAND_PADDLE_RADIUS, diskThickness, diskNormal, - collision._penetration)) { - collision._addedVelocity = palm->getVelocity(); - return true; + glm::vec3 penetration; + if (findSphereDiskPenetration(particleCenter, particleRadius, + diskCenter, HAND_PADDLE_RADIUS, DISK_THICKNESS, diskNormal, + penetration)) { + 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; -} - -/* 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; + // 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 false; + return collided; } -*/ void Avatar::setFaceModelURL(const QUrl &faceModelURL) { AvatarData::setFaceModelURL(faceModelURL); @@ -430,9 +436,9 @@ void Avatar::updateCollisionFlags() { if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithVoxels)) { _collisionFlags |= COLLISION_GROUP_VOXELS; } - //if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) { - // _collisionFlags |= COLLISION_GROUP_PARTICLES; - //} + if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) { + _collisionFlags |= COLLISION_GROUP_PARTICLES; + } } void Avatar::setScale(float scale) { @@ -449,34 +455,34 @@ float Avatar::getHeight() const { return extents.maximum.y - extents.minimum.y; } -bool Avatar::collisionWouldMoveAvatar(ModelCollisionInfo& collision) const { - // ATM only the Skeleton is pokeable - // TODO: make poke affect head - if (!collision._model) { +bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const { + if (!collision._data || collision._type != MODEL_COLLISION) { return false; } - if (collision._model == &_skeletonModel && collision._jointIndex != -1) { + Model* model = static_cast(collision._data); + int jointIndex = collision._flags; + + if (model == &(_skeletonModel) && jointIndex != -1) { // collision response of skeleton is temporarily disabled return false; //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 false; } -void Avatar::applyCollision(ModelCollisionInfo& collision) { - if (!collision._model) { +void Avatar::applyCollision(CollisionInfo& collision) { + if (!collision._data || collision._type != MODEL_COLLISION) { return; } - if (collision._model == &(_head.getFaceModel())) { + // TODO: make skeleton also respond to collisions + Model* model = static_cast(collision._data); + if (model == &(_head.getFaceModel())) { _head.applyCollision(collision); } - // TODO: make skeleton respond to collisions - //if (collision._model == &_skeletonModel && collision._jointIndex != -1) { - // _skeletonModel.applyCollision(collision); - //} } float Avatar::getPelvisFloatingHeight() const { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 2fc26a36b5..cc1168ca88 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -57,8 +57,6 @@ enum ScreenTintLayer { NUM_SCREEN_TINT_LAYERS }; -typedef QVector ModelCollisionList; - // 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 // Grayson as he's building a street around here for demo dinner 2 @@ -97,26 +95,19 @@ public: /// Checks for penetration between the described sphere and the avatar. /// \param penetratorCenter the center 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 /// \return whether or not the sphere penetrated 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. - /// \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 findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision); + /// Checks for collision between the a spherical particle and the avatar (including paddle hands) + /// \param collisionCenter the center of particle's bounding sphere + /// \param collisionRadius the radius of particle's bounding sphere + /// \param collisions[out] a list to which collisions get appended + /// \return whether or not the particle collided + 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 void setFaceModelURL(const QUrl& faceModelURL); @@ -126,13 +117,13 @@ public: 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 - bool collisionWouldMoveAvatar(ModelCollisionInfo& collision) const; + bool collisionWouldMoveAvatar(CollisionInfo& collision) const; /// \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: void updateCollisionFlags(); @@ -164,6 +155,7 @@ protected: glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const; void setScale(float scale); + float getHeight() const; float getPelvisFloatingHeight() const; float getPelvisToHeadLength() const; diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index af66c08bf4..7c9905557e 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -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) { if (!avatar || avatar == _owningAvatar) { // don't collide with our own hands (that is done elsewhere) @@ -137,7 +141,6 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) { continue; } glm::vec3 totalPenetration; - ModelCollisionList collisions; if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) { // Check for palm collisions glm::vec3 myPalmPosition = palm.getPosition(); @@ -171,20 +174,22 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) { } } } - if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions)) { - for (int j = 0; j < collisions.size(); ++j) { + handCollisions.clear(); + if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions)) { + for (int j = 0; j < handCollisions.size(); ++j) { + CollisionInfo* collision = handCollisions.getCollision(j); 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 // 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 { // when !isMyHand then avatar is MyAvatar and we apply the collision // 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 // simulation to do the right thing. - avatar->applyCollision(collisions[j]); + avatar->applyCollision(*collision); } } } @@ -200,7 +205,6 @@ void Hand::collideAgainstOurself() { return; } - ModelCollisionList collisions; int leftPalmIndex, rightPalmIndex; getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex); float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale(); @@ -210,16 +214,18 @@ void Hand::collideAgainstOurself() { if (!palm.isActive()) { 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(); + // ignoring everything below the parent of the parent of the last free joint int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex( skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() : (i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1))); - if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions, skipIndex)) { - for (int j = 0; j < collisions.size(); ++j) { - totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration); + + handCollisions.clear(); + 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 diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index bb88530aa7..ddb0660364 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -219,7 +219,7 @@ float Head::getTweakedRoll() const { 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 // we collide against a hard coded collision proxy. // 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 // NOTE: that penetration is in opposite direction (points the way out for the point, not the sphere) glm::vec3 penetration; - if (findPointSpherePenetration(collisionInfo._contactPoint, HEAD_CENTER, HEAD_RADIUS, penetration)) { + if (findPointSpherePenetration(collision._contactPoint, HEAD_CENTER, HEAD_RADIUS, penetration)) { // compute lean angles Avatar* owningAvatar = static_cast(_owningAvatar); 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); float neckLength = glm::length(_position - neckPosition); if (neckLength > 0.f) { - float forward = glm::dot(collisionInfo._penetration, zAxis) / neckLength; - float sideways = - glm::dot(collisionInfo._penetration, xAxis) / neckLength; + float forward = glm::dot(collision._penetration, zAxis) / neckLength; + float sideways = - glm::dot(collision._penetration, xAxis) / neckLength; addLean(sideways, forward); } } diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index eae8223903..c88e654d95 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -80,7 +80,7 @@ public: float getTweakedYaw() const; float getTweakedRoll() const; - void applyCollision(ModelCollisionInfo& collisionInfo); + void applyCollision(CollisionInfo& collisionInfo); private: // disallow copies of the Head, copy of owning Avatar is disallowed too diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 85c0c2b35a..6673abe88b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -955,7 +955,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { // no need to compute a bunch of stuff if we have one or fewer avatars return; } - float myBoundingRadius = 0.5f * getHeight(); + float myBoundingRadius = getBoundingRadius(); // HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis // TODO: make the collision work without assuming avatar orientation @@ -975,7 +975,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { if (_distanceToNearestAvatar > distance) { _distanceToNearestAvatar = distance; } - float theirBoundingRadius = 0.5f * avatar->getHeight(); + float theirBoundingRadius = avatar->getBoundingRadius(); if (distance < myBoundingRadius + theirBoundingRadius) { Extents theirStaticExtents = _skeletonModel.getStaticExtents(); glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 7686b1ac7f..97cee8a05e 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -446,9 +446,9 @@ bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct return false; } -bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius, - ModelCollisionInfo& collisionInfo, float boneScale, int skipIndex) const { - int jointIndex = -1; +bool Model::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, + CollisionList& collisions, float boneScale, int skipIndex) const { + bool collided = false; const glm::vec3 relativeCenter = penetratorCenter - _translation; const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::vec3 totalPenetration; @@ -477,22 +477,22 @@ bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetra if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end, startRadius, endRadius, bonePenetration)) { totalPenetration = addPenetrations(totalPenetration, bonePenetration); - // BUG: we currently overwrite the jointIndex with the last one found - // which can cause incorrect collisions when colliding against more than - // one joint. - // TODO: fix this. - jointIndex = i; + CollisionInfo* collision = collisions.getNewCollision(); + if (collision) { + collision->_type = MODEL_COLLISION; + collision->_data = (void*)(this); + 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: ; } - if (jointIndex != -1) { - // 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; + return collided; } void Model::updateJointState(int index) { @@ -725,24 +725,30 @@ void Model::renderCollisionProxies(float alpha) { glPopMatrix(); } -bool Model::collisionHitsMoveableJoint(ModelCollisionInfo& collision) const { - // the joint is pokable by a collision if it exists and is free to move - const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._jointIndex]; - if (joint.parentIndex == -1 || _jointStates.isEmpty()) { - return false; +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 + const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._flags]; + 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& freeLineage = geometry.joints.at(jointIndex).freeLineage; + return !freeLineage.isEmpty(); } - // an empty freeLineage means the joint can't move - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - const QVector& freeLineage = geometry.joints.at(collision._jointIndex).freeLineage; - return !freeLineage.isEmpty(); + return false; } -void Model::applyCollision(ModelCollisionInfo& collision) { - // This needs work. At the moment it can wiggle joints that are free to move (such as arms) - // but unmovable joints (such as torso) cannot be influenced at all. +void Model::applyCollision(CollisionInfo& collision) { + if (collision._type != MODEL_COLLISION) { + return; + } + glm::vec3 jointPosition(0.f); - if (getJointPosition(collision._jointIndex, jointPosition)) { - int jointIndex = collision._jointIndex; + int jointIndex = collision._flags; + if (getJointPosition(jointIndex, jointPosition)) { const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex]; if (joint.parentIndex != -1) { // compute the approximate distance (travel) that the joint needs to move diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index bab25bed7a..6f61d6e204 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -17,16 +17,6 @@ #include "ProgramObject.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. class Model : public QObject { Q_OBJECT @@ -162,17 +152,18 @@ public: bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; - bool findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius, - ModelCollisionInfo& collision, float boneScale = 1.0f, int skipIndex = -1) const; + bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, + CollisionList& collisions, float boneScale = 1.0f, int skipIndex = -1) const; void renderCollisionProxies(float alpha); + /// \param collision details about the collisions /// \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 - /// Use the collisionInfo to affect the model - void applyCollision(ModelCollisionInfo& collisionInfo); + /// \param collision details about the collision + /// Use the collision to affect the model + void applyCollision(CollisionInfo& collision); protected: diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 08604a95f1..be171ed145 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -135,14 +135,10 @@ public: 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; - } - bool hasIdentityChangedAfterParsing(const QByteArray& packet); QByteArray identityByteArray(); @@ -150,6 +146,8 @@ public: const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); + + virtual float getBoundingRadius() const { return 1.f; } protected: glm::vec3 _position; diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index bb0260c2bf..2d272a8f1f 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -19,9 +19,11 @@ #include "ParticleEditPacketSender.h" #include "ParticleTree.h" +const int MAX_COLLISIONS_PER_PARTICLE = 16; + ParticleCollisionSystem::ParticleCollisionSystem(ParticleEditPacketSender* packetSender, ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio, - AvatarHashMap* avatars) { + AvatarHashMap* avatars) : _collisions(MAX_COLLISIONS_PER_PARTICLE) { init(packetSender, particles, voxels, audio, avatars); } @@ -181,39 +183,53 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { const float COLLISION_FREQUENCY = 0.5f; glm::vec3 penetration; + _collisions.clear(); foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) { 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. - // 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(collisionInfo._addedVelocity) / HALTING_SPEED; - float damping = DAMPING; - if (attenuationFactor < 1.f) { - collisionInfo._addedVelocity *= attenuationFactor; - elasticity *= attenuationFactor; - // NOTE: the math below keeps the damping piecewise continuous, - // while ramping it up to 1.0 when attenuationFactor = 0 - damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING); + // use a very generous bounding radius since the arms can stretch + float totalRadius = 2.f * avatar->getBoundingRadius() + radius; + glm::vec3 relativePosition = center - avatar->getPosition(); + if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) { + continue; + } + + 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 + // (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); } } } diff --git a/libraries/particles/src/ParticleCollisionSystem.h b/libraries/particles/src/ParticleCollisionSystem.h index c525d3ddfc..3bff843743 100644 --- a/libraries/particles/src/ParticleCollisionSystem.h +++ b/libraries/particles/src/ParticleCollisionSystem.h @@ -66,6 +66,7 @@ private: VoxelTree* _voxels; AbstractAudioInterface* _audio; AvatarHashMap* _avatars; + CollisionList _collisions; }; #endif /* defined(__hifi__ParticleCollisionSystem__) */ diff --git a/libraries/shared/src/CollisionInfo.cpp b/libraries/shared/src/CollisionInfo.cpp new file mode 100644 index 0000000000..5d74d591c6 --- /dev/null +++ b/libraries/shared/src/CollisionInfo.cpp @@ -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; +} + diff --git a/libraries/shared/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h index 1fa95cd83a..acd127435c 100644 --- a/libraries/shared/src/CollisionInfo.h +++ b/libraries/shared/src/CollisionInfo.h @@ -11,15 +11,42 @@ #include -const uint32_t COLLISION_GROUP_ENVIRONMENT = 1U << 0; -const uint32_t COLLISION_GROUP_AVATARS = 1U << 1; -const uint32_t COLLISION_GROUP_VOXELS = 1U << 2; -const uint32_t COLLISION_GROUP_PARTICLES = 1U << 3; +#include + +enum CollisionType { + 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 { public: 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), _contactPoint(0.f), _penetration(0.f), @@ -28,13 +55,40 @@ public: ~CollisionInfo() {} - //glm::vec3 _normal; - float _damping; - float _elasticity; - 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; + qint32 _type; // type of Collision (will determine what is supposed to be in _data and _flags) + void* _data; // pointer to user supplied data + quint32 _flags; // 32 bits for whatever + + float _damping; // range [0,1] of friction coeficient + 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 _collisions; +}; #endif /* defined(__hifi__CollisionInfo__) */ From e4ef09e06d91528233b0f96e1d7bcc42d6d4b293 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 17 Feb 2014 09:12:38 -0800 Subject: [PATCH 3/4] Helper method for adding avatar collision submenu --- interface/src/Menu.cpp | 45 +++++++++++++++++------------------------- interface/src/Menu.h | 2 ++ 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index beb8369f44..697b21cd8c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -168,19 +168,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly); - { - // add collision options to the general edit menu - QMenu* collisionsOptionsMenu = editMenu->addMenu("Collision Options"); - QObject* avatar = appInstance->getAvatar(); - addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, - 0, false, avatar, SLOT(updateCollisionFlags())); - addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, - 0, true, avatar, SLOT(updateCollisionFlags())); - addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, - 0, false, avatar, SLOT(updateCollisionFlags())); - addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, - 0, true, avatar, SLOT(updateCollisionFlags())); - } + addAvatarCollisionSubMenu(editMenu); QMenu* toolsMenu = addMenu("Tools"); @@ -348,19 +336,7 @@ Menu::Menu() : SLOT(setTCPEnabled(bool))); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false); - { - // add collision options to the avatar menu - QMenu* collisionsOptionsMenu = avatarOptionsMenu->addMenu("Collision Options"); - QObject* avatar = appInstance->getAvatar(); - addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, - 0, false, avatar, SLOT(updateCollisionFlags())); - addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, - 0, true, avatar, SLOT(updateCollisionFlags())); - addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, - 0, false, avatar, SLOT(updateCollisionFlags())); - addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, - 0, true, avatar, SLOT(updateCollisionFlags())); - } + addAvatarCollisionSubMenu(avatarOptionsMenu); QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options"); @@ -1230,6 +1206,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) { int lastIndex; lastIndex = string.lastIndexOf(search); @@ -1240,4 +1232,3 @@ QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string) return string; } - diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 84f5325e8f..1ac5341ed9 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -139,6 +139,8 @@ private: void updateFrustumRenderModeAction(); + void addAvatarCollisionSubMenu(QMenu* overMenu); + QHash _actionHash; int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback BandwidthDialog* _bandwidthDialog; From b28a18dab04f842f3a23d349c02307baacb55484 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 17 Feb 2014 09:17:34 -0800 Subject: [PATCH 4/4] Remove warning about null-op sprintf() in g++ --- libraries/octree/src/OctreeSceneStats.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index 59287e3c5c..8a5a731cff 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -791,7 +791,6 @@ const char* OctreeSceneStats::getItemValue(Item item) { break; } default: - sprintf(_itemValueBuffer, ""); break; } return _itemValueBuffer;