Merge pull request #1965 from AndrewMeadows/avatar-interaction

Avatar interaction first pass
This commit is contained in:
Philip Rosedale 2014-02-10 17:36:54 -08:00
commit 0527cf886e
18 changed files with 287 additions and 157 deletions

View file

@ -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");

View file

@ -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";

View file

@ -15,7 +15,6 @@
#include <glm/gtc/noise.hpp>
#include <glm/gtx/quaternion.hpp>
#include <AvatarData.h>
#include <SharedUtil.h>
#include "InterfaceConfig.h"

View file

@ -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;
}

View file

@ -57,7 +57,7 @@ enum ScreenTintLayer {
NUM_SCREEN_TINT_LAYERS
};
class MyAvatar;
typedef QVector<ModelCollisionInfo> 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<Node> _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;

View file

@ -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);

View file

@ -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<Avatar*>(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<Avatar*>(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<glm::vec3>& handPositions,
}
}

View file

@ -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<float>::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<Avatar*>(avatarPointer.data());
if (static_cast<Avatar*>(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 {

View file

@ -113,7 +113,6 @@ private:
bool _isCollisionsOn;
bool _isThrustOn;
float _thrustMultiplier;
float _collisionRadius;
glm::vec3 _moveTarget;
int _moveTargetStepCounter;
QWeakPointer<AvatarData> _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);

View file

@ -70,10 +70,6 @@ bool SkeletonModel::render(float alpha) {
Model::render(alpha);
if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) {
renderCollisionProxies(alpha);
}
return true;
}

View file

@ -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)

View file

@ -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);

View file

@ -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<int>& 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;

View file

@ -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();

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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) {

View file

@ -11,22 +11,28 @@
#include <glm/glm.hpp>
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;
};