diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index e7f6120d52..7eb5807c6f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -162,14 +162,18 @@ Menu::Menu() : addDisabledActionAndSeparator(editMenu, "Physics"); addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, true); - addCheckableActionToQMenuAndActionHash(editMenu, - MenuOption::Collisions, - 0, - true, - appInstance->getAvatar(), - SLOT(setWantCollisionsOn(bool))); + 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())); QMenu* toolsMenu = addMenu("Tools"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index edc6035f60..742b9fc66f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -167,6 +167,10 @@ namespace MenuOption { const QString ChatCircling = "Chat Circling"; const QString CollisionProxies = "Collision Proxies"; const QString Collisions = "Collisions"; + const QString CollideWithAvatars = "Collide With Avatars"; + const QString CollideWithParticles = "Collide With Particles"; + const QString CollideWithVoxels = "Collide With Voxels"; + const QString CollideWithEnvironment = "Collide With World Boundaries"; const QString CopyVoxels = "Copy"; const QString CoverageMap = "Render Coverage Map"; const QString CoverageMapV2 = "Render Coverage Map V2"; diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index ef7e049d75..73ec791714 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -15,7 +15,6 @@ #include #include -#include #include #include "InterfaceConfig.h" diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index b97a50ce46..c299c0c617 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -74,6 +74,7 @@ Avatar::Avatar() : _mouseRayDirection(0.0f, 0.0f, 0.0f), _moving(false), _owningAvatarMixer(), + _collisionFlags(0), _initialized(false) { // we may have been created in the network thread, but we live in the main thread @@ -159,7 +160,13 @@ void Avatar::render(bool forceRenderHead) { Glower glower(_moving && glm::length(toTarget) > GLOW_DISTANCE ? 1.0f : 0.0f); // render body - renderBody(forceRenderHead); + if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) { + _skeletonModel.renderCollisionProxies(1.f); + } + + if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { + renderBody(forceRenderHead); + } // render sphere when far away const float MAX_ANGLE = 10.f; @@ -264,34 +271,28 @@ bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc return false; } -bool Avatar::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, - glm::vec3& penetration, int skeletonSkipIndex) const { +bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, + ModelCollisionList& collisions, int skeletonSkipIndex) { bool didPenetrate = false; - glm::vec3 totalPenetration; glm::vec3 skeletonPenetration; - if (_skeletonModel.findSpherePenetration(penetratorCenter, penetratorRadius, - skeletonPenetration, 1.0f, skeletonSkipIndex)) { - totalPenetration = addPenetrations(totalPenetration, skeletonPenetration); + ModelCollisionInfo collisionInfo; + if (_skeletonModel.findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo, 1.0f, skeletonSkipIndex)) { + collisionInfo._model = &_skeletonModel; + collisions.push_back(collisionInfo); didPenetrate = true; } - glm::vec3 facePenetration; - if (_head.getFaceModel().findSpherePenetration(penetratorCenter, penetratorRadius, facePenetration)) { - totalPenetration = addPenetrations(totalPenetration, facePenetration); + if (_head.getFaceModel().findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo)) { + collisionInfo._model = &(_head.getFaceModel()); + collisions.push_back(collisionInfo); didPenetrate = true; } - if (didPenetrate) { - penetration = totalPenetration; - return true; - } - return false; + return didPenetrate; } -bool Avatar::findSphereCollision(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) { - // TODO: provide an early exit using bounding sphere of entire avatar - +bool Avatar::findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) { const HandData* handData = getHandData(); if (handData) { - for (int i = 0; i < 2; i++) { + for (int i = 0; i < NUM_HANDS; i++) { const PalmData* palm = handData->getPalm(i); if (palm && palm->hasPaddle()) { // create a disk collision proxy where the hand is @@ -327,14 +328,20 @@ bool Avatar::findSphereCollision(const glm::vec3& sphereCenter, float sphereRadi } } } + return false; +} - if (_skeletonModel.findSpherePenetration(sphereCenter, sphereRadius, collision._penetration)) { +/* 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) { AvatarData::setFaceModelURL(faceModelURL); @@ -406,6 +413,22 @@ void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, glEnd(); } +void Avatar::updateCollisionFlags() { + _collisionFlags = 0; + if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithEnvironment)) { + _collisionFlags |= COLLISION_GROUP_ENVIRONMENT; + } + if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithAvatars)) { + _collisionFlags |= COLLISION_GROUP_AVATARS; + } + if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithVoxels)) { + _collisionFlags |= COLLISION_GROUP_VOXELS; + } + //if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) { + // _collisionFlags |= COLLISION_GROUP_PARTICLES; + //} +} + void Avatar::setScale(float scale) { _scale = scale; @@ -420,6 +443,15 @@ float Avatar::getHeight() const { return extents.maximum.y - extents.minimum.y; } +bool Avatar::poke(ModelCollisionInfo& collision) { + // ATM poke() can only affect the Skeleton (not the head) + // TODO: make poke affect head + if (collision._model == &_skeletonModel && collision._jointIndex != -1) { + return _skeletonModel.poke(collision); + } + return false; +} + float Avatar::getPelvisFloatingHeight() const { return -_skeletonModel.getBindExtents().minimum.y; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index dfe72fdc18..8290115240 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -57,7 +57,7 @@ enum ScreenTintLayer { NUM_SCREEN_TINT_LAYERS }; -class MyAvatar; +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 @@ -97,18 +97,25 @@ 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 penetration[out] the vector in which to store the penetration + /// \param collisions[out] a list of collisions /// \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 findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, - glm::vec3& penetration, int skeletonSkipIndex = -1) const; + bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, + ModelCollisionList& collisions, int skeletonSkipIndex = -1); - /// Checks for collision between the a sphere and the avatar. + /// 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 - virtual bool findSphereCollision(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision); + bool findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision); + + /// 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; } @@ -119,6 +126,15 @@ public: static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2); + float getHeight() const; + + /// \param collision a data structure for storing info about collisions against Models + /// \return true if the collision affects the Avatar models + bool poke(ModelCollisionInfo& collision); + +public slots: + void updateCollisionFlags(); + protected: Head _head; Hand _hand; @@ -137,6 +153,8 @@ protected: bool _moving; ///< set when position is changing QWeakPointer _owningAvatarMixer; + uint32_t _collisionFlags; + // protected methods... glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; } glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; } @@ -144,7 +162,6 @@ 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/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1734b755be..51cea9d085 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -67,9 +67,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) { - if (!Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { - return; - } PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::renderAvatars()"); bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors); diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index b441000cc1..c2ea3f8f31 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -90,9 +90,7 @@ void Hand::simulate(float deltaTime, bool isMine) { calculateGeometry(); if (isMine) { - // Iterate hand controllers, take actions as needed - for (size_t i = 0; i < getNumPalms(); ++i) { PalmData& palm = getPalms()[i]; if (palm.isActive()) { @@ -173,6 +171,7 @@ void Hand::updateCollisions() { int leftPalmIndex, rightPalmIndex; getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex); + ModelCollisionList collisions; // check for collisions for (size_t i = 0; i < getNumPalms(); i++) { PalmData& palm = getPalms()[i]; @@ -182,69 +181,76 @@ void Hand::updateCollisions() { float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale(); glm::vec3 totalPenetration; - // check other avatars - foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) { - Avatar* avatar = static_cast(avatarPointer.data()); - if (avatar == _owningAvatar) { - // don't collid with our own hands - continue; - } - if (Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) { - // Check for palm collisions - glm::vec3 myPalmPosition = palm.getPosition(); - float palmCollisionDistance = 0.1f; - bool wasColliding = palm.getIsCollidingWithPalm(); - palm.setIsCollidingWithPalm(false); - // If 'Play Slaps' is enabled, look for palm-to-palm collisions and make sound - for (size_t j = 0; j < avatar->getHand().getNumPalms(); j++) { - PalmData& otherPalm = avatar->getHand().getPalms()[j]; - if (!otherPalm.isActive()) { - continue; - } - glm::vec3 otherPalmPosition = otherPalm.getPosition(); - if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) { - palm.setIsCollidingWithPalm(true); - if (!wasColliding) { - const float PALM_COLLIDE_VOLUME = 1.f; - const float PALM_COLLIDE_FREQUENCY = 1000.f; - const float PALM_COLLIDE_DURATION_MAX = 0.75f; - const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f; - Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME, - PALM_COLLIDE_FREQUENCY, - PALM_COLLIDE_DURATION_MAX, - PALM_COLLIDE_DECAY_PER_SAMPLE); - // If the other person's palm is in motion, move mine downward to show I was hit - const float MIN_VELOCITY_FOR_SLAP = 0.05f; - if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) { - // add slapback here + if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithAvatars)) { + // check other avatars + foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) { + Avatar* avatar = static_cast(avatarPointer.data()); + if (avatar == _owningAvatar) { + // don't collid with our own hands + continue; + } + if (Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) { + // Check for palm collisions + glm::vec3 myPalmPosition = palm.getPosition(); + float palmCollisionDistance = 0.1f; + bool wasColliding = palm.getIsCollidingWithPalm(); + palm.setIsCollidingWithPalm(false); + // If 'Play Slaps' is enabled, look for palm-to-palm collisions and make sound + for (size_t j = 0; j < avatar->getHand().getNumPalms(); j++) { + PalmData& otherPalm = avatar->getHand().getPalms()[j]; + if (!otherPalm.isActive()) { + continue; + } + glm::vec3 otherPalmPosition = otherPalm.getPosition(); + if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) { + palm.setIsCollidingWithPalm(true); + if (!wasColliding) { + const float PALM_COLLIDE_VOLUME = 1.f; + const float PALM_COLLIDE_FREQUENCY = 1000.f; + const float PALM_COLLIDE_DURATION_MAX = 0.75f; + const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f; + Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME, + PALM_COLLIDE_FREQUENCY, + PALM_COLLIDE_DURATION_MAX, + PALM_COLLIDE_DECAY_PER_SAMPLE); + // If the other person's palm is in motion, move mine downward to show I was hit + const float MIN_VELOCITY_FOR_SLAP = 0.05f; + if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) { + // add slapback here + } } } - - } } - } - glm::vec3 avatarPenetration; - if (avatar->findSpherePenetration(palm.getPosition(), scaledPalmRadius, avatarPenetration)) { - totalPenetration = addPenetrations(totalPenetration, avatarPenetration); - // Check for collisions with the other avatar's leap palms + if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions)) { + for (size_t j = 0; j < collisions.size(); ++j) { + if (!avatar->poke(collisions[j])) { + totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration); + } + } + } } } if (Menu::getInstance()->isOptionChecked(MenuOption::HandsCollideWithSelf)) { // and the current avatar (ignoring everything below the parent of the parent of the last free joint) - glm::vec3 owningPenetration; + collisions.clear(); const Model& skeletonModel = _owningAvatar->getSkeletonModel(); int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex( skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() : (i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1))); - if (_owningAvatar->findSpherePenetration(palm.getPosition(), scaledPalmRadius, owningPenetration, skipIndex)) { - totalPenetration = addPenetrations(totalPenetration, owningPenetration); + if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions, skipIndex)) { + for (size_t j = 0; j < collisions.size(); ++j) { + totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration); + } } } // un-penetrate palm.addToPosition(-totalPenetration); + + // we recycle the collisions container, so we clear it for the next loop + collisions.clear(); } } @@ -278,14 +284,14 @@ void Hand::calculateGeometry() { FingerData& finger = palm.getFingers()[f]; if (finger.isActive()) { const float standardBallRadius = FINGERTIP_COLLISION_RADIUS; - _leapFingerTipBalls.resize(_leapFingerTipBalls.size() + 1); - HandBall& ball = _leapFingerTipBalls.back(); + HandBall ball; ball.rotation = getBaseOrientation(); ball.position = finger.getTipPosition(); ball.radius = standardBallRadius; ball.touchForce = 0.0; ball.isCollidable = true; ball.isColliding = false; + _leapFingerTipBalls.push_back(ball); } } } @@ -300,14 +306,14 @@ void Hand::calculateGeometry() { FingerData& finger = palm.getFingers()[f]; if (finger.isActive()) { const float standardBallRadius = 0.005f; - _leapFingerRootBalls.resize(_leapFingerRootBalls.size() + 1); - HandBall& ball = _leapFingerRootBalls.back(); + HandBall ball; ball.rotation = getBaseOrientation(); ball.position = finger.getRootPosition(); ball.radius = standardBallRadius; ball.touchForce = 0.0; ball.isCollidable = true; ball.isColliding = false; + _leapFingerRootBalls.push_back(ball); } } } @@ -473,8 +479,3 @@ void Hand::setLeapHands(const std::vector& handPositions, } } - - - - - diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 98eb9a4431..5628740770 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -209,21 +209,26 @@ void MyAvatar::simulate(float deltaTime) { _velocity += _scale * _gravity * (GRAVITY_EARTH * deltaTime); } - // Only collide if we are not moving to a target - if (_isCollisionsOn && (glm::length(_moveTarget) < EPSILON)) { - + if (_collisionFlags != 0) { Camera* myCamera = Application::getInstance()->getCamera(); + float radius = getHeight() * COLLISION_RADIUS_SCALE; if (myCamera->getMode() == CAMERA_MODE_FIRST_PERSON && !OculusManager::isConnected()) { - _collisionRadius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.f)); - _collisionRadius *= COLLISION_RADIUS_SCALAR; - } else { - _collisionRadius = getHeight() * COLLISION_RADIUS_SCALE; + radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.f)); + radius *= COLLISION_RADIUS_SCALAR; } - updateCollisionWithEnvironment(deltaTime); - updateCollisionWithVoxels(deltaTime); - updateAvatarCollisions(deltaTime); + if (_collisionFlags & COLLISION_GROUP_ENVIRONMENT) { + updateCollisionWithEnvironment(deltaTime, radius); + } + if (_collisionFlags & COLLISION_GROUP_VOXELS) { + updateCollisionWithVoxels(deltaTime, radius); + } + if (_collisionFlags & COLLISION_GROUP_AVATARS) { + // Note, hand-vs-avatar collisions are done elsewhere + // This is where we avatar-vs-avatar bounding capsule + updateCollisionWithAvatars(deltaTime); + } } // add thrust to velocity @@ -476,7 +481,12 @@ void MyAvatar::renderDebugBodyPoints() { void MyAvatar::render(bool forceRenderHead) { // render body - renderBody(forceRenderHead); + if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) { + _skeletonModel.renderCollisionProxies(1.f); + } + if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { + renderBody(forceRenderHead); + } //renderDebugBodyPoints(); @@ -878,10 +888,9 @@ void MyAvatar::updateHandMovementAndTouching(float deltaTime) { } } -void MyAvatar::updateCollisionWithEnvironment(float deltaTime) { +void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) { glm::vec3 up = getBodyUpDirection(); - float radius = _collisionRadius; - const float ENVIRONMENT_SURFACE_ELASTICITY = 1.0f; + const float ENVIRONMENT_SURFACE_ELASTICITY = 0.0f; const float ENVIRONMENT_SURFACE_DAMPING = 0.01f; const float ENVIRONMENT_COLLISION_FREQUENCY = 0.05f; glm::vec3 penetration; @@ -896,8 +905,7 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime) { } -void MyAvatar::updateCollisionWithVoxels(float deltaTime) { - float radius = _collisionRadius; +void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { const float VOXEL_ELASTICITY = 0.4f; const float VOXEL_DAMPING = 0.0f; const float VOXEL_COLLISION_FREQUENCY = 0.5f; @@ -917,8 +925,8 @@ void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity // Update the avatar in response to a hard collision. Position will be reset exactly // to outside the colliding surface. Velocity will be modified according to elasticity. // - // if elasticity = 1.0, collision is inelastic. - // if elasticity > 1.0, collision is elastic. + // if elasticity = 0.0, collision is 100% inelastic. + // if elasticity = 1.0, collision is elastic. // _position -= penetration; static float HALTING_VELOCITY = 0.2f; @@ -927,7 +935,7 @@ void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity if (penetrationLength > EPSILON) { _elapsedTimeSinceCollision = 0.0f; glm::vec3 direction = penetration / penetrationLength; - _velocity -= glm::dot(_velocity, direction) * direction * elasticity; + _velocity -= glm::dot(_velocity, direction) * direction * (1.f + elasticity); _velocity *= glm::clamp(1.f - damping, 0.0f, 1.0f); if ((glm::length(_velocity) < HALTING_VELOCITY) && (glm::length(_thrust) == 0.f)) { // If moving really slowly after a collision, and not applying forces, stop altogether @@ -966,11 +974,34 @@ void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTim } } -void MyAvatar::updateAvatarCollisions(float deltaTime) { +const float DEFAULT_HAND_RADIUS = 0.1f; +void MyAvatar::updateCollisionWithAvatars(float deltaTime) { // Reset detector for nearest avatar _distanceToNearestAvatar = std::numeric_limits::max(); - // loop through all the other avatars for potential interactions + const AvatarHash& avatars = Application::getInstance()->getAvatarManager().getAvatarHash(); + if (avatars.size() <= 1) { + // no need to compute a bunch of stuff if we have one or fewer avatars + return; + } + float myRadius = getHeight(); + + CollisionInfo collisionInfo; + foreach (const AvatarSharedPointer& avatarPointer, avatars) { + Avatar* avatar = static_cast(avatarPointer.data()); + if (static_cast(this) == avatar) { + // don't collide with ourselves + continue; + } + float distance = glm::length(_position - avatar->getPosition()); + if (_distanceToNearestAvatar > distance) { + _distanceToNearestAvatar = distance; + } + float theirRadius = avatar->getHeight(); + if (distance < myRadius + theirRadius) { + // TODO: Andrew to make avatar-avatar capsule collisions work here + } + } } class SortedAvatar { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 7dfb8812dd..b912f6b0a7 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -113,7 +113,6 @@ private: bool _isCollisionsOn; bool _isThrustOn; float _thrustMultiplier; - float _collisionRadius; glm::vec3 _moveTarget; int _moveTargetStepCounter; QWeakPointer _lookAtTargetAvatar; @@ -126,9 +125,9 @@ private: void renderBody(bool forceRenderHead); void updateThrust(float deltaTime); void updateHandMovementAndTouching(float deltaTime); - void updateAvatarCollisions(float deltaTime); - void updateCollisionWithEnvironment(float deltaTime); - void updateCollisionWithVoxels(float deltaTime); + void updateCollisionWithAvatars(float deltaTime); + void updateCollisionWithEnvironment(float deltaTime, float radius); + void updateCollisionWithVoxels(float deltaTime, float radius); void applyHardCollision(const glm::vec3& penetration, float elasticity, float damping); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void updateChatCircle(float deltaTime); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 60ba470c5b..ac08c52b49 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -70,10 +70,6 @@ bool SkeletonModel::render(float alpha) { Model::render(alpha); - if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) { - renderCollisionProxies(alpha); - } - return true; } diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index af1eafc85b..79feb5eb3f 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -98,15 +98,6 @@ void SixenseManager::update(float deltaTime) { // Compute current velocity from position change glm::vec3 rawVelocity = (position - palm->getRawPosition()) / deltaTime / 1000.f; palm->setRawVelocity(rawVelocity); // meters/sec - /* - if (i == 0) - { - printf("ADEBUG rawVelocity = [%e, %e, %e]\n", - rawVelocity.x, - rawVelocity.y, - rawVelocity.z); - } - */ palm->setRawPosition(position); // use the velocity to determine whether there's any movement (if the hand isn't new) diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index e4dfd7dd84..237ba7196d 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -1265,7 +1265,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) const glm::mat4& transform = geometry.joints.at(geometry.neckJointIndex).transform; geometry.neckPivot = glm::vec3(transform[3][0], transform[3][1], transform[3][2]); } - + geometry.bindExtents.minimum = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX); geometry.bindExtents.maximum = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 6d61b2df68..e1652b1237 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -437,11 +437,11 @@ bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct return false; } -bool Model::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, - glm::vec3& penetration, float boneScale, int skipIndex) const { +bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius, + ModelCollisionInfo& collisionInfo, float boneScale, int skipIndex) const { + int jointIndex = -1; const glm::vec3 relativeCenter = penetratorCenter - _translation; const FBXGeometry& geometry = _geometry->getFBXGeometry(); - bool didPenetrate = false; glm::vec3 totalPenetration; float radiusScale = extractUniformScale(_scale) * boneScale; for (int i = 0; i < _jointStates.size(); i++) { @@ -468,12 +468,16 @@ bool Model::findSpherePenetration(const glm::vec3& penetratorCenter, float penet if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end, startRadius, endRadius, bonePenetration)) { totalPenetration = addPenetrations(totalPenetration, bonePenetration); - didPenetrate = true; + // TODO: Andrew to try to keep the joint furthest toward the root + jointIndex = i; } outerContinue: ; } - if (didPenetrate) { - penetration = totalPenetration; + 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; @@ -548,6 +552,9 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int last glm::vec3 relativePosition = position - _translation; const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; + if (freeLineage.isEmpty()) { + return false; + } if (lastFreeIndex == -1) { lastFreeIndex = freeLineage.last(); } @@ -706,6 +713,37 @@ void Model::renderCollisionProxies(float alpha) { glPopMatrix(); } +bool Model::poke(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. + glm::vec3 jointPosition(0.f); + if (getJointPosition(collision._jointIndex, jointPosition)) { + int jointIndex = collision._jointIndex; + const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex]; + if (joint.parentIndex != -1) { + // compute the approximate distance (travel) that the joint needs to move + glm::vec3 start; + getJointPosition(joint.parentIndex, start); + glm::vec3 contactPoint = collision._contactPoint - start; + glm::vec3 penetrationEnd = contactPoint + collision._penetration; + glm::vec3 axis = glm::cross(contactPoint, penetrationEnd); + float travel = glm::length(axis); + const float MIN_TRAVEL = 1.0e-8f; + if (travel > MIN_TRAVEL) { + // compute the new position of the joint + float angle = asinf(travel / (glm::length(contactPoint) * glm::length(penetrationEnd))); + axis = glm::normalize(axis); + glm::vec3 end; + getJointPosition(jointIndex, end); + glm::vec3 newEnd = start + glm::angleAxis(glm::degrees(angle), axis) * (end - start); + // try to move it + return setJointPosition(jointIndex, newEnd, -1, true); + } + } + } + return false; +} + void Model::deleteGeometry() { foreach (Model* attachment, _attachments) { delete attachment; diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 389020d1b1..f99e46c5f4 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -17,6 +17,16 @@ #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 @@ -149,8 +159,14 @@ public: bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; - bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, - glm::vec3& penetration, float boneScale = 1.0f, int skipIndex = -1) const; + bool findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius, + ModelCollisionInfo& collision, float boneScale = 1.0f, int skipIndex = -1) const; + + void renderCollisionProxies(float alpha); + + /// \param collisionInfo info about the collision + /// \return true if collision affects the Model + bool poke(ModelCollisionInfo& collisionInfo); protected: @@ -209,8 +225,6 @@ protected: void applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain = true); - void renderCollisionProxies(float alpha); - private: void deleteGeometry(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 8981f78023..c375f8b82d 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -63,7 +63,7 @@ QByteArray AvatarData::toByteArray() { if (!_headData) { _headData = new HeadData(this); } - // lazily allocate memory for HeadData in case we're not an Avatar instance + // lazily allocate memory for HandData in case we're not an Avatar instance if (!_handData) { _handData = new HandData(this); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 43db344899..46d92c0f2e 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -134,16 +134,11 @@ public: virtual const glm::vec3& getVelocity() const { return vec3Zero; } - /// 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 penetration[out] the vector in which to store the penetration - /// \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 - virtual bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, - glm::vec3& penetration, int skeletonSkipIndex = -1) const { return false; } + virtual bool findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) { + return false; + } - virtual bool findSphereCollision(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) { + virtual bool findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) { return false; } diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index c718ab3ddc..bb0260c2bf 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -139,18 +139,22 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA) // handle A particle particleA->setVelocity(particleA->getVelocity() - axialVelocity * (2.0f * massB / totalMass)); + particleA->setPosition(particleA->getPosition() - 0.5f * penetration); ParticleProperties propertiesA; ParticleID particleAid(particleA->getID()); propertiesA.copyFromParticle(*particleA); propertiesA.setVelocity(particleA->getVelocity() * (float)TREE_SCALE); + propertiesA.setPosition(particleA->getPosition() * (float)TREE_SCALE); _packetSender->queueParticleEditMessage(PacketTypeParticleAddOrEdit, particleAid, propertiesA); // handle B particle particleB->setVelocity(particleB->getVelocity() + axialVelocity * (2.0f * massA / totalMass)); + particleA->setPosition(particleB->getPosition() + 0.5f * penetration); ParticleProperties propertiesB; ParticleID particleBid(particleB->getID()); propertiesB.copyFromParticle(*particleB); propertiesB.setVelocity(particleB->getVelocity() * (float)TREE_SCALE); + propertiesB.setPosition(particleB->getPosition() * (float)TREE_SCALE); _packetSender->queueParticleEditMessage(PacketTypeParticleAddOrEdit, particleBid, propertiesB); _packetSender->releaseQueuedMessages(); @@ -182,7 +186,9 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { CollisionInfo collisionInfo; collisionInfo._damping = DAMPING; collisionInfo._elasticity = ELASTICITY; - if (avatar->findSphereCollision(center, radius, collisionInfo)) { + 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) { diff --git a/libraries/shared/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h index 38ae64e30c..1fa95cd83a 100644 --- a/libraries/shared/src/CollisionInfo.h +++ b/libraries/shared/src/CollisionInfo.h @@ -11,22 +11,28 @@ #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; + class CollisionInfo { public: CollisionInfo() : _damping(0.f), _elasticity(1.f), + _contactPoint(0.f), _penetration(0.f), - _addedVelocity(0.f) { - } + _addedVelocity(0.f) { + } ~CollisionInfo() {} - //glm::vec3 _point; //glm::vec3 _normal; float _damping; float _elasticity; - glm::vec3 _penetration; // depth that bodyA is penetrates bodyB + 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; };