Merge branch 'master' of https://github.com/highfidelity/hifi into sit_on_a_model

This commit is contained in:
Atlante45 2014-06-25 10:49:51 -07:00
commit c660986480
54 changed files with 2699 additions and 1134 deletions

View file

@ -3189,7 +3189,7 @@ void Application::updateWindowTitle(){
float creditBalance = accountManager.getAccountInfo().getBalance() / SATOSHIS_PER_CREDIT;
QString creditBalanceString;
creditBalanceString.sprintf("%.8f", floor(creditBalance + 0.5));
creditBalanceString.sprintf("%.8f", creditBalance);
title += " - ₵" + creditBalanceString;
}

View file

@ -375,7 +375,7 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollideAsRagDoll);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollideAsRagdoll);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu,

View file

@ -322,7 +322,7 @@ namespace MenuOption {
const QString CascadedShadows = "Cascaded";
const QString Chat = "Chat...";
const QString ChatCircling = "Chat Circling";
const QString CollideAsRagDoll = "Collide As RagDoll";
const QString CollideAsRagdoll = "Collide As Ragdoll";
const QString CollideWithAvatars = "Collide With Avatars";
const QString CollideWithEnvironment = "Collide With World Boundaries";
const QString CollideWithParticles = "Collide With Particles";

View file

@ -218,9 +218,6 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes);
bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes);
bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
if (renderSkeleton || renderHead || renderBounding) {
updateShapePositions();
}
if (renderSkeleton) {
_skeletonModel.renderJointCollisionShapes(0.7f);
@ -230,7 +227,6 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
getHead()->getFaceModel().renderJointCollisionShapes(0.7f);
}
if (renderBounding && shouldRenderHead(cameraPosition, renderMode)) {
getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f);
_skeletonModel.renderBoundingCollisionShapes(0.7f);
}
@ -579,10 +575,9 @@ bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
return false;
}
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
CollisionList& collisions, int skeletonSkipIndex) {
return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions, skeletonSkipIndex);
// Temporarily disabling collisions against the head because most of its collision proxies are bad.
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions) {
return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
// TODO: Andrew to fix: Temporarily disabling collisions against the head
//return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
}
@ -591,18 +586,6 @@ bool Avatar::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisio
getHead()->getFaceModel().findPlaneCollisions(plane, collisions);
}
void Avatar::updateShapePositions() {
_skeletonModel.updateShapePositions();
Model& headModel = getHead()->getFaceModel();
headModel.updateShapePositions();
/* KEEP FOR DEBUG: use this in rather than code above to see shapes
* in their default positions where the bounding shape is computed.
_skeletonModel.resetShapePositions();
Model& headModel = getHead()->getFaceModel();
headModel.resetShapePositions();
*/
}
bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions) {
// TODO: Andrew to fix: also collide against _skeleton
//bool collided = _skeletonModel.findCollisions(shapes, collisions);
@ -613,69 +596,6 @@ bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList&
return collided;
}
bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
if (_collisionGroups & 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++) {
const PalmData* palm = handData->getPalm(i);
if (palm && palm->hasPaddle()) {
// create a disk collision proxy where the hand is
int jointIndex = -1;
glm::vec3 handPosition;
if (i == 0) {
_skeletonModel.getLeftHandPosition(handPosition);
jointIndex = _skeletonModel.getLeftHandJointIndex();
}
else {
_skeletonModel.getRightHandPosition(handPosition);
jointIndex = _skeletonModel.getRightHandJointIndex();
}
glm::vec3 fingerAxis = palm->getFingerDirection();
glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis;
glm::vec3 diskNormal = palm->getNormal();
const float DISK_THICKNESS = 0.08f;
// collide against the disk
glm::vec3 penetration;
if (findSphereDiskPenetration(particleCenter, particleRadius,
diskCenter, HAND_PADDLE_RADIUS, DISK_THICKNESS, diskNormal,
penetration)) {
CollisionInfo* collision = collisions.getNewCollision();
if (collision) {
collision->_type = COLLISION_TYPE_PADDLE_HAND;
collision->_intData = jointIndex;
collision->_penetration = penetration;
collision->_addedVelocity = palm->getVelocity();
collided = true;
} else {
// collisions are full, so we might as well bail now
return collided;
}
}
}
}
}
// then collide against the models
int preNumCollisions = collisions.size();
if (_skeletonModel.findSphereCollisions(particleCenter, particleRadius, collisions)) {
// the Model doesn't have velocity info, so we have to set it for each new collision
int postNumCollisions = collisions.size();
for (int i = preNumCollisions; i < postNumCollisions; ++i) {
CollisionInfo* collision = collisions.getCollision(i);
collision->_penetration /= (float)(TREE_SCALE);
collision->_addedVelocity = getVelocity();
}
collided = true;
}
return collided;
}
glm::quat Avatar::getJointRotation(int index) const {
if (QThread::currentThread() != thread()) {
return AvatarData::getJointRotation(index);
@ -909,25 +829,6 @@ float Avatar::getHeadHeight() const {
return DEFAULT_HEAD_HEIGHT;
}
bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
if (!collision._data || collision._type != COLLISION_TYPE_MODEL) {
return false;
}
Model* model = static_cast<Model*>(collision._data);
int jointIndex = collision._intData;
if (model == &(_skeletonModel) && jointIndex != -1) {
// collision response of skeleton is temporarily disabled
return false;
//return _skeletonModel.collisionHitsMoveableJoint(collision);
}
if (model == &(getHead()->getFaceModel())) {
// ATM we always handle COLLISION_TYPE_MODEL against the face.
return true;
}
return false;
}
float Avatar::getBoundingRadius() const {
// TODO: also use head model when computing the avatar's bounding radius
return _skeletonModel.getBoundingRadius();

View file

@ -101,14 +101,12 @@ public:
/// \return true if at least one shape collided with avatar
bool findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions);
/// Checks for penetration between the described sphere and the avatar.
/// Checks for penetration between the a sphere and the avatar's models.
/// \param penetratorCenter the center of the penetration test sphere
/// \param penetratorRadius the radius of the penetration test sphere
/// \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,
CollisionList& collisions, int skeletonSkipIndex = -1);
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions);
/// Checks for penetration between the described plane and the avatar.
/// \param plane the penetration plane
@ -116,13 +114,6 @@ public:
/// \return whether or not the plane penetrated
bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
/// 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);
virtual bool isMyAvatar() { return false; }
virtual glm::quat getJointRotation(int index) const;
@ -141,14 +132,10 @@ public:
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
/// \return true if we expect the avatar would move as a result of the collision
bool collisionWouldMoveAvatar(CollisionInfo& collision) const;
virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { }
/// \return bounding radius of avatar
virtual float getBoundingRadius() const;
void updateShapePositions();
quint32 getCollisionGroups() const { return _collisionGroups; }
virtual void setCollisionGroups(quint32 collisionGroups) { _collisionGroups = (collisionGroups & VALID_COLLISION_GROUPS); }

View file

@ -94,39 +94,6 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
}
}
void Hand::collideAgainstOurself() {
if (!Menu::getInstance()->isOptionChecked(MenuOption::HandsCollideWithSelf)) {
return;
}
int leftPalmIndex, rightPalmIndex;
getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
const SkeletonModel& skeletonModel = _owningAvatar->getSkeletonModel();
for (int i = 0; i < int(getNumPalms()); i++) {
PalmData& palm = getPalms()[i];
if (!palm.isActive()) {
continue;
}
// ignoring everything below the parent of the parent of the last free joint
int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex(
skeletonModel.getLastFreeJointIndex((int(i) == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() :
(int(i) == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1)));
handCollisions.clear();
if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions, skipIndex)) {
glm::vec3 totalPenetration;
for (int j = 0; j < handCollisions.size(); ++j) {
CollisionInfo* collision = handCollisions.getCollision(j);
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
}
// resolve penetration
palm.addToPenetration(totalPenetration);
}
}
}
void Hand::resolvePenetrations() {
for (size_t i = 0; i < getNumPalms(); ++i) {
PalmData& palm = getPalms()[i];

View file

@ -54,7 +54,6 @@ public:
void render(bool isMine, Model::RenderMode renderMode = Model::DEFAULT_RENDER_MODE);
void collideAgainstAvatar(Avatar* avatar, bool isMyHand);
void collideAgainstOurself();
void resolvePenetrations();

View file

@ -76,14 +76,21 @@ MyAvatar::MyAvatar() :
_lastFloorContactPoint(0.0f),
_lookAtTargetAvatar(),
_shouldRender(true),
_billboardValid(false)
_billboardValid(false),
_physicsSimulation()
{
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
_driveKeys[i] = 0.0f;
}
_skeletonModel.setEnableShapes(true);
// The skeleton is both a PhysicsEntity and Ragdoll, so we add it to the simulation once for each type.
_physicsSimulation.addEntity(&_skeletonModel);
_physicsSimulation.addRagdoll(&_skeletonModel);
}
MyAvatar::~MyAvatar() {
_physicsSimulation.removeEntity(&_skeletonModel);
_physicsSimulation.removeRagdoll(&_skeletonModel);
_lookAtTargetAvatar.clear();
}
@ -154,7 +161,6 @@ void MyAvatar::simulate(float deltaTime) {
{
PerformanceTimer perfTimer("MyAvatar::simulate/hand Collision,simulate");
// update avatar skeleton and simulate hand and head
getHand()->collideAgainstOurself();
getHand()->simulate(deltaTime, true);
}
@ -189,6 +195,18 @@ void MyAvatar::simulate(float deltaTime) {
head->simulate(deltaTime, true);
}
{
PerformanceTimer perfTimer("MyAvatar::simulate/ragdoll");
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) {
const int minError = 0.01f;
const float maxIterations = 10;
const quint64 maxUsec = 2000;
_physicsSimulation.stepForward(deltaTime, minError, maxIterations, maxUsec);
} else {
_skeletonModel.moveShapesTowardJoints(1.0f);
}
}
// now that we're done stepping the avatar forward in time, compute new collisions
if (_collisionGroups != 0) {
PerformanceTimer perfTimer("MyAvatar::simulate/_collisionGroups");
@ -199,7 +217,6 @@ void MyAvatar::simulate(float deltaTime) {
radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.0f));
radius *= COLLISION_RADIUS_SCALAR;
}
updateShapePositions();
if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) {
PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithEnvironment");
updateCollisionWithEnvironment(deltaTime, radius);
@ -210,10 +227,12 @@ void MyAvatar::simulate(float deltaTime) {
} else {
_trapDuration = 0.0f;
}
/* TODO: Andrew to make this work
if (_collisionGroups & COLLISION_GROUP_AVATARS) {
PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithAvatars");
updateCollisionWithAvatars(deltaTime);
}
*/
}
// consider updating our billboard
@ -1233,7 +1252,7 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
float capsuleHalfHeight = boundingShape.getHalfHeight();
const float MAX_STEP_HEIGHT = capsuleRadius + capsuleHalfHeight;
const float MIN_STEP_HEIGHT = 0.0f;
glm::vec3 footBase = boundingShape.getPosition() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection;
glm::vec3 footBase = boundingShape.getTranslation() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection;
float highestStep = 0.0f;
float lowestStep = MAX_STEP_HEIGHT;
glm::vec3 floorPoint;
@ -1250,7 +1269,7 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
if (horizontalDepth > capsuleRadius || fabsf(verticalDepth) > MAX_STEP_HEIGHT) {
isTrapped = true;
if (_trapDuration > MAX_TRAP_PERIOD) {
float distance = glm::dot(boundingShape.getPosition() - cubeCenter, _worldUpDirection);
float distance = glm::dot(boundingShape.getTranslation() - cubeCenter, _worldUpDirection);
if (distance < 0.0f) {
distance = fabsf(distance) + 0.5f * cubeSide;
}
@ -1435,7 +1454,6 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
// don't collide with ourselves
continue;
}
avatar->updateShapePositions();
float distance = glm::length(_position - avatar->getPosition());
if (_distanceToNearestAvatar > distance) {
_distanceToNearestAvatar = distance;
@ -1461,17 +1479,10 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
}
}
// collide our hands against them
// TODO: make this work when we can figure out when the other avatar won't yeild
// (for example, we're colliding against their chest or leg)
//getHand()->collideAgainstAvatar(avatar, true);
// collide their hands against us
avatar->getHand()->collideAgainstAvatar(this, false);
}
}
// TODO: uncomment this when we handle collisions that won't affect other avatar
//getHand()->resolvePenetrations();
}
class SortedAvatar {

View file

@ -14,6 +14,8 @@
#include <QSettings>
#include <PhysicsSimulation.h>
#include "Avatar.h"
enum AvatarHandState
@ -173,6 +175,7 @@ private:
float _oculusYawOffset;
QList<AnimationHandlePointer> _animationHandles;
PhysicsSimulation _physicsSimulation;
// private methods
float computeDistanceToFloor(const glm::vec3& startPoint);

View file

@ -11,21 +11,37 @@
#include <glm/gtx/transform.hpp>
#include <VerletCapsuleShape.h>
#include <VerletSphereShape.h>
#include "Application.h"
#include "Avatar.h"
#include "Hand.h"
#include "Menu.h"
#include "SkeletonModel.h"
SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
_owningAvatar(owningAvatar) {
SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
Model(parent),
Ragdoll(),
_owningAvatar(owningAvatar),
_boundingShape(),
_boundingShapeLocalOffset(0.0f) {
}
void SkeletonModel::setJointStates(QVector<JointState> states) {
Model::setJointStates(states);
if (isActive() && _owningAvatar->isMyAvatar()) {
_ragDoll.init(_jointStates);
// the SkeletonModel override of updateJointState() will clear the translation part
// of its root joint and we need that done before we try to build shapes hence we
// recompute all joint transforms at this time.
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i);
}
clearShapes();
clearRagdollConstraintsAndPoints();
if (_enableShapes) {
buildShapes();
}
}
@ -86,25 +102,13 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
applyPalmData(geometry.leftHandJointIndex, hand->getPalms()[leftPalmIndex]);
applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[rightPalmIndex]);
}
simulateRagDoll(deltaTime);
}
void SkeletonModel::simulateRagDoll(float deltaTime) {
_ragDoll.slaveToSkeleton(_jointStates, 0.1f); // fraction = 0.1f left intentionally low for demo purposes
float MIN_CONSTRAINT_ERROR = 0.005f; // 5mm
int MAX_ITERATIONS = 4;
int iterations = 0;
float delta = 0.0f;
do {
delta = _ragDoll.enforceConstraints();
++iterations;
} while (delta > MIN_CONSTRAINT_ERROR && iterations < MAX_ITERATIONS);
_boundingShape.setTranslation(_translation + _rotation * _boundingShapeLocalOffset);
_boundingShape.setRotation(_rotation);
}
void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const {
if (jointIndex < 0 || jointIndex >= int(_jointShapes.size())) {
if (jointIndex < 0 || jointIndex >= int(_shapes.size())) {
return;
}
if (jointIndex == getLeftHandJointIndex()
@ -114,18 +118,21 @@ void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes)
for (int i = 0; i < _jointStates.size(); i++) {
const FBXJoint& joint = geometry.joints[i];
int parentIndex = joint.parentIndex;
Shape* shape = _shapes[i];
if (i == jointIndex) {
// this shape is the hand
shapes.push_back(_jointShapes[i]);
if (parentIndex != -1) {
// also add the forearm
shapes.push_back(_jointShapes[parentIndex]);
if (shape) {
shapes.push_back(shape);
}
} else {
if (parentIndex != -1 && _shapes[parentIndex]) {
// also add the forearm
shapes.push_back(_shapes[parentIndex]);
}
} else if (shape) {
while (parentIndex != -1) {
if (parentIndex == jointIndex) {
// this shape is a child of the hand
shapes.push_back(_jointShapes[i]);
shapes.push_back(shape);
break;
}
parentIndex = geometry.joints[parentIndex].parentIndex;
@ -145,7 +152,7 @@ void SkeletonModel::renderIKConstraints() {
renderJointConstraints(getRightHandJointIndex());
renderJointConstraints(getLeftHandJointIndex());
//if (isActive() && _owningAvatar->isMyAvatar()) {
// renderRagDoll();
// renderRagdoll();
//}
}
@ -244,15 +251,6 @@ void SkeletonModel::updateJointState(int index) {
}
}
void SkeletonModel::updateShapePositions() {
if (isActive() && _owningAvatar->isMyAvatar() &&
Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagDoll)) {
_ragDoll.updateShapes(_jointShapes, _rotation, _translation);
} else {
Model::updateShapePositions();
}
}
void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
if (!_owningAvatar->isMyAvatar() || Application::getInstance()->getPrioVR()->isActive()) {
return;
@ -478,22 +476,21 @@ bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& seco
return false;
}
void SkeletonModel::renderRagDoll() {
void SkeletonModel::renderRagdoll() {
const int BALL_SUBDIVISIONS = 6;
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glPushMatrix();
Application::getInstance()->loadTranslatedViewMatrix(_translation);
QVector<glm::vec3> points = _ragDoll.getPoints();
int numPoints = points.size();
int numPoints = _ragdollPoints.size();
float alpha = 0.3f;
float radius1 = 0.008f;
float radius2 = 0.01f;
for (int i = 0; i < numPoints; ++i) {
glPushMatrix();
// draw each point as a yellow hexagon with black border
glm::vec3 position = _rotation * points[i];
glm::vec3 position = _rotation * _ragdollPoints[i]._position;
glTranslatef(position.x, position.y, position.z);
glColor4f(0.0f, 0.0f, 0.0f, alpha);
glutSolidSphere(radius2, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
@ -505,3 +502,266 @@ void SkeletonModel::renderRagDoll() {
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
}
// virtual
void SkeletonModel::initRagdollPoints() {
assert(_ragdollPoints.size() == 0);
assert(_ragdollConstraints.size() == 0);
// one point for each joint
int numJoints = _jointStates.size();
_ragdollPoints.fill(VerletPoint(), numJoints);
for (int i = 0; i < numJoints; ++i) {
const JointState& state = _jointStates.at(i);
glm::vec3 position = state.getPosition();
_ragdollPoints[i]._position = position;
_ragdollPoints[i]._lastPosition = position;
}
}
void SkeletonModel::buildRagdollConstraints() {
// NOTE: the length of DistanceConstraints is computed and locked in at this time
// so make sure the ragdoll positions are in a normal configuration before here.
const int numPoints = _ragdollPoints.size();
assert(numPoints == _jointStates.size());
for (int i = 0; i < numPoints; ++i) {
const JointState& state = _jointStates.at(i);
const FBXJoint& joint = state.getFBXJoint();
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
FixedConstraint* anchor = new FixedConstraint(&(_ragdollPoints[i]), glm::vec3(0.0f));
_ragdollConstraints.push_back(anchor);
} else {
DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[i]), &(_ragdollPoints[parentIndex]));
_ragdollConstraints.push_back(bone);
}
}
}
// virtual
void SkeletonModel::stepRagdollForward(float deltaTime) {
const float RAGDOLL_FOLLOWS_JOINTS_TIMESCALE = 0.03f;
float fraction = glm::clamp(deltaTime / RAGDOLL_FOLLOWS_JOINTS_TIMESCALE, 0.0f, 1.0f);
moveShapesTowardJoints(fraction);
}
float DENSITY_OF_WATER = 1000.0f; // kg/m^3
float MIN_JOINT_MASS = 1.0f;
float VERY_BIG_MASS = 1.0e6f;
// virtual
void SkeletonModel::buildShapes() {
if (!_geometry || _rootIndex == -1) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (geometry.joints.isEmpty()) {
return;
}
initRagdollPoints();
float uniformScale = extractUniformScale(_scale);
const int numStates = _jointStates.size();
for (int i = 0; i < numStates; i++) {
JointState& state = _jointStates[i];
const FBXJoint& joint = state.getFBXJoint();
float radius = uniformScale * joint.boneRadius;
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
Shape::Type type = joint.shapeType;
if (i == 0 || (type == Shape::CAPSULE_SHAPE && halfHeight < EPSILON)) {
// this shape is forced to be a sphere
type = Shape::SPHERE_SHAPE;
}
Shape* shape = NULL;
int parentIndex = joint.parentIndex;
if (type == Shape::SPHERE_SHAPE) {
shape = new VerletSphereShape(radius, &(_ragdollPoints[i]));
shape->setEntity(this);
_ragdollPoints[i]._mass = glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume());
} else if (type == Shape::CAPSULE_SHAPE) {
assert(parentIndex != -1);
shape = new VerletCapsuleShape(radius, &(_ragdollPoints[parentIndex]), &(_ragdollPoints[i]));
shape->setEntity(this);
_ragdollPoints[i]._mass = glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume());
}
if (parentIndex != -1) {
// always disable collisions between joint and its parent
disableCollisions(i, parentIndex);
} else {
// give the base joint a very large mass since it doesn't actually move
// in the local-frame simulation (it defines the origin)
_ragdollPoints[i]._mass = VERY_BIG_MASS;
}
_shapes.push_back(shape);
}
// This method moves the shapes to their default positions in Model frame.
computeBoundingShape(geometry);
// While the shapes are in their default position we disable collisions between
// joints that are currently colliding.
disableCurrentSelfCollisions();
buildRagdollConstraints();
// ... then move shapes back to current joint positions
moveShapesTowardJoints(1.0f);
enforceRagdollConstraints();
}
void SkeletonModel::moveShapesTowardJoints(float fraction) {
const int numStates = _jointStates.size();
assert(_jointStates.size() == _ragdollPoints.size());
assert(fraction >= 0.0f && fraction <= 1.0f);
if (_ragdollPoints.size() == numStates) {
float oneMinusFraction = 1.0f - fraction;
int numJoints = _jointStates.size();
for (int i = 0; i < numJoints; ++i) {
_ragdollPoints[i]._lastPosition = _ragdollPoints[i]._position;
_ragdollPoints[i]._position = oneMinusFraction * _ragdollPoints[i]._position + fraction * _jointStates.at(i).getPosition();
}
}
}
void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
// compute default joint transforms
int numJoints = geometry.joints.size();
if (numJoints != _ragdollPoints.size()) {
return;
}
QVector<glm::mat4> transforms;
transforms.fill(glm::mat4(), numJoints);
// compute the default transforms and slam the ragdoll positions accordingly
// (which puts the shapes where we want them)
transforms[0] = _jointStates[0].getTransform();
_ragdollPoints[0]._position = extractTranslation(transforms[0]);
_ragdollPoints[0]._lastPosition = _ragdollPoints[0]._position;
for (int i = 1; i < numJoints; i++) {
const FBXJoint& joint = geometry.joints.at(i);
int parentIndex = joint.parentIndex;
assert(parentIndex != -1);
glm::quat modifiedRotation = joint.preRotation * joint.rotation * joint.postRotation;
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation)
* joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform;
// setting the ragdollPoints here slams the VerletShapes into their default positions
_ragdollPoints[i]._position = extractTranslation(transforms[i]);
_ragdollPoints[i]._lastPosition = _ragdollPoints[i]._position;
}
// compute bounding box that encloses all shapes
Extents totalExtents;
totalExtents.reset();
totalExtents.addPoint(glm::vec3(0.0f));
for (int i = 0; i < _shapes.size(); i++) {
Shape* shape = _shapes[i];
if (!shape) {
continue;
}
// TODO: skip hand and arm shapes for bounding box calculation
Extents shapeExtents;
shapeExtents.reset();
glm::vec3 localPosition = shape->getTranslation();
int type = shape->getType();
if (type == Shape::CAPSULE_SHAPE) {
// add the two furthest surface points of the capsule
CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
glm::vec3 axis;
capsule->computeNormalizedAxis(axis);
float radius = capsule->getRadius();
float halfHeight = capsule->getHalfHeight();
axis = halfHeight * axis + glm::vec3(radius);
shapeExtents.addPoint(localPosition + axis);
shapeExtents.addPoint(localPosition - axis);
totalExtents.addExtents(shapeExtents);
} else if (type == Shape::SPHERE_SHAPE) {
float radius = shape->getBoundingRadius();
glm::vec3 axis = glm::vec3(radius);
shapeExtents.addPoint(localPosition + axis);
shapeExtents.addPoint(localPosition - axis);
totalExtents.addExtents(shapeExtents);
}
}
// compute bounding shape parameters
// NOTE: we assume that the longest side of totalExtents is the yAxis...
glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum;
// ... and assume the radius is half the RMS of the X and Z sides:
float capsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
_boundingShape.setRadius(capsuleRadius);
_boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius);
_boundingShapeLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum);
_boundingRadius = 0.5f * glm::length(diagonal);
}
void SkeletonModel::resetShapePositions() {
// DEBUG method.
// Moves shapes to the joint default locations for debug visibility into
// how the bounding shape is computed.
if (!_geometry || _rootIndex == -1 || _shapes.isEmpty()) {
// geometry or joints have not yet been created
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (geometry.joints.isEmpty() || _shapes.size() != geometry.joints.size()) {
return;
}
// The shapes are moved to their default positions in computeBoundingShape().
computeBoundingShape(geometry);
// Then we move them into world frame for rendering at the Model's location.
for (int i = 0; i < _shapes.size(); i++) {
Shape* shape = _shapes[i];
if (shape) {
shape->setTranslation(_translation + _rotation * shape->getTranslation());
shape->setRotation(_rotation * shape->getRotation());
}
}
_boundingShape.setTranslation(_translation + _rotation * _boundingShapeLocalOffset);
_boundingShape.setRotation(_rotation);
}
void SkeletonModel::renderBoundingCollisionShapes(float alpha) {
const int BALL_SUBDIVISIONS = 10;
if (_shapes.isEmpty()) {
// the bounding shape has not been propery computed
// so no need to render it
return;
}
glPushMatrix();
Application::getInstance()->loadTranslatedViewMatrix(_translation);
// draw a blue sphere at the capsule endpoint
glm::vec3 endPoint;
_boundingShape.getEndPoint(endPoint);
endPoint = endPoint - _translation;
glTranslatef(endPoint.x, endPoint.y, endPoint.z);
glColor4f(0.6f, 0.6f, 0.8f, alpha);
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
// draw a yellow sphere at the capsule startpoint
glm::vec3 startPoint;
_boundingShape.getStartPoint(startPoint);
startPoint = startPoint - _translation;
glm::vec3 axis = endPoint - startPoint;
glTranslatef(-axis.x, -axis.y, -axis.z);
glColor4f(0.8f, 0.8f, 0.6f, alpha);
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
// draw a green cylinder between the two points
glm::vec3 origin(0.0f);
glColor4f(0.6f, 0.8f, 0.6f, alpha);
Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius());
glPopMatrix();
}

View file

@ -13,23 +13,23 @@
#define hifi_SkeletonModel_h
#include "renderer/Model.h"
#include "renderer/RagDoll.h"
#include <CapsuleShape.h>
#include <Ragdoll.h>
class Avatar;
/// A skeleton loaded from a model.
class SkeletonModel : public Model {
class SkeletonModel : public Model, public Ragdoll {
Q_OBJECT
public:
SkeletonModel(Avatar* owningAvatar);
SkeletonModel(Avatar* owningAvatar, QObject* parent = NULL);
void setJointStates(QVector<JointState> states);
void simulate(float deltaTime, bool fullUpdate = true);
void simulateRagDoll(float deltaTime);
void updateShapePositions();
/// \param jointIndex index of hand joint
/// \param shapes[out] list in which is stored pointers to hand shapes
@ -94,9 +94,27 @@ public:
/// \return whether or not both eye meshes were found
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
void renderRagDoll();
// virtual overrride from Ragdoll
virtual void stepRagdollForward(float deltaTime);
void moveShapesTowardJoints(float fraction);
void computeBoundingShape(const FBXGeometry& geometry);
void renderBoundingCollisionShapes(float alpha);
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
const CapsuleShape& getBoundingShape() const { return _boundingShape; }
void resetShapePositions(); // DEBUG method
void renderRagdoll();
protected:
// virtual overrrides from Ragdoll
void initRagdollPoints();
void buildRagdollConstraints();
void buildShapes();
/// \param jointIndex index of joint in model
/// \param position position of joint in model-frame
void applyHandPosition(int jointIndex, const glm::vec3& position);
@ -120,7 +138,9 @@ private:
void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation);
Avatar* _owningAvatar;
RagDoll _ragDoll;
CapsuleShape _boundingShape;
glm::vec3 _boundingShapeLocalOffset;
};
#endif // hifi_SkeletonModel_h

View file

@ -16,15 +16,15 @@
#include <glm/gtx/transform.hpp>
#include <glm/gtx/norm.hpp>
#include <CapsuleShape.h>
#include <GeometryUtil.h>
#include <PhysicsEntity.h>
#include <ShapeCollider.h>
#include <SphereShape.h>
#include "Application.h"
#include "Model.h"
#include <SphereShape.h>
#include <CapsuleShape.h>
#include <ShapeCollider.h>
using namespace std;
static int modelPointerTypeId = qRegisterMetaType<QPointer<Model> >();
@ -40,10 +40,7 @@ Model::Model(QObject* parent) :
_snapModelToCenter(false),
_snappedToCenter(false),
_rootIndex(-1),
_shapesAreDirty(true),
_boundingRadius(0.0f),
_boundingShape(),
_boundingShapeLocalOffset(0.0f),
//_enableCollisionShapes(false),
_lodDistance(0.0f),
_pupilDilation(0.0f),
_url("http://invalid.com") {
@ -129,7 +126,10 @@ void Model::setScaleInternal(const glm::vec3& scale) {
const float ONE_PERCENT = 0.01f;
if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) {
_scale = scale;
rebuildShapes();
if (_shapes.size() > 0) {
clearShapes();
buildShapes();
}
}
}
@ -174,6 +174,7 @@ QVector<JointState> Model::createJointStates(const FBXGeometry& geometry) {
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
_rootIndex = i;
// NOTE: in practice geometry.offset has a non-unity scale (rather than a translation)
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
state.computeTransform(parentTransform);
} else {
@ -551,7 +552,6 @@ bool Model::updateGeometry() {
model->setURL(attachment.url);
_attachments.append(model);
}
rebuildShapes();
needFullUpdate = true;
}
return needFullUpdate;
@ -560,6 +560,18 @@ bool Model::updateGeometry() {
// virtual
void Model::setJointStates(QVector<JointState> states) {
_jointStates = states;
// compute an approximate bounding radius for broadphase collision queries
// against PhysicsSimulation boundaries
int numJoints = _jointStates.size();
float radius = 0.0f;
for (int i = 0; i < numJoints; ++i) {
float distance = glm::length(_jointStates[i].getPosition());
if (distance > radius) {
radius = distance;
}
}
_boundingRadius = radius;
}
bool Model::render(float alpha, RenderMode mode, bool receiveShadows) {
@ -774,304 +786,13 @@ AnimationHandlePointer Model::createAnimationHandle() {
return handle;
}
void Model::clearShapes() {
for (int i = 0; i < _jointShapes.size(); ++i) {
delete _jointShapes[i];
}
_jointShapes.clear();
}
void Model::rebuildShapes() {
clearShapes();
if (!_geometry || _rootIndex == -1) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (geometry.joints.isEmpty()) {
return;
}
// We create the shapes with proper dimensions, but we set their transforms later.
float uniformScale = extractUniformScale(_scale);
for (int i = 0; i < _jointStates.size(); i++) {
const FBXJoint& joint = geometry.joints[i];
float radius = uniformScale * joint.boneRadius;
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
Shape::Type type = joint.shapeType;
if (type == Shape::CAPSULE_SHAPE && halfHeight < EPSILON) {
// this capsule is effectively a sphere
type = Shape::SPHERE_SHAPE;
}
if (type == Shape::CAPSULE_SHAPE) {
CapsuleShape* capsule = new CapsuleShape(radius, halfHeight);
_jointShapes.push_back(capsule);
} else if (type == Shape::SPHERE_SHAPE) {
SphereShape* sphere = new SphereShape(radius, glm::vec3(0.0f));
_jointShapes.push_back(sphere);
} else {
// this shape type is not handled and the joint shouldn't collide,
// however we must have a shape for each joint,
// so we make a bogus sphere with zero radius.
// TODO: implement collision groups for more control over what collides with what
SphereShape* sphere = new SphereShape(0.0f, glm::vec3(0.0f));
_jointShapes.push_back(sphere);
}
}
// This method moves the shapes to their default positions in Model frame
// which is where we compute the bounding shape's parameters.
computeBoundingShape(geometry);
// finally sync shapes to joint positions
_shapesAreDirty = true;
updateShapePositions();
}
void Model::computeBoundingShape(const FBXGeometry& geometry) {
// compute default joint transforms and rotations
// (in local frame, ignoring Model translation and rotation)
int numJoints = geometry.joints.size();
QVector<glm::mat4> transforms;
transforms.fill(glm::mat4(), numJoints);
QVector<glm::quat> finalRotations;
finalRotations.fill(glm::quat(), numJoints);
QVector<bool> shapeIsSet;
shapeIsSet.fill(false, numJoints);
int numShapesSet = 0;
int lastNumShapesSet = -1;
while (numShapesSet < numJoints && numShapesSet != lastNumShapesSet) {
lastNumShapesSet = numShapesSet;
for (int i = 0; i < numJoints; i++) {
const FBXJoint& joint = geometry.joints.at(i);
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
glm::mat4 baseTransform = glm::scale(_scale) * glm::translate(_offset);
glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
glm::mat4 rootTransform = baseTransform * geometry.offset * glm::translate(joint.translation)
* joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform;
// remove the tranlsation part before we save the root transform
transforms[i] = glm::translate(- extractTranslation(rootTransform)) * rootTransform;
finalRotations[i] = combinedRotation;
++numShapesSet;
shapeIsSet[i] = true;
} else if (shapeIsSet[parentIndex]) {
glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation)
* joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform;
finalRotations[i] = finalRotations[parentIndex] * combinedRotation;
++numShapesSet;
shapeIsSet[i] = true;
}
}
}
// sync shapes to joints
_boundingRadius = 0.0f;
float uniformScale = extractUniformScale(_scale);
for (int i = 0; i < _jointShapes.size(); i++) {
const FBXJoint& joint = geometry.joints[i];
glm::vec3 jointToShapeOffset = uniformScale * (finalRotations[i] * joint.shapePosition);
glm::vec3 localPosition = extractTranslation(transforms[i]) + jointToShapeOffset;
Shape* shape = _jointShapes[i];
shape->setPosition(localPosition);
shape->setRotation(finalRotations[i] * joint.shapeRotation);
float distance = glm::length(localPosition) + shape->getBoundingRadius();
if (distance > _boundingRadius) {
_boundingRadius = distance;
}
}
// compute bounding box
Extents totalExtents;
totalExtents.reset();
for (int i = 0; i < _jointShapes.size(); i++) {
Extents shapeExtents;
shapeExtents.reset();
Shape* shape = _jointShapes[i];
glm::vec3 localPosition = shape->getPosition();
int type = shape->getType();
if (type == Shape::CAPSULE_SHAPE) {
// add the two furthest surface points of the capsule
CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
glm::vec3 axis;
capsule->computeNormalizedAxis(axis);
float radius = capsule->getRadius();
float halfHeight = capsule->getHalfHeight();
axis = halfHeight * axis + glm::vec3(radius);
shapeExtents.addPoint(localPosition + axis);
shapeExtents.addPoint(localPosition - axis);
totalExtents.addExtents(shapeExtents);
} else if (type == Shape::SPHERE_SHAPE) {
float radius = shape->getBoundingRadius();
glm::vec3 axis = glm::vec3(radius);
shapeExtents.addPoint(localPosition + axis);
shapeExtents.addPoint(localPosition - axis);
totalExtents.addExtents(shapeExtents);
}
}
// compute bounding shape parameters
// NOTE: we assume that the longest side of totalExtents is the yAxis...
glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum;
// ... and assume the radius is half the RMS of the X and Z sides:
float capsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
_boundingShape.setRadius(capsuleRadius);
_boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius);
_boundingShapeLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum);
}
void Model::resetShapePositions() {
// DEBUG method.
// Moves shapes to the joint default locations for debug visibility into
// how the bounding shape is computed.
if (!_geometry || _rootIndex == -1) {
// geometry or joints have not yet been created
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (geometry.joints.isEmpty() || _jointShapes.size() != geometry.joints.size()) {
return;
}
// The shapes are moved to their default positions in computeBoundingShape().
computeBoundingShape(geometry);
// Then we move them into world frame for rendering at the Model's location.
for (int i = 0; i < _jointShapes.size(); i++) {
Shape* shape = _jointShapes[i];
shape->setPosition(_translation + _rotation * shape->getPosition());
shape->setRotation(_rotation * shape->getRotation());
}
_boundingShape.setPosition(_translation + _rotation * _boundingShapeLocalOffset);
_boundingShape.setRotation(_rotation);
// virtual override from PhysicsEntity
void Model::buildShapes() {
// TODO: figure out how to load/build collision shapes for general models
}
void Model::updateShapePositions() {
if (_shapesAreDirty && _jointShapes.size() == _jointStates.size()) {
glm::vec3 rootPosition(0.0f);
_boundingRadius = 0.0f;
float uniformScale = extractUniformScale(_scale);
for (int i = 0; i < _jointStates.size(); i++) {
const JointState& state = _jointStates[i];
const FBXJoint& joint = state.getFBXJoint();
// shape position and rotation need to be in world-frame
glm::quat stateRotation = state.getRotation();
glm::vec3 shapeOffset = uniformScale * (stateRotation * joint.shapePosition);
glm::vec3 worldPosition = _translation + _rotation * (state.getPosition() + shapeOffset);
Shape* shape = _jointShapes[i];
shape->setPosition(worldPosition);
shape->setRotation(_rotation * stateRotation * joint.shapeRotation);
float distance = glm::distance(worldPosition, _translation) + shape->getBoundingRadius();
if (distance > _boundingRadius) {
_boundingRadius = distance;
}
if (joint.parentIndex == -1) {
rootPosition = worldPosition;
}
}
_shapesAreDirty = false;
_boundingShape.setPosition(rootPosition + _rotation * _boundingShapeLocalOffset);
_boundingShape.setRotation(_rotation);
}
}
bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
const glm::vec3 relativeOrigin = origin - _translation;
const FBXGeometry& geometry = _geometry->getFBXGeometry();
float minDistance = FLT_MAX;
float radiusScale = extractUniformScale(_scale);
for (int i = 0; i < _jointStates.size(); i++) {
const FBXJoint& joint = geometry.joints[i];
glm::vec3 end = _translation + _rotation * _jointStates[i].getPosition();
float endRadius = joint.boneRadius * radiusScale;
glm::vec3 start = end;
float startRadius = joint.boneRadius * radiusScale;
if (joint.parentIndex != -1) {
start = _translation + _rotation * _jointStates[joint.parentIndex].getPosition();
startRadius = geometry.joints[joint.parentIndex].boneRadius * radiusScale;
}
// for now, use average of start and end radii
float capsuleDistance;
if (findRayCapsuleIntersection(relativeOrigin, direction, start, end,
(startRadius + endRadius) / 2.0f, capsuleDistance)) {
minDistance = qMin(minDistance, capsuleDistance);
}
}
if (minDistance < FLT_MAX) {
distance = minDistance;
return true;
}
return false;
}
bool Model::findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions) {
bool collided = false;
for (int i = 0; i < shapes.size(); ++i) {
const Shape* theirShape = shapes[i];
for (int j = 0; j < _jointShapes.size(); ++j) {
const Shape* ourShape = _jointShapes[j];
if (ShapeCollider::collideShapes(theirShape, ourShape, collisions)) {
collided = true;
}
}
}
return collided;
}
bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius,
CollisionList& collisions, int skipIndex) {
bool collided = false;
SphereShape sphere(sphereRadius, sphereCenter);
const FBXGeometry& geometry = _geometry->getFBXGeometry();
for (int i = 0; i < _jointShapes.size(); i++) {
const FBXJoint& joint = geometry.joints[i];
if (joint.parentIndex != -1) {
if (skipIndex != -1) {
int ancestorIndex = joint.parentIndex;
do {
if (ancestorIndex == skipIndex) {
goto outerContinue;
}
ancestorIndex = geometry.joints[ancestorIndex].parentIndex;
} while (ancestorIndex != -1);
}
}
if (ShapeCollider::collideShapes(&sphere, _jointShapes[i], collisions)) {
CollisionInfo* collision = collisions.getLastCollision();
collision->_type = COLLISION_TYPE_MODEL;
collision->_data = (void*)(this);
collision->_intData = i;
collided = true;
}
outerContinue: ;
}
return collided;
}
bool Model::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) {
bool collided = false;
PlaneShape planeShape(plane);
for (int i = 0; i < _jointShapes.size(); i++) {
if (ShapeCollider::collideShapes(&planeShape, _jointShapes[i], collisions)) {
CollisionInfo* collision = collisions.getLastCollision();
collision->_type = COLLISION_TYPE_MODEL;
collision->_data = (void*)(this);
collision->_intData = i;
collided = true;
}
}
return collided;
// TODO: implement this when we know how to build shapes for regular Models
}
class Blender : public QRunnable {
@ -1197,7 +918,7 @@ void Model::simulateInternal(float deltaTime) {
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i);
}
_shapesAreDirty = true;
_shapesAreDirty = ! _shapes.isEmpty();
// update the attachment transforms and simulate them
const FBXGeometry& geometry = _geometry->getFBXGeometry();
@ -1332,7 +1053,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, const gl
for (int j = freeLineage.size() - 1; j >= 0; j--) {
updateJointState(freeLineage.at(j));
}
_shapesAreDirty = true;
_shapesAreDirty = !_shapes.isEmpty();
return true;
}
@ -1370,14 +1091,17 @@ const int BALL_SUBDIVISIONS = 10;
void Model::renderJointCollisionShapes(float alpha) {
glPushMatrix();
Application::getInstance()->loadTranslatedViewMatrix(_translation);
for (int i = 0; i < _jointShapes.size(); i++) {
glPushMatrix();
for (int i = 0; i < _shapes.size(); i++) {
Shape* shape = _shapes[i];
if (!shape) {
continue;
}
Shape* shape = _jointShapes[i];
glPushMatrix();
// NOTE: the shapes are in the avatar local-frame
if (shape->getType() == Shape::SPHERE_SHAPE) {
// shapes are stored in world-frame, so we have to transform into model frame
glm::vec3 position = shape->getPosition() - _translation;
glm::vec3 position = _rotation * shape->getTranslation();
glTranslatef(position.x, position.y, position.z);
const glm::quat& rotation = shape->getRotation();
glm::vec3 axis = glm::axis(rotation);
@ -1392,7 +1116,7 @@ void Model::renderJointCollisionShapes(float alpha) {
// draw a blue sphere at the capsule endpoint
glm::vec3 endPoint;
capsule->getEndPoint(endPoint);
endPoint = endPoint - _translation;
endPoint = _rotation * endPoint;
glTranslatef(endPoint.x, endPoint.y, endPoint.z);
glColor4f(0.6f, 0.6f, 0.8f, alpha);
glutSolidSphere(capsule->getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
@ -1400,7 +1124,7 @@ void Model::renderJointCollisionShapes(float alpha) {
// draw a yellow sphere at the capsule startpoint
glm::vec3 startPoint;
capsule->getStartPoint(startPoint);
startPoint = startPoint - _translation;
startPoint = _rotation * startPoint;
glm::vec3 axis = endPoint - startPoint;
glTranslatef(-axis.x, -axis.y, -axis.z);
glColor4f(0.8f, 0.8f, 0.6f, alpha);
@ -1416,85 +1140,6 @@ void Model::renderJointCollisionShapes(float alpha) {
glPopMatrix();
}
void Model::renderBoundingCollisionShapes(float alpha) {
glPushMatrix();
Application::getInstance()->loadTranslatedViewMatrix(_translation);
// draw a blue sphere at the capsule endpoint
glm::vec3 endPoint;
_boundingShape.getEndPoint(endPoint);
endPoint = endPoint - _translation;
glTranslatef(endPoint.x, endPoint.y, endPoint.z);
glColor4f(0.6f, 0.6f, 0.8f, alpha);
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
// draw a yellow sphere at the capsule startpoint
glm::vec3 startPoint;
_boundingShape.getStartPoint(startPoint);
startPoint = startPoint - _translation;
glm::vec3 axis = endPoint - startPoint;
glTranslatef(-axis.x, -axis.y, -axis.z);
glColor4f(0.8f, 0.8f, 0.6f, alpha);
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
// draw a green cylinder between the two points
glm::vec3 origin(0.0f);
glColor4f(0.6f, 0.8f, 0.6f, alpha);
Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius());
glPopMatrix();
}
bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const {
if (collision._type == COLLISION_TYPE_MODEL) {
// the joint is pokable by a collision if it exists and is free to move
const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._intData];
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._intData;
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
return !freeLineage.isEmpty();
}
return false;
}
void Model::applyCollision(CollisionInfo& collision) {
if (collision._type != COLLISION_TYPE_MODEL) {
return;
}
glm::vec3 jointPosition(0.0f);
int jointIndex = collision._intData;
if (getJointPositionInWorldFrame(jointIndex, jointPosition)) {
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;
getJointPositionInWorldFrame(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;
getJointPositionInWorldFrame(jointIndex, end);
// transform into model-frame
glm::vec3 newEnd = glm::inverse(_rotation) * (start + glm::angleAxis(angle, axis) * (end - start) - _translation);
// try to move it
setJointPosition(jointIndex, newEnd, glm::quat(), false, -1, true);
}
}
}
}
void Model::setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
if (_blendedVertexBuffers.isEmpty()) {
return;

View file

@ -16,7 +16,7 @@
#include <QObject>
#include <QUrl>
#include <CapsuleShape.h>
#include <PhysicsEntity.h>
#include <AnimationCache.h>
@ -33,7 +33,7 @@ typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
/// A generic 3D model displaying geometry loaded from a URL.
class Model : public QObject {
class Model : public QObject, public PhysicsEntity {
Q_OBJECT
public:
@ -41,12 +41,6 @@ public:
Model(QObject* parent = NULL);
virtual ~Model();
void setTranslation(const glm::vec3& translation) { _translation = translation; }
const glm::vec3& getTranslation() const { return _translation; }
void setRotation(const glm::quat& rotation) { _rotation = rotation; }
const glm::quat& getRotation() const { return _rotation; }
/// enables/disables scale to fit behavior, the model will be automatically scaled to the specified largest dimension
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f);
bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
@ -67,7 +61,7 @@ public:
void setBlendshapeCoefficients(const QVector<float>& coefficients) { _blendshapeCoefficients = coefficients; }
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
bool isActive() const { return _geometry && _geometry->isLoaded(); }
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().isEmpty()); }
@ -134,69 +128,34 @@ public:
QStringList getJointNames() const;
AnimationHandlePointer createAnimationHandle();
const QList<AnimationHandlePointer>& getRunningAnimations() const { return _runningAnimations; }
void clearShapes();
void rebuildShapes();
void resetShapePositions();
// virtual overrides from PhysicsEntity
virtual void buildShapes();
virtual void updateShapePositions();
void renderJointCollisionShapes(float alpha);
void renderBoundingCollisionShapes(float alpha);
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
/// \param shapes list of pointers shapes to test against Model
/// \param collisions list to store collision results
/// \return true if at least one shape collided agains Model
bool findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions);
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
CollisionList& collisions, int skipIndex = -1);
bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
/// \param collision details about the collisions
/// \return true if the collision is against a moveable joint
bool collisionHitsMoveableJoint(CollisionInfo& collision) const;
/// \param collision details about the collision
/// Use the collision to affect the model
void applyCollision(CollisionInfo& collision);
float getBoundingRadius() const { return _boundingRadius; }
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
/// Sets blended vertices computed in a separate thread.
void setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
const CapsuleShape& getBoundingShape() const { return _boundingShape; }
protected:
QSharedPointer<NetworkGeometry> _geometry;
glm::vec3 _translation;
glm::quat _rotation;
glm::vec3 _scale;
glm::vec3 _offset;
bool _scaleToFit; /// If you set scaleToFit, we will calculate scale based on MeshExtents
float _scaleToFitLargestDimension; /// this is the dimension that scale to fit will use
bool _scaledToFit; /// have we scaled to fit
bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space
bool _snappedToCenter; /// are we currently snapped to center
int _rootIndex;
bool _shapesAreDirty;
QVector<JointState> _jointStates;
QVector<Shape*> _jointShapes;
float _boundingRadius;
CapsuleShape _boundingShape;
glm::vec3 _boundingShapeLocalOffset;
class MeshState {
public:
QVector<glm::mat4> clusterMatrices;
@ -240,8 +199,6 @@ protected:
/// first free ancestor.
float getLimbLength(int jointIndex) const;
void computeBoundingShape(const FBXGeometry& geometry);
private:
friend class AnimationHandle;

View file

@ -1,167 +0,0 @@
//
// RagDoll.cpp
// interface/src/avatar
//
// Created by Andrew Meadows 2014.05.30
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/transform.hpp>
#include <CollisionInfo.h>
#include <SharedUtil.h>
#include <CapsuleShape.h>
#include <SphereShape.h>
#include "RagDoll.h"
// ----------------------------------------------------------------------------
// FixedConstraint
// ----------------------------------------------------------------------------
FixedConstraint::FixedConstraint(glm::vec3* point, const glm::vec3& anchor) : _point(point), _anchor(anchor) {
}
float FixedConstraint::enforce() {
assert(_point != NULL);
float distance = glm::distance(_anchor, *_point);
*_point = _anchor;
return distance;
}
void FixedConstraint::setPoint(glm::vec3* point) {
_point = point;
}
void FixedConstraint::setAnchor(const glm::vec3& anchor) {
_anchor = anchor;
}
// ----------------------------------------------------------------------------
// DistanceConstraint
// ----------------------------------------------------------------------------
DistanceConstraint::DistanceConstraint(glm::vec3* startPoint, glm::vec3* endPoint) : _distance(-1.0f) {
_points[0] = startPoint;
_points[1] = endPoint;
_distance = glm::distance(*(_points[0]), *(_points[1]));
}
DistanceConstraint::DistanceConstraint(const DistanceConstraint& other) {
_distance = other._distance;
_points[0] = other._points[0];
_points[1] = other._points[1];
}
void DistanceConstraint::setDistance(float distance) {
_distance = fabsf(distance);
}
float DistanceConstraint::enforce() {
float newDistance = glm::distance(*(_points[0]), *(_points[1]));
glm::vec3 direction(0.0f, 1.0f, 0.0f);
if (newDistance > EPSILON) {
direction = (*(_points[0]) - *(_points[1])) / newDistance;
}
glm::vec3 center = 0.5f * (*(_points[0]) + *(_points[1]));
*(_points[0]) = center + (0.5f * _distance) * direction;
*(_points[1]) = center - (0.5f * _distance) * direction;
return glm::abs(newDistance - _distance);
}
void DistanceConstraint::updateProxyShape(Shape* shape, const glm::quat& rotation, const glm::vec3& translation) const {
if (!shape) {
return;
}
switch (shape->getType()) {
case Shape::SPHERE_SHAPE: {
// sphere collides at endPoint
SphereShape* sphere = static_cast<SphereShape*>(shape);
sphere->setPosition(translation + rotation * (*_points[1]));
}
break;
case Shape::CAPSULE_SHAPE: {
// capsule collides from startPoint to endPoint
CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
capsule->setEndPoints(translation + rotation * (*_points[0]), translation + rotation * (*_points[1]));
}
break;
default:
break;
}
}
// ----------------------------------------------------------------------------
// RagDoll
// ----------------------------------------------------------------------------
RagDoll::RagDoll() {
}
RagDoll::~RagDoll() {
clear();
}
void RagDoll::init(const QVector<JointState>& states) {
clear();
const int numStates = states.size();
_points.reserve(numStates);
for (int i = 0; i < numStates; ++i) {
const JointState& state = states[i];
_points.push_back(state.getPosition());
int parentIndex = state.getFBXJoint().parentIndex;
assert(parentIndex < i);
if (parentIndex == -1) {
FixedConstraint* anchor = new FixedConstraint(&(_points[i]), glm::vec3(0.0f));
_constraints.push_back(anchor);
} else {
DistanceConstraint* stick = new DistanceConstraint(&(_points[i]), &(_points[parentIndex]));
_constraints.push_back(stick);
}
}
}
/// Delete all data.
void RagDoll::clear() {
int numConstraints = _constraints.size();
for (int i = 0; i < numConstraints; ++i) {
delete _constraints[i];
}
_constraints.clear();
_points.clear();
}
float RagDoll::slaveToSkeleton(const QVector<JointState>& states, float fraction) {
const int numStates = states.size();
assert(numStates == _points.size());
fraction = glm::clamp(fraction, 0.0f, 1.0f);
float maxDistance = 0.0f;
for (int i = 0; i < numStates; ++i) {
glm::vec3 oldPoint = _points[i];
_points[i] = (1.0f - fraction) * _points[i] + fraction * states[i].getPosition();
maxDistance = glm::max(maxDistance, glm::distance(oldPoint, _points[i]));
}
return maxDistance;
}
float RagDoll::enforceConstraints() {
float maxDistance = 0.0f;
const int numConstraints = _constraints.size();
for (int i = 0; i < numConstraints; ++i) {
DistanceConstraint* c = static_cast<DistanceConstraint*>(_constraints[i]);
//maxDistance = glm::max(maxDistance, _constraints[i]->enforce());
maxDistance = glm::max(maxDistance, c->enforce());
}
return maxDistance;
}
void RagDoll::updateShapes(const QVector<Shape*>& shapes, const glm::quat& rotation, const glm::vec3& translation) const {
int numShapes = shapes.size();
int numConstraints = _constraints.size();
for (int i = 0; i < numShapes && i < numConstraints; ++i) {
_constraints[i]->updateProxyShape(shapes[i], rotation, translation);
}
}

View file

@ -1,95 +0,0 @@
//
// RagDoll.h
// interface/src/avatar
//
// Created by Andrew Meadows 2014.05.30
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RagDoll_h
#define hifi_RagDoll_h
#include "renderer/Model.h"
class Shape;
class Constraint {
public:
Constraint() {}
virtual ~Constraint() {}
/// Enforce contraint by moving relevant points.
/// \return max distance of point movement
virtual float enforce() = 0;
/// \param shape pointer to shape that will be this Constraint's collision proxy
/// \param rotation rotation into shape's collision frame
/// \param translation translation into shape's collision frame
/// Moves the shape such that it will collide at this constraint's position
virtual void updateProxyShape(Shape* shape, const glm::quat& rotation, const glm::vec3& translation) const {}
protected:
int _type;
};
class FixedConstraint : public Constraint {
public:
FixedConstraint(glm::vec3* point, const glm::vec3& anchor);
float enforce();
void setPoint(glm::vec3* point);
void setAnchor(const glm::vec3& anchor);
private:
glm::vec3* _point;
glm::vec3 _anchor;
};
class DistanceConstraint : public Constraint {
public:
DistanceConstraint(glm::vec3* startPoint, glm::vec3* endPoint);
DistanceConstraint(const DistanceConstraint& other);
float enforce();
void setDistance(float distance);
void updateProxyShape(Shape* shape, const glm::quat& rotation, const glm::vec3& translation) const;
private:
float _distance;
glm::vec3* _points[2];
};
class RagDoll {
public:
RagDoll();
virtual ~RagDoll();
/// Create points and constraints based on topology of collection of joints
/// \param joints list of connected joint states
void init(const QVector<JointState>& states);
/// Delete all data.
void clear();
/// \param states list of joint states
/// \param fraction range from 0.0 (no movement) to 1.0 (use joint locations)
/// \return max distance of point movement
float slaveToSkeleton(const QVector<JointState>& states, float fraction);
/// Enforce contraints.
/// \return max distance of point movement
float enforceConstraints();
const QVector<glm::vec3>& getPoints() const { return _points; }
/// \param shapes list of shapes to be updated with new positions
/// \param rotation rotation into shapes' collision frame
/// \param translation translation into shapes' collision frame
void updateShapes(const QVector<Shape*>& shapes, const glm::quat& rotation, const glm::vec3& translation) const;
private:
QVector<Constraint*> _constraints;
QVector<glm::vec3> _points;
};
#endif // hifi_RagDoll_h

View file

@ -181,7 +181,7 @@ void ChatWindow::addTimeStamp() {
QLabel* timeLabel = new QLabel(timeString);
timeLabel->setStyleSheet("color: #333333;"
"background-color: white;"
"font-size: 14pt;"
"font-size: 14px;"
"padding: 4px;");
timeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
timeLabel->setAlignment(Qt::AlignLeft);
@ -287,7 +287,7 @@ void ChatWindow::participantsChanged() {
"padding-bottom: 2px;"
"padding-left: 2px;"
"border: 1px solid palette(shadow);"
"font-size: 14pt;"
"font-size: 14px;"
"font-weight: bold");
userLabel->setProperty("user", participantName);
userLabel->setCursor(Qt::PointingHandCursor);
@ -323,7 +323,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
"padding-right: 20px;"
"margin: 0px;"
"color: #333333;"
"font-size: 14pt;"
"font-size: 14px;"
"background-color: rgba(0, 0, 0, 0%);"
"border: 0; }"
"QMenu{ border: 2px outset gray; }");

View file

@ -23,7 +23,7 @@ ScriptsTableWidget::ScriptsTableWidget(QWidget* parent) :
setShowGrid(false);
setSelectionMode(QAbstractItemView::NoSelection);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setStyleSheet("QTableWidget { background: transparent; color: #333333; } QToolTip { color: #000000; background: #f9f6e4; padding: 2px; }");
setStyleSheet("QTableWidget { border: none; background: transparent; color: #333333; } QToolTip { color: #000000; background: #f9f6e4; padding: 2px; }");
setToolTipDuration(200);
setWordWrap(true);
setGeometry(0, 0, parent->width(), parent->height());

View file

@ -660,7 +660,7 @@ color: #0e7077</string>
<string notr="true">background: #0e7077;
color: #fff;
border-radius: 4px;
font: bold 14pt;
font: bold 14px;
padding: 10px;margin-top:10px</string>
</property>
<property name="text">

View file

@ -25,18 +25,18 @@ QWidget {
<property name="geometry">
<rect>
<x>37</x>
<y>29</y>
<y>20</y>
<width>251</width>
<height>20</height>
<height>31</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077;
font-size: 20pt;
font-size: 20px;
background: transparent;</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18pt;&quot;&gt;Running Scripts&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18px;&quot;&gt;Running Scripts&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="margin">
<number>0</number>
@ -56,7 +56,7 @@ background: transparent;</string>
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077;
font: bold 14pt;
font: bold 14px;
background: transparent;</string>
</property>
<property name="text">
@ -82,7 +82,7 @@ background: transparent;</string>
<string notr="true">background: #0e7077;
color: #fff;
border-radius: 4px;
font: bold 14pt;
font: bold 14px;
padding-top: 3px;</string>
</property>
<property name="text">
@ -105,7 +105,7 @@ padding-top: 3px;</string>
<string notr="true">background: #0e7077;
color: #fff;
border-radius: 4px;
font: bold 14pt;
font: bold 14px;
padding-top: 3px;</string>
</property>
<property name="text">
@ -123,7 +123,7 @@ padding-top: 3px;</string>
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077;
font: bold 14pt;</string>
font: bold 14px;</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Recently loaded&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -140,7 +140,7 @@ font: bold 14pt;</string>
</property>
<property name="styleSheet">
<string notr="true">color: #95a5a6;
font-size: 14pt;</string>
font-size: 14px;</string>
</property>
<property name="text">
<string>(click a script to load and run it)</string>
@ -184,7 +184,7 @@ font-size: 14pt;</string>
</rect>
</property>
<property name="styleSheet">
<string notr="true">font: 14pt;</string>
<string notr="true">font: 14px;</string>
</property>
<property name="text">
<string>There are no scripts currently running.</string>
@ -204,7 +204,7 @@ font-size: 14pt;</string>
</property>
<property name="styleSheet">
<string notr="true">background: transparent;
font-size: 14pt;</string>
font-size: 14px;</string>
</property>
</widget>
<widget class="QWidget" name="runningScriptsTableWidget" native="true">
@ -218,7 +218,7 @@ font-size: 14pt;</string>
</property>
<property name="styleSheet">
<string notr="true">background: transparent;
font-size: 14pt;</string>
font-size: 14px;</string>
</property>
</widget>
<widget class="QPushButton" name="loadScriptButton">
@ -237,7 +237,7 @@ font-size: 14pt;</string>
<string notr="true">background: #0e7077;
color: #fff;
border-radius: 4px;
font: bold 14pt;
font: bold 14px;
padding-top: 3px;</string>
</property>
<property name="text">

View file

@ -277,7 +277,7 @@ padding-left:20px;</string>
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Helvetica'; font-size:14pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Helvetica'; font-size:14px; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>

View file

@ -31,7 +31,7 @@ const int NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL = NETWORK_BUFFER_LENGTH_BYTE
const unsigned int BUFFER_SEND_INTERVAL_USECS = floorf((NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL
/ (float) SAMPLE_RATE) * 1000 * 1000);
const short RING_BUFFER_LENGTH_FRAMES = 100;
const short RING_BUFFER_LENGTH_FRAMES = 10;
const int MAX_SAMPLE_VALUE = std::numeric_limits<int16_t>::max();
const int MIN_SAMPLE_VALUE = std::numeric_limits<int16_t>::min();

View file

@ -223,7 +223,7 @@ public:
virtual const glm::vec3& getVelocity() const { return vec3Zero; }
virtual bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
virtual bool findSphereCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
return false;
}

View file

@ -1717,7 +1717,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
glm::vec3 boneEnd = extractTranslation(transformJointToMesh);
glm::vec3 boneBegin = boneEnd;
glm::vec3 boneDirection;
float boneLength;
float boneLength = 0.0f;
if (joint.parentIndex != -1) {
boneBegin = extractTranslation(inverseModelTransform * geometry.joints[joint.parentIndex].bindTransform);
boneDirection = boneEnd - boneBegin;
@ -1779,7 +1779,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
glm::vec3 boneBegin = boneEnd;
glm::vec3 boneDirection;
float boneLength;
float boneLength = 0.0f;
if (joint.parentIndex != -1) {
boneBegin = extractTranslation(inverseModelTransform * geometry.joints[joint.parentIndex].bindTransform);
boneDirection = boneEnd - boneBegin;

View file

@ -12,9 +12,10 @@
#ifndef hifi_ModelItem_h
#define hifi_ModelItem_h
#include <glm/glm.hpp>
#include <stdint.h>
#include <glm/glm.hpp>
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
@ -23,6 +24,7 @@
#include <SharedUtil.h>
#include <OctreePacketData.h>
class ModelItem;
class ModelEditPacketSender;
class ModelItemProperties;

View file

@ -757,7 +757,7 @@ bool findShapeCollisionsOp(OctreeElement* element, void* extraData) {
// coarse check against bounds
AACube cube = element->getAACube();
cube.scale(TREE_SCALE);
if (!cube.expandedContains(args->shape->getPosition(), args->shape->getBoundingRadius())) {
if (!cube.expandedContains(args->shape->getTranslation(), args->shape->getBoundingRadius())) {
return false;
}
if (!element->isLeaf()) {

View file

@ -202,14 +202,13 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
AvatarData* avatar = avatarPointer.data();
// use a very generous bounding radius since the arms can stretch
float totalRadius = 2.f * avatar->getBoundingRadius() + radius;
float totalRadius = avatar->getBoundingRadius() + radius;
glm::vec3 relativePosition = center - avatar->getPosition();
if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) {
continue;
}
if (avatar->findParticleCollisions(center, radius, _collisions)) {
if (avatar->findSphereCollisions(center, radius, _collisions)) {
int numCollisions = _collisions.size();
for (int i = 0; i < numCollisions; ++i) {
CollisionInfo* collision = _collisions.getCollision(i);
@ -222,25 +221,6 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
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 == COLLISION_TYPE_PADDLE_HAND) {
// 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);
}
collision->_damping = damping;
}
// HACK END
updateCollisionSound(particle, collision->_penetration, COLLISION_FREQUENCY);
collision->_penetration /= (float)(TREE_SCALE);
particle->applyHardCollision(*collision);

View file

@ -18,9 +18,6 @@
#include "SharedUtil.h"
// default axis of CapsuleShape is Y-axis
const glm::vec3 localAxis(0.0f, 1.0f, 0.0f);
CapsuleShape::CapsuleShape() : Shape(Shape::CAPSULE_SHAPE), _radius(0.0f), _halfHeight(0.0f) {}
CapsuleShape::CapsuleShape(float radius, float halfHeight) : Shape(Shape::CAPSULE_SHAPE),
@ -40,17 +37,17 @@ CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm:
/// \param[out] startPoint is the center of start cap
void CapsuleShape::getStartPoint(glm::vec3& startPoint) const {
startPoint = _position - _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
startPoint = _translation - _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
}
/// \param[out] endPoint is the center of the end cap
void CapsuleShape::getEndPoint(glm::vec3& endPoint) const {
endPoint = _position + _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
endPoint = _translation + _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
}
void CapsuleShape::computeNormalizedAxis(glm::vec3& axis) const {
// default axis of a capsule is along the yAxis
axis = _rotation * glm::vec3(0.0f, 1.0f, 0.0f);
axis = _rotation * DEFAULT_CAPSULE_AXIS;
}
void CapsuleShape::setRadius(float radius) {
@ -71,17 +68,12 @@ void CapsuleShape::setRadiusAndHalfHeight(float radius, float halfHeight) {
void CapsuleShape::setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint) {
glm::vec3 axis = endPoint - startPoint;
_position = 0.5f * (endPoint + startPoint);
_translation = 0.5f * (endPoint + startPoint);
float height = glm::length(axis);
if (height > EPSILON) {
_halfHeight = 0.5f * height;
axis /= height;
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
float angle = glm::angle(axis, yAxis);
if (angle > EPSILON) {
axis = glm::normalize(glm::cross(yAxis, axis));
_rotation = glm::angleAxis(angle, axis);
}
computeNewRotation(axis);
}
updateBoundingRadius();
}
@ -94,3 +86,13 @@ bool CapsuleShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec
// TODO: implement the raycast to return inside surface intersection for the internal rayStart.
return findRayCapsuleIntersection(rayStart, rayDirection, capsuleStart, capsuleEnd, _radius, distance);
}
// static
glm::quat CapsuleShape::computeNewRotation(const glm::vec3& newAxis) {
float angle = glm::angle(newAxis, DEFAULT_CAPSULE_AXIS);
if (angle > EPSILON) {
glm::vec3 rotationAxis = glm::normalize(glm::cross(DEFAULT_CAPSULE_AXIS, newAxis));
return glm::angleAxis(angle, rotationAxis);
}
return glm::quat();
}

View file

@ -14,7 +14,11 @@
#include "Shape.h"
#include "SharedUtil.h"
// default axis of CapsuleShape is Y-axis
const glm::vec3 DEFAULT_CAPSULE_AXIS(0.0f, 1.0f, 0.0f);
class CapsuleShape : public Shape {
public:
@ -23,26 +27,33 @@ public:
CapsuleShape(float radius, float halfHeight, const glm::vec3& position, const glm::quat& rotation);
CapsuleShape(float radius, const glm::vec3& startPoint, const glm::vec3& endPoint);
virtual ~CapsuleShape() {}
float getRadius() const { return _radius; }
float getHalfHeight() const { return _halfHeight; }
virtual float getHalfHeight() const { return _halfHeight; }
/// \param[out] startPoint is the center of start cap
void getStartPoint(glm::vec3& startPoint) const;
virtual void getStartPoint(glm::vec3& startPoint) const;
/// \param[out] endPoint is the center of the end cap
void getEndPoint(glm::vec3& endPoint) const;
virtual void getEndPoint(glm::vec3& endPoint) const;
void computeNormalizedAxis(glm::vec3& axis) const;
virtual void computeNormalizedAxis(glm::vec3& axis) const;
void setRadius(float radius);
void setHalfHeight(float height);
void setRadiusAndHalfHeight(float radius, float height);
void setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint);
virtual void setHalfHeight(float height);
virtual void setRadiusAndHalfHeight(float radius, float height);
/// Sets the endpoints and updates center, rotation, and halfHeight to agree.
virtual void setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint);
bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const;
virtual float getVolume() const { return (PI * _radius * _radius) * (1.3333333333f * _radius + getHalfHeight()); }
protected:
void updateBoundingRadius() { _boundingRadius = _radius + _halfHeight; }
virtual void updateBoundingRadius() { _boundingRadius = _radius + getHalfHeight(); }
static glm::quat computeNewRotation(const glm::vec3& newAxis);
float _radius;
float _halfHeight;

View file

@ -11,6 +11,20 @@
#include "CollisionInfo.h"
#include "Shape.h"
#include "SharedUtil.h"
CollisionInfo::CollisionInfo() :
_data(NULL),
_intData(0),
_shapeA(NULL),
_shapeB(NULL),
_damping(0.f),
_elasticity(1.f),
_contactPoint(0.f),
_penetration(0.f),
_addedVelocity(0.f) {
}
CollisionList::CollisionList(int maxSize) :
_maxSize(maxSize),
@ -18,6 +32,29 @@ CollisionList::CollisionList(int maxSize) :
_collisions.resize(_maxSize);
}
void CollisionInfo::apply() {
assert(_shapeA);
// NOTE: Shape::computeEffectiveMass() has side effects: computes and caches partial Lagrangian coefficients
Shape* shapeA = const_cast<Shape*>(_shapeA);
float massA = shapeA->computeEffectiveMass(_penetration, _contactPoint);
float massB = MAX_SHAPE_MASS;
float totalMass = massA + massB;
if (_shapeB) {
Shape* shapeB = const_cast<Shape*>(_shapeB);
massB = shapeB->computeEffectiveMass(-_penetration, _contactPoint - _penetration);
totalMass = massA + massB;
if (totalMass < EPSILON) {
massA = massB = 1.0f;
totalMass = 2.0f;
}
// remember that _penetration points from A into B
shapeB->accumulateDelta(massA / totalMass, _penetration);
}
// NOTE: Shape::accumulateDelta() uses the coefficients from previous call to Shape::computeEffectiveMass()
// remember that _penetration points from A into B
shapeA->accumulateDelta(massB / totalMass, -_penetration);
}
CollisionInfo* CollisionList::getNewCollision() {
// return pointer to existing CollisionInfo, or NULL of list is full
return (_size < _maxSize) ? &(_collisions[_size++]) : NULL;
@ -38,17 +75,17 @@ CollisionInfo* CollisionList::getLastCollision() {
}
void CollisionList::clear() {
// we rely on the external context to properly set or clear the data members of a collision
// whenever it is used.
// we rely on the external context to properly set or clear the data members of CollisionInfos
/*
for (int i = 0; i < _size; ++i) {
// we only clear the important stuff
CollisionInfo& collision = _collisions[i];
collision._type = COLLISION_TYPE_UNKNOWN;
//collision._data = NULL;
//collision._intData = 0;
//collision._floatDAta = 0.0f;
//collision._vecData = glm::vec3(0.0f);
//collision._shapeA = NULL;
//collision._shapeB = NULL;
//collision._damping;
//collision._elasticity;
//collision._contactPoint;

View file

@ -17,16 +17,7 @@
#include <QVector>
enum CollisionType {
COLLISION_TYPE_UNKNOWN = 0,
COLLISION_TYPE_PADDLE_HAND,
COLLISION_TYPE_MODEL,
// _data = pointer to Model that owns joint
// _intData = joint index
COLLISION_TYPE_AACUBE,
// _floatData = cube side
// _vecData = cube center
};
class Shape;
const quint32 COLLISION_GROUP_ENVIRONMENT = 1U << 0;
const quint32 COLLISION_GROUP_AVATARS = 1U << 1;
@ -41,38 +32,24 @@ const quint32 VALID_COLLISION_GROUPS = 0x0f;
class CollisionInfo {
public:
CollisionInfo()
: _type(0),
_data(NULL),
_intData(0),
_damping(0.f),
_elasticity(1.f),
_contactPoint(0.f),
_penetration(0.f),
_addedVelocity(0.f) {
}
CollisionInfo(qint32 type)
: _type(type),
_data(NULL),
_intData(0),
_damping(0.f),
_elasticity(1.f),
_contactPoint(0.f),
_penetration(0.f),
_addedVelocity(0.f) {
}
CollisionInfo();
~CollisionInfo() {}
int _type; // type of Collision
// the value of the *Data fields depend on the type
// TODO: Andrew to get rid of these data members
void* _data;
int _intData;
float _floatData;
glm::vec3 _vecData;
/// accumulates position changes for the shapes in this collision to resolve penetration
void apply();
Shape* getShapeA() const { return const_cast<Shape*>(_shapeA); }
Shape* getShapeB() const { return const_cast<Shape*>(_shapeB); }
const Shape* _shapeA; // pointer to shapeA in this collision
const Shape* _shapeB; // pointer to shapeB in this collision
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

View file

@ -14,7 +14,7 @@
// ListShapeEntry
void ListShapeEntry::updateTransform(const glm::vec3& rootPosition, const glm::quat& rootRotation) {
_shape->setPosition(rootPosition + rootRotation * _localPosition);
_shape->setTranslation(rootPosition + rootRotation * _localPosition);
_shape->setRotation(_localRotation * rootRotation);
}
@ -24,9 +24,9 @@ ListShape::~ListShape() {
clear();
}
void ListShape::setPosition(const glm::vec3& position) {
void ListShape::setTranslation(const glm::vec3& position) {
_subShapeTransformsAreDirty = true;
Shape::setPosition(position);
Shape::setTranslation(position);
}
void ListShape::setRotation(const glm::quat& rotation) {
@ -44,7 +44,7 @@ const Shape* ListShape::getSubShape(int index) const {
void ListShape::updateSubTransforms() {
if (_subShapeTransformsAreDirty) {
for (int i = 0; i < _subShapeEntries.size(); ++i) {
_subShapeEntries[i].updateTransform(_position, _rotation);
_subShapeEntries[i].updateTransform(_translation, _rotation);
}
_subShapeTransformsAreDirty = false;
}

View file

@ -42,7 +42,7 @@ public:
~ListShape();
void setPosition(const glm::vec3& position);
void setTranslation(const glm::vec3& position);
void setRotation(const glm::quat& rotation);
const Shape* getSubShape(int index) const;

View file

@ -0,0 +1,211 @@
//
// PhysicsEntity.cpp
// libraries/shared/src
//
// Created by Andrew Meadows 2014.06.11
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "PhysicsEntity.h"
#include "PhysicsSimulation.h"
#include "Shape.h"
#include "ShapeCollider.h"
PhysicsEntity::PhysicsEntity() :
_translation(0.0f),
_rotation(),
_boundingRadius(0.0f),
_shapesAreDirty(true),
_enableShapes(false),
_simulation(NULL) {
}
PhysicsEntity::~PhysicsEntity() {
if (_simulation) {
_simulation->removeEntity(this);
_simulation = NULL;
}
}
void PhysicsEntity::setTranslation(const glm::vec3& translation) {
if (_translation != translation) {
_shapesAreDirty = !_shapes.isEmpty();
_translation = translation;
}
}
void PhysicsEntity::setRotation(const glm::quat& rotation) {
if (_rotation != rotation) {
_shapesAreDirty = !_shapes.isEmpty();
_rotation = rotation;
}
}
void PhysicsEntity::setShapeBackPointers() {
for (int i = 0; i < _shapes.size(); i++) {
Shape* shape = _shapes[i];
if (shape) {
shape->setEntity(this);
}
}
}
void PhysicsEntity::setEnableShapes(bool enable) {
if (enable != _enableShapes) {
clearShapes();
_enableShapes = enable;
if (_enableShapes) {
buildShapes();
}
}
}
void PhysicsEntity::clearShapes() {
for (int i = 0; i < _shapes.size(); ++i) {
delete _shapes[i];
}
_shapes.clear();
}
bool PhysicsEntity::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
int numShapes = _shapes.size();
float minDistance = FLT_MAX;
for (int j = 0; j < numShapes; ++j) {
const Shape* shape = _shapes[j];
float thisDistance = FLT_MAX;
if (shape && shape->findRayIntersection(origin, direction, thisDistance)) {
if (thisDistance < minDistance) {
minDistance = thisDistance;
}
}
}
if (minDistance < FLT_MAX) {
distance = minDistance;
return true;
}
return false;
}
bool PhysicsEntity::findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions) {
bool collided = false;
int numTheirShapes = shapes.size();
for (int i = 0; i < numTheirShapes; ++i) {
const Shape* theirShape = shapes[i];
if (!theirShape) {
continue;
}
int numOurShapes = _shapes.size();
for (int j = 0; j < numOurShapes; ++j) {
const Shape* ourShape = _shapes.at(j);
if (ourShape && ShapeCollider::collideShapes(theirShape, ourShape, collisions)) {
collided = true;
}
}
}
return collided;
}
bool PhysicsEntity::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius, CollisionList& collisions) {
bool collided = false;
SphereShape sphere(sphereRadius, sphereCenter);
for (int i = 0; i < _shapes.size(); i++) {
Shape* shape = _shapes[i];
if (!shape) {
continue;
}
if (ShapeCollider::collideShapes(&sphere, shape, collisions)) {
CollisionInfo* collision = collisions.getLastCollision();
collision->_data = (void*)(this);
collision->_intData = i;
collided = true;
}
}
return collided;
}
bool PhysicsEntity::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) {
bool collided = false;
PlaneShape planeShape(plane);
for (int i = 0; i < _shapes.size(); i++) {
if (_shapes.at(i) && ShapeCollider::collideShapes(&planeShape, _shapes.at(i), collisions)) {
CollisionInfo* collision = collisions.getLastCollision();
collision->_data = (void*)(this);
collision->_intData = i;
collided = true;
}
}
return collided;
}
// -----------------------------------------------------------
// TODO: enforce this maximum when shapes are actually built. The gotcha here is
// that the Model class (derived from PhysicsEntity) expects numShapes == numJoints,
// so we have to modify that code to be safe.
const int MAX_SHAPES_PER_ENTITY = 256;
// the first 256 prime numbers
const int primes[256] = {
2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
547, 557, 563, 569, 571, 577, 587, 593, 599, 601,
607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
661, 673, 677, 683, 691, 701, 709, 719, 727, 733,
739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013,
1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069,
1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151,
1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223,
1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291,
1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451,
1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511,
1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583,
1597, 1601, 1607, 1609, 1613, 1619 };
void PhysicsEntity::disableCollisions(int shapeIndexA, int shapeIndexB) {
if (shapeIndexA < MAX_SHAPES_PER_ENTITY && shapeIndexB < MAX_SHAPES_PER_ENTITY) {
_disabledCollisions.insert(primes[shapeIndexA] * primes[shapeIndexB]);
}
}
bool PhysicsEntity::collisionsAreEnabled(int shapeIndexA, int shapeIndexB) const {
if (shapeIndexA < MAX_SHAPES_PER_ENTITY && shapeIndexB < MAX_SHAPES_PER_ENTITY) {
return !_disabledCollisions.contains(primes[shapeIndexA] * primes[shapeIndexB]);
}
return false;
}
void PhysicsEntity::disableCurrentSelfCollisions() {
CollisionList collisions(10);
int numShapes = _shapes.size();
for (int i = 0; i < numShapes; ++i) {
const Shape* shape = _shapes.at(i);
if (!shape) {
continue;
}
for (int j = i+1; j < numShapes; ++j) {
if (!collisionsAreEnabled(i, j)) {
continue;
}
const Shape* otherShape = _shapes.at(j);
if (otherShape && ShapeCollider::collideShapes(shape, otherShape, collisions)) {
disableCollisions(i, j);
collisions.clear();
}
}
}
}

View file

@ -0,0 +1,78 @@
//
// PhysicsEntity.h
// libraries/shared/src
//
// Created by Andrew Meadows 2014.05.30
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_PhysicsEntity_h
#define hifi_PhysicsEntity_h
#include <QVector>
#include <QSet>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include "CollisionInfo.h"
class Shape;
class PhysicsSimulation;
// PhysicsEntity is the base class for anything that owns one or more Shapes that collide in a
// PhysicsSimulation. Each CollisionInfo generated by a PhysicsSimulation has back pointers to the
// two Shapes involved, and those Shapes may (optionally) have valid back pointers to their PhysicsEntity.
class PhysicsEntity {
public:
PhysicsEntity();
virtual ~PhysicsEntity();
void setTranslation(const glm::vec3& translation);
void setRotation(const glm::quat& rotation);
const glm::vec3& getTranslation() const { return _translation; }
const glm::quat& getRotation() const { return _rotation; }
float getBoundingRadius() const { return _boundingRadius; }
void setShapeBackPointers();
void setEnableShapes(bool enable);
virtual void buildShapes() = 0;
virtual void clearShapes();
const QVector<Shape*> getShapes() const { return _shapes; }
PhysicsSimulation* getSimulation() const { return _simulation; }
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
bool findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions);
bool findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius, CollisionList& collisions);
bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
void disableCollisions(int shapeIndexA, int shapeIndexB);
bool collisionsAreEnabled(int shapeIndexA, int shapeIndexB) const;
void disableCurrentSelfCollisions();
protected:
glm::vec3 _translation;
glm::quat _rotation;
float _boundingRadius;
bool _shapesAreDirty;
bool _enableShapes;
QVector<Shape*> _shapes;
QSet<int> _disabledCollisions;
private:
// PhysicsSimulation is a friend so that it can set the protected _simulation backpointer
friend class PhysicsSimulation;
PhysicsSimulation* _simulation;
};
#endif // hifi_PhysicsEntity_h

View file

@ -0,0 +1,244 @@
//
// PhysicsSimulation.cpp
// interface/src/avatar
//
// Created by Andrew Meadows 2014.06.06
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <glm/glm.hpp>
#include <iostream>
#include "PhysicsSimulation.h"
#include "PhysicsEntity.h"
#include "Ragdoll.h"
#include "SharedUtil.h"
#include "ShapeCollider.h"
int MAX_DOLLS_PER_SIMULATION = 16;
int MAX_ENTITIES_PER_SIMULATION = 64;
int MAX_COLLISIONS_PER_SIMULATION = 256;
const int NUM_SHAPE_BITS = 6;
const int SHAPE_INDEX_MASK = (1 << (NUM_SHAPE_BITS + 1)) - 1;
PhysicsSimulation::PhysicsSimulation() : _collisionList(MAX_COLLISIONS_PER_SIMULATION),
_numIterations(0), _numCollisions(0), _constraintError(0.0f), _stepTime(0) {
}
PhysicsSimulation::~PhysicsSimulation() {
// entities have a backpointer to this simulator that must be cleaned up
int numEntities = _entities.size();
for (int i = 0; i < numEntities; ++i) {
_entities[i]->_simulation = NULL;
}
_entities.clear();
// but Ragdolls do not
_dolls.clear();
}
bool PhysicsSimulation::addEntity(PhysicsEntity* entity) {
if (!entity) {
return false;
}
if (entity->_simulation == this) {
int numEntities = _entities.size();
for (int i = 0; i < numEntities; ++i) {
if (entity == _entities.at(i)) {
// already in list
assert(entity->_simulation == this);
return true;
}
}
// belongs to some other simulation
return false;
}
int numEntities = _entities.size();
if (numEntities > MAX_ENTITIES_PER_SIMULATION) {
// list is full
return false;
}
// add to list
entity->_simulation = this;
_entities.push_back(entity);
return true;
}
void PhysicsSimulation::removeEntity(PhysicsEntity* entity) {
if (!entity || !entity->_simulation || !(entity->_simulation == this)) {
return;
}
int numEntities = _entities.size();
for (int i = 0; i < numEntities; ++i) {
if (entity == _entities.at(i)) {
if (i == numEntities - 1) {
// remove it
_entities.pop_back();
} else {
// swap the last for this one
PhysicsEntity* lastEntity = _entities[numEntities - 1];
_entities.pop_back();
_entities[i] = lastEntity;
}
entity->_simulation = NULL;
break;
}
}
}
bool PhysicsSimulation::addRagdoll(Ragdoll* doll) {
if (!doll) {
return false;
}
int numDolls = _dolls.size();
if (numDolls > MAX_DOLLS_PER_SIMULATION) {
// list is full
return false;
}
for (int i = 0; i < numDolls; ++i) {
if (doll == _dolls[i]) {
// already in list
return true;
}
}
// add to list
_dolls.push_back(doll);
return true;
}
void PhysicsSimulation::removeRagdoll(Ragdoll* doll) {
int numDolls = _dolls.size();
for (int i = 0; i < numDolls; ++i) {
if (doll == _dolls[i]) {
if (i == numDolls - 1) {
// remove it
_dolls.pop_back();
} else {
// swap the last for this one
Ragdoll* lastDoll = _dolls[numDolls - 1];
_dolls.pop_back();
_dolls[i] = lastDoll;
}
break;
}
}
}
// TODO: Andrew to implement:
// DONE (1) joints pull points (SpecialCapsuleShape would help solve this)
// DONE (2) points slam shapes (SpecialCapsuleShape would help solve this)
// DONE (3) detect collisions
// DONE (4) collisions move points (SpecialCapsuleShape would help solve this)
// DONE (5) enforce constraints
// DONE (6) make sure MyAvatar creates shapes, adds to simulation with ragdoll support
// DONE (7) support for pairwise collision bypass
// DONE (8) process collisions
// DONE (8a) stubbery
// DONE (8b) shapes actually accumulate movement
// DONE (9) verify that avatar shapes self collide
// (10) slave rendered SkeletonModel to physical shapes
// (10a) give SkeletonModel duplicate JointState data
// (10b) figure out how to slave dupe JointStates to physical shapes
// (11) add and enforce angular contraints for joints
void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIterations, quint64 maxUsec) {
quint64 now = usecTimestampNow();
quint64 startTime = now;
quint64 expiry = startTime + maxUsec;
moveRagdolls(deltaTime);
int numDolls = _dolls.size();
_numCollisions = 0;
int iterations = 0;
float error = 0.0f;
do {
computeCollisions();
processCollisions();
// enforce constraints
error = 0.0f;
for (int i = 0; i < numDolls; ++i) {
error = glm::max(error, _dolls[i]->enforceRagdollConstraints());
}
++iterations;
now = usecTimestampNow();
} while (_numCollisions != 0 && (iterations < maxIterations) && (error > minError) && (now < expiry));
_numIterations = iterations;
_constraintError = error;
_stepTime = usecTimestampNow()- startTime;
#ifdef ANDREW_DEBUG
// temporary debug info for watching simulation performance
static int adebug = 0; ++adebug;
if (0 == (adebug % 100)) {
std::cout << "adebug Ni = " << _numIterations << " E = " << error << " t = " << _stepTime << std::endl; // adebug
}
#endif // ANDREW_DEBUG
}
void PhysicsSimulation::moveRagdolls(float deltaTime) {
int numDolls = _dolls.size();
for (int i = 0; i < numDolls; ++i) {
_dolls.at(i)->stepRagdollForward(deltaTime);
}
}
void PhysicsSimulation::computeCollisions() {
_collisionList.clear();
// TODO: keep track of QSet<PhysicsEntity*> collidedEntities;
int numEntities = _entities.size();
for (int i = 0; i < numEntities; ++i) {
PhysicsEntity* entity = _entities.at(i);
const QVector<Shape*> shapes = entity->getShapes();
int numShapes = shapes.size();
// collide with self
for (int j = 0; j < numShapes; ++j) {
const Shape* shape = shapes.at(j);
if (!shape) {
continue;
}
for (int k = j+1; k < numShapes; ++k) {
const Shape* otherShape = shapes.at(k);
if (otherShape && entity->collisionsAreEnabled(j, k)) {
ShapeCollider::collideShapes(shape, otherShape, _collisionList);
}
}
}
// collide with others
for (int j = i+1; j < numEntities; ++j) {
const QVector<Shape*> otherShapes = _entities.at(j)->getShapes();
ShapeCollider::collideShapesWithShapes(shapes, otherShapes, _collisionList);
}
}
_numCollisions = _collisionList.size();
}
void PhysicsSimulation::processCollisions() {
// walk all collisions, accumulate movement on shapes, and build a list of affected shapes
QSet<Shape*> shapes;
int numCollisions = _collisionList.size();
for (int i = 0; i < numCollisions; ++i) {
CollisionInfo* collision = _collisionList.getCollision(i);
collision->apply();
// there is always a shapeA
shapes.insert(collision->getShapeA());
// but need to check for valid shapeB
if (collision->_shapeB) {
shapes.insert(collision->getShapeB());
}
}
// walk all affected shapes and apply accumulated movement
QSet<Shape*>::const_iterator shapeItr = shapes.constBegin();
while (shapeItr != shapes.constEnd()) {
(*shapeItr)->applyAccumulatedDelta();
++shapeItr;
}
}

View file

@ -0,0 +1,60 @@
//
// PhysicsSimulation.h
// interface/src/avatar
//
// Created by Andrew Meadows 2014.06.06
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_PhysicsSimulation
#define hifi_PhysicsSimulation
#include <QVector>
#include "CollisionInfo.h"
class PhysicsEntity;
class Ragdoll;
class PhysicsSimulation {
public:
PhysicsSimulation();
~PhysicsSimulation();
/// \return true if entity was added to or is already in the list
bool addEntity(PhysicsEntity* entity);
void removeEntity(PhysicsEntity* entity);
/// \return true if doll was added to or is already in the list
bool addRagdoll(Ragdoll* doll);
void removeRagdoll(Ragdoll* doll);
/// \param minError constraint motion below this value is considered "close enough"
/// \param maxIterations max number of iterations before giving up
/// \param maxUsec max number of usec to spend enforcing constraints
/// \return distance of largest movement
void stepForward(float deltaTime, float minError, int maxIterations, quint64 maxUsec);
void moveRagdolls(float deltaTime);
void computeCollisions();
void processCollisions();
private:
CollisionList _collisionList;
QVector<PhysicsEntity*> _entities;
QVector<Ragdoll*> _dolls;
// some stats
int _numIterations;
int _numCollisions;
float _constraintError;
quint64 _stepTime;
};
#endif // hifi_PhysicsSimulation

View file

@ -18,7 +18,7 @@ PlaneShape::PlaneShape(const glm::vec4& coefficients) :
Shape(Shape::PLANE_SHAPE) {
glm::vec3 normal = glm::vec3(coefficients);
_position = -normal * coefficients.w;
_translation = -normal * coefficients.w;
float angle = acosf(glm::dot(normal, UNROTATED_NORMAL));
if (angle > EPSILON) {
@ -36,7 +36,7 @@ glm::vec3 PlaneShape::getNormal() const {
glm::vec4 PlaneShape::getCoefficients() const {
glm::vec3 normal = _rotation * UNROTATED_NORMAL;
return glm::vec4(normal.x, normal.y, normal.z, -glm::dot(normal, _position));
return glm::vec4(normal.x, normal.y, normal.z, -glm::dot(normal, _translation));
}
bool PlaneShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const {
@ -44,9 +44,9 @@ bool PlaneShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3&
float denominator = glm::dot(n, rayDirection);
if (fabsf(denominator) < EPSILON) {
// line is parallel to plane
return glm::dot(_position - rayStart, n) < EPSILON;
return glm::dot(_translation - rayStart, n) < EPSILON;
} else {
float d = glm::dot(_position - rayStart, n) / denominator;
float d = glm::dot(_translation - rayStart, n) / denominator;
if (d > 0.0f) {
// ray points toward plane
distance = d;

View file

@ -0,0 +1,121 @@
//
// Ragdoll.cpp
// libraries/shared/src
//
// Created by Andrew Meadows 2014.05.30
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Ragdoll.h"
#include "CapsuleShape.h"
#include "CollisionInfo.h"
#include "SharedUtil.h"
#include "SphereShape.h"
// ----------------------------------------------------------------------------
// VerletPoint
// ----------------------------------------------------------------------------
void VerletPoint::accumulateDelta(const glm::vec3& delta) {
_accumulatedDelta += delta;
++_numDeltas;
}
void VerletPoint::applyAccumulatedDelta() {
if (_numDeltas > 0) {
_position += _accumulatedDelta / (float)_numDeltas;
_accumulatedDelta = glm::vec3(0.0f);
_numDeltas = 0;
}
}
// ----------------------------------------------------------------------------
// FixedConstraint
// ----------------------------------------------------------------------------
FixedConstraint::FixedConstraint(VerletPoint* point, const glm::vec3& anchor) : _point(point), _anchor(anchor) {
}
float FixedConstraint::enforce() {
assert(_point != NULL);
// TODO: use fast approximate sqrt here
float distance = glm::distance(_anchor, _point->_position);
_point->_position = _anchor;
return distance;
}
void FixedConstraint::setPoint(VerletPoint* point) {
assert(point);
_point = point;
_point->_mass = MAX_SHAPE_MASS;
}
void FixedConstraint::setAnchor(const glm::vec3& anchor) {
_anchor = anchor;
}
// ----------------------------------------------------------------------------
// DistanceConstraint
// ----------------------------------------------------------------------------
DistanceConstraint::DistanceConstraint(VerletPoint* startPoint, VerletPoint* endPoint) : _distance(-1.0f) {
_points[0] = startPoint;
_points[1] = endPoint;
_distance = glm::distance(_points[0]->_position, _points[1]->_position);
}
DistanceConstraint::DistanceConstraint(const DistanceConstraint& other) {
_distance = other._distance;
_points[0] = other._points[0];
_points[1] = other._points[1];
}
void DistanceConstraint::setDistance(float distance) {
_distance = fabsf(distance);
}
float DistanceConstraint::enforce() {
// TODO: use a fast distance approximation
float newDistance = glm::distance(_points[0]->_position, _points[1]->_position);
glm::vec3 direction(0.0f, 1.0f, 0.0f);
if (newDistance > EPSILON) {
direction = (_points[0]->_position - _points[1]->_position) / newDistance;
}
glm::vec3 center = 0.5f * (_points[0]->_position + _points[1]->_position);
_points[0]->_position = center + (0.5f * _distance) * direction;
_points[1]->_position = center - (0.5f * _distance) * direction;
return glm::abs(newDistance - _distance);
}
// ----------------------------------------------------------------------------
// Ragdoll
// ----------------------------------------------------------------------------
Ragdoll::Ragdoll() {
}
Ragdoll::~Ragdoll() {
clearRagdollConstraintsAndPoints();
}
void Ragdoll::clearRagdollConstraintsAndPoints() {
int numConstraints = _ragdollConstraints.size();
for (int i = 0; i < numConstraints; ++i) {
delete _ragdollConstraints[i];
}
_ragdollConstraints.clear();
_ragdollPoints.clear();
}
float Ragdoll::enforceRagdollConstraints() {
float maxDistance = 0.0f;
const int numConstraints = _ragdollConstraints.size();
for (int i = 0; i < numConstraints; ++i) {
DistanceConstraint* c = static_cast<DistanceConstraint*>(_ragdollConstraints[i]);
//maxDistance = glm::max(maxDistance, _ragdollConstraints[i]->enforce());
maxDistance = glm::max(maxDistance, c->enforce());
}
return maxDistance;
}

View file

@ -0,0 +1,107 @@
//
// Ragdoll.h
// libraries/shared/src
//
// Created by Andrew Meadows 2014.05.30
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_Ragdoll_h
#define hifi_Ragdoll_h
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <QVector>
class Shape;
// TODO: Andrew to move VerletPoint class to its own file
class VerletPoint {
public:
VerletPoint() : _position(0.0f), _lastPosition(0.0f), _mass(1.0f), _accumulatedDelta(0.0f), _numDeltas(0) {}
void accumulateDelta(const glm::vec3& delta);
void applyAccumulatedDelta();
glm::vec3 getAccumulatedDelta() const {
glm::vec3 foo(0.0f);
if (_numDeltas > 0) {
foo = _accumulatedDelta / (float)_numDeltas;
}
return foo;
}
glm::vec3 _position;
glm::vec3 _lastPosition;
float _mass;
private:
glm::vec3 _accumulatedDelta;
int _numDeltas;
};
class Constraint {
public:
Constraint() {}
virtual ~Constraint() {}
/// Enforce contraint by moving relevant points.
/// \return max distance of point movement
virtual float enforce() = 0;
protected:
int _type;
};
class FixedConstraint : public Constraint {
public:
FixedConstraint(VerletPoint* point, const glm::vec3& anchor);
float enforce();
void setPoint(VerletPoint* point);
void setAnchor(const glm::vec3& anchor);
private:
VerletPoint* _point;
glm::vec3 _anchor;
};
class DistanceConstraint : public Constraint {
public:
DistanceConstraint(VerletPoint* startPoint, VerletPoint* endPoint);
DistanceConstraint(const DistanceConstraint& other);
float enforce();
void setDistance(float distance);
float getDistance() const { return _distance; }
private:
float _distance;
VerletPoint* _points[2];
};
class Ragdoll {
public:
Ragdoll();
virtual ~Ragdoll();
virtual void stepRagdollForward(float deltaTime) = 0;
/// \return max distance of point movement
float enforceRagdollConstraints();
// both const and non-const getPoints()
const QVector<VerletPoint>& getRagdollPoints() const { return _ragdollPoints; }
QVector<VerletPoint>& getRagdollPoints() { return _ragdollPoints; }
protected:
void clearRagdollConstraintsAndPoints();
virtual void initRagdollPoints() = 0;
virtual void buildRagdollConstraints() = 0;
QVector<VerletPoint> _ragdollPoints;
QVector<Constraint*> _ragdollConstraints;
};
#endif // hifi_Ragdoll_h

View file

@ -15,47 +15,76 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
class PhysicsEntity;
const float MAX_SHAPE_MASS = 1.0e18f; // something less than sqrt(FLT_MAX)
class Shape {
public:
enum Type{
UNKNOWN_SHAPE = 0,
SPHERE_SHAPE,
CAPSULE_SHAPE,
PLANE_SHAPE,
BOX_SHAPE,
LIST_SHAPE
};
Shape() : _type(UNKNOWN_SHAPE), _boundingRadius(0.f), _position(0.f), _rotation() { }
Shape() : _type(UNKNOWN_SHAPE), _owningEntity(NULL), _boundingRadius(0.f), _translation(0.f), _rotation(), _mass(MAX_SHAPE_MASS) { }
virtual ~Shape() {}
int getType() const { return _type; }
float getBoundingRadius() const { return _boundingRadius; }
const glm::vec3& getPosition() const { return _position; }
const glm::quat& getRotation() const { return _rotation; }
virtual void setPosition(const glm::vec3& position) { _position = position; }
void setEntity(PhysicsEntity* entity) { _owningEntity = entity; }
PhysicsEntity* getEntity() const { return _owningEntity; }
float getBoundingRadius() const { return _boundingRadius; }
virtual const glm::quat& getRotation() const { return _rotation; }
virtual void setRotation(const glm::quat& rotation) { _rotation = rotation; }
virtual void setTranslation(const glm::vec3& translation) { _translation = translation; }
virtual const glm::vec3& getTranslation() const { return _translation; }
virtual void setMass(float mass) { _mass = mass; }
virtual float getMass() const { return _mass; }
virtual bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const = 0;
/// \param penetration of collision
/// \param contactPoint of collision
/// \return the effective mass for the collision
/// For most shapes has side effects: computes and caches the partial Lagrangian coefficients which will be
/// used in the next accumulateDelta() call.
virtual float computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint) { return _mass; }
/// \param relativeMassFactor the final ingredient for partial Lagrangian coefficients from computeEffectiveMass()
/// \param penetration the delta movement
virtual void accumulateDelta(float relativeMassFactor, const glm::vec3& penetration) {}
virtual void applyAccumulatedDelta() {}
/// \return volume of shape in cubic meters
virtual float getVolume() const { return 1.0; }
protected:
// these ctors are protected (used by derived classes only)
Shape(Type type) : _type(type), _boundingRadius(0.f), _position(0.f), _rotation() {}
Shape(Type type) : _type(type), _owningEntity(NULL), _boundingRadius(0.f), _translation(0.f), _rotation() {}
Shape(Type type, const glm::vec3& position)
: _type(type), _boundingRadius(0.f), _position(position), _rotation() {}
: _type(type), _owningEntity(NULL), _boundingRadius(0.f), _translation(position), _rotation() {}
Shape(Type type, const glm::vec3& position, const glm::quat& rotation)
: _type(type), _boundingRadius(0.f), _position(position), _rotation(rotation) {}
: _type(type), _owningEntity(NULL), _boundingRadius(0.f), _translation(position), _rotation(rotation) {}
void setBoundingRadius(float radius) { _boundingRadius = radius; }
int _type;
PhysicsEntity* _owningEntity;
float _boundingRadius;
glm::vec3 _position;
glm::vec3 _translation;
glm::quat _rotation;
float _mass;
};
#endif // hifi_Shape_h

View file

@ -24,7 +24,6 @@
namespace ShapeCollider {
bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) {
// ATM we only have two shape types so we just check every case.
// TODO: make a fast lookup for correct method
int typeA = shapeA->getType();
int typeB = shapeB->getType();
@ -74,7 +73,7 @@ bool collideShapesCoarse(const QVector<const Shape*>& shapesA, const QVector<con
tempCollisions.clear();
foreach (const Shape* shapeA, shapesA) {
foreach (const Shape* shapeB, shapesB) {
ShapeCollider::collideShapes(shapeA, shapeB, tempCollisions);
collideShapes(shapeA, shapeB, tempCollisions);
}
}
if (tempCollisions.size() > 0) {
@ -87,11 +86,52 @@ bool collideShapesCoarse(const QVector<const Shape*>& shapesA, const QVector<con
}
collision._penetration = totalPenetration;
collision._contactPoint = averageContactPoint / (float)(tempCollisions.size());
// there are no valid shape pointers for this collision so we set them NULL
collision._shapeA = NULL;
collision._shapeB = NULL;
return true;
}
return false;
}
bool collideShapeWithShapes(const Shape* shapeA, const QVector<Shape*>& shapes, int startIndex, CollisionList& collisions) {
bool collided = false;
if (shapeA) {
int numShapes = shapes.size();
for (int i = startIndex; i < numShapes; ++i) {
const Shape* shapeB = shapes.at(i);
if (!shapeB) {
continue;
}
if (collideShapes(shapeA, shapeB, collisions)) {
collided = true;
if (collisions.isFull()) {
break;
}
}
}
}
return collided;
}
bool collideShapesWithShapes(const QVector<Shape*>& shapesA, const QVector<Shape*>& shapesB, CollisionList& collisions) {
bool collided = false;
int numShapesA = shapesA.size();
for (int i = 0; i < numShapesA; ++i) {
Shape* shapeA = shapesA.at(i);
if (!shapeA) {
continue;
}
if (collideShapeWithShapes(shapeA, shapesB, 0, collisions)) {
collided = true;
if (collisions.isFull()) {
break;
}
}
}
return collided;
}
bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
int typeA = shapeA->getType();
if (typeA == Shape::SPHERE_SHAPE) {
@ -116,7 +156,7 @@ bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, fl
}
bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionList& collisions) {
glm::vec3 BA = sphereB->getPosition() - sphereA->getPosition();
glm::vec3 BA = sphereB->getTranslation() - sphereA->getTranslation();
float distanceSquared = glm::dot(BA, BA);
float totalRadius = sphereA->getRadius() + sphereB->getRadius();
if (distanceSquared < totalRadius * totalRadius) {
@ -132,10 +172,11 @@ bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, Collis
// penetration points from A into B
CollisionInfo* collision = collisions.getNewCollision();
if (collision) {
collision->_type = COLLISION_TYPE_UNKNOWN;
collision->_penetration = BA * (totalRadius - distance);
// contactPoint is on surface of A
collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * BA;
collision->_contactPoint = sphereA->getTranslation() + sphereA->getRadius() * BA;
collision->_shapeA = sphereA;
collision->_shapeB = sphereB;
return true;
}
}
@ -144,7 +185,7 @@ bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, Collis
bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionList& collisions) {
// find sphereA's closest approach to axis of capsuleB
glm::vec3 BA = capsuleB->getPosition() - sphereA->getPosition();
glm::vec3 BA = capsuleB->getTranslation() - sphereA->getTranslation();
glm::vec3 capsuleAxis;
capsuleB->computeNormalizedAxis(capsuleAxis);
float axialDistance = - glm::dot(BA, capsuleAxis);
@ -179,8 +220,9 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
// penetration points from A into B
collision->_penetration = (totalRadius - radialDistance) * radialAxis; // points from A into B
// contactPoint is on surface of sphereA
collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * radialAxis;
collision->_type = COLLISION_TYPE_UNKNOWN;
collision->_contactPoint = sphereA->getTranslation() + sphereA->getRadius() * radialAxis;
collision->_shapeA = sphereA;
collision->_shapeB = capsuleB;
} else {
// A is on B's axis, so the penetration is undefined...
if (absAxialDistance > capsuleB->getHalfHeight()) {
@ -201,8 +243,9 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
float sign = (axialDistance > 0.0f) ? -1.0f : 1.0f;
collision->_penetration = (sign * (totalRadius + capsuleB->getHalfHeight() - absAxialDistance)) * capsuleAxis;
// contactPoint is on surface of sphereA
collision->_contactPoint = sphereA->getPosition() + (sign * sphereA->getRadius()) * capsuleAxis;
collision->_type = COLLISION_TYPE_UNKNOWN;
collision->_contactPoint = sphereA->getTranslation() + (sign * sphereA->getRadius()) * capsuleAxis;
collision->_shapeA = sphereA;
collision->_shapeB = capsuleB;
}
return true;
}
@ -211,14 +254,15 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, CollisionList& collisions) {
glm::vec3 penetration;
if (findSpherePlanePenetration(sphereA->getPosition(), sphereA->getRadius(), planeB->getCoefficients(), penetration)) {
if (findSpherePlanePenetration(sphereA->getTranslation(), sphereA->getRadius(), planeB->getCoefficients(), penetration)) {
CollisionInfo* collision = collisions.getNewCollision();
if (!collision) {
return false; // collision list is full
}
collision->_penetration = penetration;
collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * glm::normalize(penetration);
collision->_type = COLLISION_TYPE_UNKNOWN;
collision->_contactPoint = sphereA->getTranslation() + sphereA->getRadius() * glm::normalize(penetration);
collision->_shapeA = sphereA;
collision->_shapeB = planeB;
return true;
}
return false;
@ -226,7 +270,7 @@ bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, Collision
bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionList& collisions) {
// find sphereB's closest approach to axis of capsuleA
glm::vec3 AB = capsuleA->getPosition() - sphereB->getPosition();
glm::vec3 AB = capsuleA->getTranslation() - sphereB->getTranslation();
glm::vec3 capsuleAxis;
capsuleA->computeNormalizedAxis(capsuleAxis);
float axialDistance = - glm::dot(AB, capsuleAxis);
@ -242,14 +286,14 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
}
// closestApproach = point on capsuleA's axis that is closest to sphereB's center
glm::vec3 closestApproach = capsuleA->getPosition() + axialDistance * capsuleAxis;
glm::vec3 closestApproach = capsuleA->getTranslation() + axialDistance * capsuleAxis;
if (absAxialDistance > capsuleA->getHalfHeight()) {
// sphere hits capsule on a cap
// --> recompute radialAxis and closestApproach
float sign = (axialDistance > 0.0f) ? 1.0f : -1.0f;
closestApproach = capsuleA->getPosition() + (sign * capsuleA->getHalfHeight()) * capsuleAxis;
radialAxis = closestApproach - sphereB->getPosition();
closestApproach = capsuleA->getTranslation() + (sign * capsuleA->getHalfHeight()) * capsuleAxis;
radialAxis = closestApproach - sphereB->getTranslation();
radialDistance2 = glm::length2(radialAxis);
if (radialDistance2 > totalRadius2) {
return false;
@ -268,7 +312,8 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
collision->_penetration = (radialDistance - totalRadius) * radialAxis; // points from A into B
// contactPoint is on surface of capsuleA
collision->_contactPoint = closestApproach - capsuleA->getRadius() * radialAxis;
collision->_type = COLLISION_TYPE_UNKNOWN;
collision->_shapeA = capsuleA;
collision->_shapeB = sphereB;
} else {
// A is on B's axis, so the penetration is undefined...
if (absAxialDistance > capsuleA->getHalfHeight()) {
@ -289,7 +334,8 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
collision->_penetration = (sign * (totalRadius + capsuleA->getHalfHeight() - absAxialDistance)) * capsuleAxis;
// contactPoint is on surface of sphereA
collision->_contactPoint = closestApproach + (sign * capsuleA->getRadius()) * capsuleAxis;
collision->_type = COLLISION_TYPE_UNKNOWN;
collision->_shapeA = capsuleA;
collision->_shapeB = sphereB;
}
}
return true;
@ -302,8 +348,8 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
capsuleA->computeNormalizedAxis(axisA);
glm::vec3 axisB;
capsuleB->computeNormalizedAxis(axisB);
glm::vec3 centerA = capsuleA->getPosition();
glm::vec3 centerB = capsuleB->getPosition();
glm::vec3 centerA = capsuleA->getTranslation();
glm::vec3 centerB = capsuleB->getTranslation();
// NOTE: The formula for closest approach between two lines is:
// d = [(B - A) . (a - (a.b)b)] / (1 - (a.b)^2)
@ -361,7 +407,8 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
collision->_penetration = BA * (totalRadius - distance);
// contactPoint is on surface of A
collision->_contactPoint = centerA + distanceA * axisA + capsuleA->getRadius() * BA;
collision->_type = COLLISION_TYPE_UNKNOWN;
collision->_shapeA = capsuleA;
collision->_shapeB = capsuleB;
return true;
}
} else {
@ -427,7 +474,8 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
// average the internal pair, and then do the math from centerB
collision->_contactPoint = centerB + (0.5f * (points[1] + points[2])) * axisB
+ (capsuleA->getRadius() - distance) * BA;
collision->_type = COLLISION_TYPE_UNKNOWN;
collision->_shapeA = capsuleA;
collision->_shapeB = capsuleB;
return true;
}
}
@ -447,7 +495,8 @@ bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, Collis
collision->_penetration = penetration;
glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end;
collision->_contactPoint = deepestEnd + capsuleA->getRadius() * glm::normalize(penetration);
collision->_type = COLLISION_TYPE_UNKNOWN;
collision->_shapeA = capsuleA;
collision->_shapeB = planeB;
return true;
}
return false;
@ -455,15 +504,16 @@ bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, Collis
bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, CollisionList& collisions) {
glm::vec3 penetration;
if (findSpherePlanePenetration(sphereB->getPosition(), sphereB->getRadius(), planeA->getCoefficients(), penetration)) {
if (findSpherePlanePenetration(sphereB->getTranslation(), sphereB->getRadius(), planeA->getCoefficients(), penetration)) {
CollisionInfo* collision = collisions.getNewCollision();
if (!collision) {
return false; // collision list is full
}
collision->_penetration = -penetration;
collision->_contactPoint = sphereB->getPosition() +
collision->_contactPoint = sphereB->getTranslation() +
(sphereB->getRadius() / glm::length(penetration) - 1.0f) * penetration;
collision->_type = COLLISION_TYPE_UNKNOWN;
collision->_shapeA = planeA;
collision->_shapeB = sphereB;
return true;
}
return false;
@ -482,7 +532,8 @@ bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, Collis
collision->_penetration = -penetration;
glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end;
collision->_contactPoint = deepestEnd + (capsuleB->getRadius() / glm::length(penetration) - 1.0f) * penetration;
collision->_type = COLLISION_TYPE_UNKNOWN;
collision->_shapeA = planeA;
collision->_shapeB = capsuleB;
return true;
}
return false;
@ -668,15 +719,15 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::
direction /= lengthDirection;
// compute collision details
collision->_type = COLLISION_TYPE_AACUBE;
collision->_floatData = cubeSide;
collision->_vecData = cubeCenter;
collision->_penetration = (halfCubeSide * lengthDirection + sphereRadius - maxBA * glm::dot(BA, direction)) * direction;
collision->_contactPoint = sphereCenter + sphereRadius * direction;
}
collision->_type = COLLISION_TYPE_AACUBE;
collision->_floatData = cubeSide;
collision->_vecData = cubeCenter;
collision->_shapeA = NULL;
collision->_shapeB = NULL;
return true;
} else if (sphereRadius + halfCubeSide > distance) {
// NOTE: for cocentric approximation we collide sphere and cube as two spheres which means
@ -688,9 +739,10 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::
// contactPoint is on surface of A
collision->_contactPoint = sphereCenter + collision->_penetration;
collision->_type = COLLISION_TYPE_AACUBE;
collision->_floatData = cubeSide;
collision->_vecData = cubeCenter;
collision->_shapeA = NULL;
collision->_shapeB = NULL;
return true;
}
}
@ -726,6 +778,8 @@ bool sphereAACube_StarkAngles(const glm::vec3& sphereCenter, float sphereRadius,
collision->_penetration = glm::dot(surfaceAB, direction) * direction;
// contactPoint is on surface of A
collision->_contactPoint = sphereCenter + sphereRadius * direction;
collision->_shapeA = NULL;
collision->_shapeB = NULL;
return true;
}
}
@ -738,6 +792,8 @@ bool sphereAACube_StarkAngles(const glm::vec3& sphereCenter, float sphereRadius,
collision->_penetration = (sphereRadius + 0.5f * cubeSide) * glm::vec3(0.0f, -1.0f, 0.0f);
// contactPoint is on surface of A
collision->_contactPoint = sphereCenter + collision->_penetration;
collision->_shapeA = NULL;
collision->_shapeB = NULL;
return true;
}
}
@ -746,21 +802,21 @@ bool sphereAACube_StarkAngles(const glm::vec3& sphereCenter, float sphereRadius,
*/
bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
return sphereAACube(sphereA->getPosition(), sphereA->getRadius(), cubeCenter, cubeSide, collisions);
return sphereAACube(sphereA->getTranslation(), sphereA->getRadius(), cubeCenter, cubeSide, collisions);
}
bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
// find nerest approach of capsule line segment to cube
glm::vec3 capsuleAxis;
capsuleA->computeNormalizedAxis(capsuleAxis);
float offset = glm::dot(cubeCenter - capsuleA->getPosition(), capsuleAxis);
float offset = glm::dot(cubeCenter - capsuleA->getTranslation(), capsuleAxis);
float halfHeight = capsuleA->getHalfHeight();
if (offset > halfHeight) {
offset = halfHeight;
} else if (offset < -halfHeight) {
offset = -halfHeight;
}
glm::vec3 nearestApproach = capsuleA->getPosition() + offset * capsuleAxis;
glm::vec3 nearestApproach = capsuleA->getTranslation() + offset * capsuleAxis;
// collide nearest approach like a sphere at that point
return sphereAACube(nearestApproach, capsuleA->getRadius(), cubeCenter, cubeSide, collisions);
}

View file

@ -12,6 +12,8 @@
#ifndef hifi_ShapeCollider_h
#define hifi_ShapeCollider_h
#include <QVector>
#include "CapsuleShape.h"
#include "CollisionInfo.h"
#include "ListShape.h"
@ -33,6 +35,9 @@ namespace ShapeCollider {
/// \return true if any shapes collide
bool collideShapesCoarse(const QVector<const Shape*>& shapesA, const QVector<const Shape*>& shapesB, CollisionInfo& collision);
bool collideShapeWithShapes(const Shape* shapeA, const QVector<Shape*>& shapes, int startIndex, CollisionList& collisions);
bool collideShapesWithShapes(const QVector<Shape*>& shapesA, const QVector<Shape*>& shapesB, CollisionList& collisions);
/// \param shapeA a pointer to a shape (cannot be NULL)
/// \param cubeCenter center of cube
/// \param cubeSide lenght of side of cube

View file

@ -17,14 +17,14 @@ bool SphereShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3
float r2 = _boundingRadius * _boundingRadius;
// compute closest approach (CA)
float a = glm::dot(_position - rayStart, rayDirection); // a = distance from ray-start to CA
float b2 = glm::distance2(_position, rayStart + a * rayDirection); // b2 = squared distance from sphere-center to CA
float a = glm::dot(_translation - rayStart, rayDirection); // a = distance from ray-start to CA
float b2 = glm::distance2(_translation, rayStart + a * rayDirection); // b2 = squared distance from sphere-center to CA
if (b2 > r2) {
// ray does not hit sphere
return false;
}
float c = sqrtf(r2 - b2); // c = distance from CA to sphere surface along rayDirection
float d2 = glm::distance2(rayStart, _position); // d2 = squared distance from sphere-center to ray-start
float d2 = glm::distance2(rayStart, _translation); // d2 = squared distance from sphere-center to ray-start
if (a < 0.0f) {
// ray points away from sphere-center
if (d2 > r2) {

View file

@ -14,6 +14,8 @@
#include "Shape.h"
#include "SharedUtil.h"
class SphereShape : public Shape {
public:
SphereShape() : Shape(Shape::SPHERE_SHAPE) {}
@ -26,11 +28,15 @@ public:
_boundingRadius = radius;
}
virtual ~SphereShape() {}
float getRadius() const { return _boundingRadius; }
void setRadius(float radius) { _boundingRadius = radius; }
bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const;
float getVolume() const { return 1.3333333333f * PI * _boundingRadius * _boundingRadius * _boundingRadius; }
};
#endif // hifi_SphereShape_h

View file

@ -0,0 +1,166 @@
//
// VerletCapsuleShape.cpp
// libraries/shared/src
//
// Created by Andrew Meadows on 2014.06.16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "VerletCapsuleShape.h"
#include "Ragdoll.h" // for VerletPoint
#include "SharedUtil.h"
VerletCapsuleShape::VerletCapsuleShape(VerletPoint* startPoint, VerletPoint* endPoint) :
CapsuleShape(), _startPoint(startPoint), _endPoint(endPoint), _startLagrangeCoef(0.5f), _endLagrangeCoef(0.5f) {
assert(startPoint);
assert(endPoint);
_halfHeight = 0.5f * glm::distance(_startPoint->_position, _endPoint->_position);
updateBoundingRadius();
}
VerletCapsuleShape::VerletCapsuleShape(float radius, VerletPoint* startPoint, VerletPoint* endPoint) :
CapsuleShape(radius, 1.0f), _startPoint(startPoint), _endPoint(endPoint),
_startLagrangeCoef(0.5f), _endLagrangeCoef(0.5f) {
assert(startPoint);
assert(endPoint);
_halfHeight = 0.5f * glm::distance(_startPoint->_position, _endPoint->_position);
updateBoundingRadius();
}
const glm::quat& VerletCapsuleShape::getRotation() const {
// NOTE: The "rotation" of this shape must be computed on the fly,
// which makes this method MUCH more more expensive than you might expect.
glm::vec3 axis;
computeNormalizedAxis(axis);
VerletCapsuleShape* thisCapsule = const_cast<VerletCapsuleShape*>(this);
thisCapsule->_rotation = computeNewRotation(axis);
return _rotation;
}
void VerletCapsuleShape::setRotation(const glm::quat& rotation) {
// NOTE: this method will update the verlet points, which is probably not
// what you want to do. Only call this method if you know what you're doing.
// update points such that they have the same center but a different axis
glm::vec3 center = getTranslation();
float halfHeight = getHalfHeight();
glm::vec3 axis = rotation * DEFAULT_CAPSULE_AXIS;
_startPoint->_position = center - halfHeight * axis;
_endPoint->_position = center + halfHeight * axis;
}
void VerletCapsuleShape::setTranslation(const glm::vec3& position) {
// NOTE: this method will update the verlet points, which is probably not
// what you want to do. Only call this method if you know what you're doing.
// update the points such that their center is at position
glm::vec3 movement = position - getTranslation();
_startPoint->_position += movement;
_endPoint->_position += movement;
}
const glm::vec3& VerletCapsuleShape::getTranslation() const {
// the "translation" of this shape must be computed on the fly
VerletCapsuleShape* thisCapsule = const_cast<VerletCapsuleShape*>(this);
thisCapsule->_translation = 0.5f * (_startPoint->_position + _endPoint->_position);
return _translation;
}
float VerletCapsuleShape::computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint) {
glm::vec3 startLeg = _startPoint->_position - contactPoint;
glm::vec3 endLeg = _endPoint->_position - contactPoint;
// TODO: use fast approximate distance calculations here
float startLength = glm::length(startLeg);
float endlength = glm::length(endLeg);
// The raw coefficient is proportional to the other leg's length multiplied by the dot-product
// of the penetration and this leg direction. We don't worry about the common penetration length
// because it is normalized out later.
float startCoef = glm::abs(glm::dot(startLeg, penetration)) * endlength / (startLength + EPSILON);
float endCoef = glm::abs(glm::dot(endLeg, penetration)) * startLength / (endlength + EPSILON);
float maxCoef = glm::max(startCoef, endCoef);
if (maxCoef > EPSILON) {
// One of these coeficients will be 1.0, the other will be less -->
// one endpoint will move the full amount while the other will move less.
_startLagrangeCoef = startCoef / maxCoef;
_endLagrangeCoef = endCoef / maxCoef;
assert(!glm::isnan(_startLagrangeCoef));
assert(!glm::isnan(_startLagrangeCoef));
} else {
// The coefficients are the same --> the collision will move both equally
// as if the object were solid.
_startLagrangeCoef = 1.0f;
_endLagrangeCoef = 1.0f;
}
// the effective mass is the weighted sum of the two endpoints
return _startLagrangeCoef * _startPoint->_mass + _endLagrangeCoef * _endPoint->_mass;
}
void VerletCapsuleShape::accumulateDelta(float relativeMassFactor, const glm::vec3& penetration) {
assert(!glm::isnan(relativeMassFactor));
_startPoint->accumulateDelta(relativeMassFactor * _startLagrangeCoef * penetration);
_endPoint->accumulateDelta(relativeMassFactor * _endLagrangeCoef * penetration);
}
void VerletCapsuleShape::applyAccumulatedDelta() {
_startPoint->applyAccumulatedDelta();
_endPoint->applyAccumulatedDelta();
}
// virtual
float VerletCapsuleShape::getHalfHeight() const {
return 0.5f * glm::distance(_startPoint->_position, _endPoint->_position);
}
// virtual
void VerletCapsuleShape::getStartPoint(glm::vec3& startPoint) const {
startPoint = _startPoint->_position;
}
// virtual
void VerletCapsuleShape::getEndPoint(glm::vec3& endPoint) const {
endPoint = _endPoint->_position;
}
// virtual
void VerletCapsuleShape::computeNormalizedAxis(glm::vec3& axis) const {
glm::vec3 unormalizedAxis = _endPoint->_position - _startPoint->_position;
float fullLength = glm::length(unormalizedAxis);
if (fullLength > EPSILON) {
axis = unormalizedAxis / fullLength;
} else {
// the axis is meaningless, but we fill it with a normalized direction
// just in case the calling context assumes it really is normalized.
axis = glm::vec3(0.0f, 1.0f, 0.0f);
}
}
// virtual
void VerletCapsuleShape::setHalfHeight(float halfHeight) {
// push points along axis so they are 2*halfHeight apart
glm::vec3 center = getTranslation();
glm::vec3 axis;
computeNormalizedAxis(axis);
_startPoint->_position = center - halfHeight * axis;
_endPoint->_position = center + halfHeight * axis;
_boundingRadius = _radius + halfHeight;
}
// virtual
void VerletCapsuleShape::setRadiusAndHalfHeight(float radius, float halfHeight) {
_radius = radius;
setHalfHeight(halfHeight);
}
// virtual
void VerletCapsuleShape::setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint) {
_startPoint->_position = startPoint;
_endPoint->_position = endPoint;
updateBoundingRadius();
}

View file

@ -0,0 +1,83 @@
//
// VerletCapsuleShape.h
// libraries/shared/src
//
// Created by Andrew Meadows on 2014.06.16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_VerletCapsuleShape_h
#define hifi_VerletCapsuleShape_h
#include "CapsuleShape.h"
// The VerletCapsuleShape is similar to a regular CapsuleShape, except it keeps a pointer
// to its endpoints which are owned by some other data structure (a verlet simulation system).
// This makes it easier for the points to be moved around by constraints in the system
// as well as collisions with the shape, however it has some drawbacks:
//
// (1) The Shape::_translation and ::_rotation data members are not used (wasted)
//
// (2) A VerletShape doesn't own the points that it uses, so you must be careful not to
// leave dangling pointers around.
//
// (3) Some const methods of VerletCapsuleShape are much more expensive than you might think.
// For example getHalfHeight() and setHalfHeight() methods must do extra computation. In
// particular setRotation() is significantly more expensive than for the CapsuleShape.
// Not too expensive to use when setting up shapes, but you woudln't want to use it deep
// down in a hot simulation loop, such as when processing collision results. Best to
// just let the verlet simulation do its thing and not try to constantly force a rotation.
class VerletPoint;
class VerletCapsuleShape : public CapsuleShape {
public:
VerletCapsuleShape(VerletPoint* startPoint, VerletPoint* endPoint);
VerletCapsuleShape(float radius, VerletPoint* startPoint, VerletPoint* endPoint);
// virtual overrides from Shape
const glm::quat& getRotation() const;
void setRotation(const glm::quat& rotation);
void setTranslation(const glm::vec3& position);
const glm::vec3& getTranslation() const;
float computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint);
void accumulateDelta(float relativeMassFactor, const glm::vec3& penetration);
void applyAccumulatedDelta();
//float getRadius() const { return _radius; }
virtual float getHalfHeight() const;
/// \param[out] startPoint is the center of start cap
void getStartPoint(glm::vec3& startPoint) const;
/// \param[out] endPoint is the center of the end cap
void getEndPoint(glm::vec3& endPoint) const;
/// \param[out] axis is a normalized vector that points from start to end
void computeNormalizedAxis(glm::vec3& axis) const;
//void setRadius(float radius);
void setHalfHeight(float halfHeight);
void setRadiusAndHalfHeight(float radius, float halfHeight);
void setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint);
//void assignEndPoints(glm::vec3* startPoint, glm::vec3* endPoint);
protected:
// NOTE: VerletCapsuleShape does NOT own the data in its points.
VerletPoint* _startPoint;
VerletPoint* _endPoint;
// The LagrangeCoef's are numerical weights for distributing collision movement
// between the relevant VerletPoints associated with this shape. They are functions
// of the movement parameters and are computed (and cached) in computeEffectiveMass()
// and then used in the subsequent accumulateDelta().
float _startLagrangeCoef;
float _endLagrangeCoef;
};
#endif // hifi_VerletCapsuleShape_h

View file

@ -0,0 +1,50 @@
//
// VerletSphereShape.cpp
// libraries/shared/src
//
// Created by Andrew Meadows on 2014.06.16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "VerletSphereShape.h"
#include "Ragdoll.h" // for VerletPoint
VerletSphereShape::VerletSphereShape(VerletPoint* centerPoint) : SphereShape() {
assert(centerPoint);
_point = centerPoint;
}
VerletSphereShape::VerletSphereShape(float radius, VerletPoint* centerPoint) : SphereShape(radius) {
assert(centerPoint);
_point = centerPoint;
}
// virtual from Shape class
void VerletSphereShape::setTranslation(const glm::vec3& position) {
_point->_position = position;
_point->_lastPosition = position;
}
// virtual from Shape class
const glm::vec3& VerletSphereShape::getTranslation() const {
return _point->_position;
}
// virtual
float VerletSphereShape::computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint) {
return _point->_mass;
}
// virtual
void VerletSphereShape::accumulateDelta(float relativeMassFactor, const glm::vec3& penetration) {
_point->accumulateDelta(relativeMassFactor * penetration);
}
// virtual
void VerletSphereShape::applyAccumulatedDelta() {
_point->applyAccumulatedDelta();
}

View file

@ -0,0 +1,47 @@
//
// VerletSphereShape.h
// libraries/shared/src
//
// Created by Andrew Meadows on 2014.06.16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_VerletSphereShape_h
#define hifi_VerletSphereShape_h
#include "SphereShape.h"
// The VerletSphereShape is similar to a regular SphereShape, except it keeps a pointer
// to its center which is owned by some other data structure (a verlet simulation system).
// This makes it easier for the points to be moved around by constraints in the system
// as well as collisions with the shape, however it has some drawbacks:
//
// (1) The Shape::_translation data member is not used (wasted)
//
// (2) A VerletShape doesn't own the points that it uses, so you must be careful not to
// leave dangling pointers around.
class VerletPoint;
class VerletSphereShape : public SphereShape {
public:
VerletSphereShape(VerletPoint* point);
VerletSphereShape(float radius, VerletPoint* centerPoint);
// virtual overrides from Shape
void setTranslation(const glm::vec3& position);
const glm::vec3& getTranslation() const;
float computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint);
void accumulateDelta(float relativeMassFactor, const glm::vec3& penetration);
void applyAccumulatedDelta();
protected:
// NOTE: VerletSphereShape does NOT own its _point
VerletPoint* _point;
};
#endif // hifi_VerletSphereShape_h

View file

@ -30,15 +30,20 @@ include_glm(${TARGET_NAME} ${ROOT_DIR})
# link in the shared libraries
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(animation ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(fbx ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(networking ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(models ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(networking ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(animation ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(fbx ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
IF (WIN32)
# add a definition for ssize_t so that windows doesn't bail
add_definitions(-Dssize_t=long)
#target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
target_link_libraries(${TARGET_NAME} wsock32.lib)
ENDIF(WIN32)

View file

@ -14,17 +14,16 @@
#include <QDebug>
#include <Octree.h>
#include <ModelItem.h>
#include <ModelTree.h>
#include <ModelTreeElement.h>
#include <Octree.h>
#include <OctreeConstants.h>
#include <PropertyFlags.h>
#include <SharedUtil.h>
#include "ModelTests.h"
void ModelTests::modelTreeTests(bool verbose) {
int testsTaken = 0;
int testsPassed = 0;

View file

@ -123,8 +123,8 @@ void ShapeColliderTests::sphereTouchesSphere() {
}
// contactPoint is on surface of sphereA
glm::vec3 AtoB = sphereB.getPosition() - sphereA.getPosition();
glm::vec3 expectedContactPoint = sphereA.getPosition() + radiusA * glm::normalize(AtoB);
glm::vec3 AtoB = sphereB.getTranslation() - sphereA.getTranslation();
glm::vec3 expectedContactPoint = sphereA.getTranslation() + radiusA * glm::normalize(AtoB);
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
@ -153,8 +153,8 @@ void ShapeColliderTests::sphereTouchesSphere() {
}
// contactPoint is on surface of sphereA
glm::vec3 BtoA = sphereA.getPosition() - sphereB.getPosition();
glm::vec3 expectedContactPoint = sphereB.getPosition() + radiusB * glm::normalize(BtoA);
glm::vec3 BtoA = sphereA.getTranslation() - sphereB.getTranslation();
glm::vec3 expectedContactPoint = sphereB.getTranslation() + radiusB * glm::normalize(BtoA);
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
@ -182,7 +182,7 @@ void ShapeColliderTests::sphereMissesCapsule() {
glm::quat rotation = glm::angleAxis(angle, axis);
glm::vec3 translation(15.1f, -27.1f, -38.6f);
capsuleB.setRotation(rotation);
capsuleB.setPosition(translation);
capsuleB.setTranslation(translation);
CollisionList collisions(16);
@ -193,7 +193,7 @@ void ShapeColliderTests::sphereMissesCapsule() {
for (int i = 0; i < numberOfSteps; ++i) {
// translate sphereA into world-frame
glm::vec3 localPosition = localStartPosition + ((float)i * delta) * yAxis;
sphereA.setPosition(rotation * localPosition + translation);
sphereA.setTranslation(rotation * localPosition + translation);
// sphereA agains capsuleB
if (ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
@ -236,7 +236,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
int numCollisions = 0;
{ // sphereA collides with capsuleB's cylindrical wall
sphereA.setPosition(radialOffset * xAxis);
sphereA.setTranslation(radialOffset * xAxis);
if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
{
@ -258,7 +258,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
}
// contactPoint is on surface of sphereA
glm::vec3 expectedContactPoint = sphereA.getPosition() - radiusA * xAxis;
glm::vec3 expectedContactPoint = sphereA.getTranslation() - radiusA * xAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
@ -287,8 +287,8 @@ void ShapeColliderTests::sphereTouchesCapsule() {
}
// contactPoint is on surface of capsuleB
glm::vec3 BtoA = sphereA.getPosition() - capsuleB.getPosition();
glm::vec3 closestApproach = capsuleB.getPosition() + glm::dot(BtoA, yAxis) * yAxis;
glm::vec3 BtoA = sphereA.getTranslation() - capsuleB.getTranslation();
glm::vec3 closestApproach = capsuleB.getTranslation() + glm::dot(BtoA, yAxis) * yAxis;
expectedContactPoint = closestApproach + radiusB * glm::normalize(BtoA - closestApproach);
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
@ -299,7 +299,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
}
{ // sphereA hits end cap at axis
glm::vec3 axialOffset = (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
sphereA.setPosition(axialOffset * yAxis);
sphereA.setTranslation(axialOffset * yAxis);
if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
{
@ -321,7 +321,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
}
// contactPoint is on surface of sphereA
glm::vec3 expectedContactPoint = sphereA.getPosition() - radiusA * yAxis;
glm::vec3 expectedContactPoint = sphereA.getTranslation() - radiusA * yAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
@ -362,7 +362,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
}
{ // sphereA hits start cap at axis
glm::vec3 axialOffset = - (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
sphereA.setPosition(axialOffset * yAxis);
sphereA.setTranslation(axialOffset * yAxis);
if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
{
@ -384,7 +384,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
}
// contactPoint is on surface of sphereA
glm::vec3 expectedContactPoint = sphereA.getPosition() + radiusA * yAxis;
glm::vec3 expectedContactPoint = sphereA.getTranslation() + radiusA * yAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
@ -441,12 +441,12 @@ void ShapeColliderTests::capsuleMissesCapsule() {
float totalHalfLength = totalRadius + halfHeightA + halfHeightB;
CapsuleShape capsuleA(radiusA, halfHeightA);
CapsuleShape capsuleB(radiusA, halfHeightA);
CapsuleShape capsuleB(radiusB, halfHeightB);
CollisionList collisions(16);
// side by side
capsuleB.setPosition((1.01f * totalRadius) * xAxis);
capsuleB.setTranslation((1.01f * totalRadius) * xAxis);
if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
@ -461,7 +461,7 @@ void ShapeColliderTests::capsuleMissesCapsule() {
}
// end to end
capsuleB.setPosition((1.01f * totalHalfLength) * xAxis);
capsuleB.setTranslation((1.01f * totalHalfLength) * xAxis);
if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
@ -478,7 +478,7 @@ void ShapeColliderTests::capsuleMissesCapsule() {
// rotate B and move it to the side
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
capsuleB.setRotation(rotation);
capsuleB.setPosition((1.01f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
capsuleB.setTranslation((1.01f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
@ -516,7 +516,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
int numCollisions = 0;
{ // side by side
capsuleB.setPosition((0.99f * totalRadius) * xAxis);
capsuleB.setTranslation((0.99f * totalRadius) * xAxis);
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
@ -536,7 +536,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
}
{ // end to end
capsuleB.setPosition((0.99f * totalHalfLength) * yAxis);
capsuleB.setTranslation((0.99f * totalHalfLength) * yAxis);
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
@ -559,7 +559,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
{ // rotate B and move it to the side
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
capsuleB.setRotation(rotation);
capsuleB.setPosition((0.99f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
capsuleB.setTranslation((0.99f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
@ -584,7 +584,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
capsuleB.setRotation(rotation);
glm::vec3 positionB = ((totalRadius + capsuleB.getHalfHeight()) - overlap) * xAxis;
capsuleB.setPosition(positionB);
capsuleB.setTranslation(positionB);
// capsuleA vs capsuleB
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
@ -605,7 +605,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
<< " actual = " << collision->_penetration;
}
glm::vec3 expectedContactPoint = capsuleA.getPosition() + radiusA * xAxis;
glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * xAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
@ -633,7 +633,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
<< std::endl;
}
expectedContactPoint = capsuleB.getPosition() - (radiusB + halfHeightB) * xAxis;
expectedContactPoint = capsuleB.getTranslation() - (radiusB + halfHeightB) * xAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
@ -649,7 +649,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
capsuleB.setRotation(rotation);
glm::vec3 positionB = (totalRadius - overlap) * zAxis + shift * yAxis;
capsuleB.setPosition(positionB);
capsuleB.setTranslation(positionB);
// capsuleA vs capsuleB
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
@ -671,7 +671,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
<< std::endl;
}
glm::vec3 expectedContactPoint = capsuleA.getPosition() + radiusA * zAxis + shift * yAxis;
glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * zAxis + shift * yAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
@ -708,7 +708,7 @@ void ShapeColliderTests::sphereTouchesAACubeFaces() {
float overlap = 0.25f;
float sphereOffset = 0.5f * cubeSide + sphereRadius - overlap;
sphereCenter = cubeCenter + sphereOffset * axis;
sphere.setPosition(sphereCenter);
sphere.setTranslation(sphereCenter);
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube. axis = " << axis << std::endl;
@ -741,7 +741,7 @@ void ShapeColliderTests::sphereTouchesAACubeFaces() {
float overlap = 1.25f * sphereRadius;
float sphereOffset = 0.5f * cubeSide + sphereRadius - overlap;
sphereCenter = cubeCenter + sphereOffset * axis;
sphere.setPosition(sphereCenter);
sphere.setTranslation(sphereCenter);
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube."
@ -815,7 +815,7 @@ void ShapeColliderTests::sphereTouchesAACubeEdges() {
float overlap = 0.25f;
sphereCenter = cubeCenter + (lengthAxis * 0.5f * cubeSide + sphereRadius - overlap) * axis;
sphere.setPosition(sphereCenter);
sphere.setTranslation(sphereCenter);
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube. axis = " << axis << std::endl;
@ -857,42 +857,42 @@ void ShapeColliderTests::sphereMissesAACube() {
// top
sphereCenter = cubeCenter + sphereOffset * yAxis;
sphere.setPosition(sphereCenter);
sphere.setTranslation(sphereCenter);
if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
}
// bottom
sphereCenter = cubeCenter - sphereOffset * yAxis;
sphere.setPosition(sphereCenter);
sphere.setTranslation(sphereCenter);
if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
}
// left
sphereCenter = cubeCenter + sphereOffset * xAxis;
sphere.setPosition(sphereCenter);
sphere.setTranslation(sphereCenter);
if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
}
// right
sphereCenter = cubeCenter - sphereOffset * xAxis;
sphere.setPosition(sphereCenter);
sphere.setTranslation(sphereCenter);
if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
}
// forward
sphereCenter = cubeCenter + sphereOffset * zAxis;
sphere.setPosition(sphereCenter);
sphere.setTranslation(sphereCenter);
if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
}
// back
sphereCenter = cubeCenter - sphereOffset * zAxis;
sphere.setPosition(sphereCenter);
sphere.setTranslation(sphereCenter);
if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
}
@ -955,7 +955,7 @@ void ShapeColliderTests::rayHitsSphere() {
rayDirection = rotation * unrotatedRayDirection;
sphere.setRadius(radius);
sphere.setPosition(rotation * translation);
sphere.setTranslation(rotation * translation);
float distance = FLT_MAX;
if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) {
@ -994,7 +994,7 @@ void ShapeColliderTests::rayBarelyHitsSphere() {
rayStart = rotation * (rayStart + translation);
rayDirection = rotation * rayDirection;
sphere.setPosition(rotation * translation);
sphere.setTranslation(rotation * translation);
// ...and test again
distance = FLT_MAX;
@ -1032,7 +1032,7 @@ void ShapeColliderTests::rayBarelyMissesSphere() {
rayStart = rotation * (rayStart + translation);
rayDirection = rotation * rayDirection;
sphere.setPosition(rotation * translation);
sphere.setTranslation(rotation * translation);
// ...and test again
distance = FLT_MAX;
@ -1183,10 +1183,10 @@ void ShapeColliderTests::rayMissesCapsule() {
void ShapeColliderTests::rayHitsPlane() {
// make a simple plane
float planeDistanceFromOrigin = 3.579;
float planeDistanceFromOrigin = 3.579f;
glm::vec3 planePosition(0.0f, planeDistanceFromOrigin, 0.0f);
PlaneShape plane;
plane.setPosition(planePosition);
plane.setTranslation(planePosition);
// make a simple ray
float startDistance = 1.234f;
@ -1209,7 +1209,7 @@ void ShapeColliderTests::rayHitsPlane() {
glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
glm::quat rotation = glm::angleAxis(angle, axis);
plane.setPosition(rotation * planePosition);
plane.setTranslation(rotation * planePosition);
plane.setRotation(rotation);
rayStart = rotation * rayStart;
rayDirection = rotation * rayDirection;
@ -1228,10 +1228,10 @@ void ShapeColliderTests::rayHitsPlane() {
void ShapeColliderTests::rayMissesPlane() {
// make a simple plane
float planeDistanceFromOrigin = 3.579;
float planeDistanceFromOrigin = 3.579f;
glm::vec3 planePosition(0.0f, planeDistanceFromOrigin, 0.0f);
PlaneShape plane;
plane.setPosition(planePosition);
plane.setTranslation(planePosition);
{ // parallel rays should miss
float startDistance = 1.234f;
@ -1251,7 +1251,7 @@ void ShapeColliderTests::rayMissesPlane() {
glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
glm::quat rotation = glm::angleAxis(angle, axis);
plane.setPosition(rotation * planePosition);
plane.setTranslation(rotation * planePosition);
plane.setRotation(rotation);
rayStart = rotation * rayStart;
rayDirection = rotation * rayDirection;
@ -1283,7 +1283,7 @@ void ShapeColliderTests::rayMissesPlane() {
glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
glm::quat rotation = glm::angleAxis(angle, axis);
plane.setPosition(rotation * planePosition);
plane.setTranslation(rotation * planePosition);
plane.setRotation(rotation);
rayStart = rotation * rayStart;
rayDirection = rotation * rayDirection;

View file

@ -0,0 +1,769 @@
//
// VerletShapeTests.cpp
// tests/physics/src
//
// Created by Andrew Meadows on 02/21/2014.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
//#include <stdio.h>
#include <iostream>
#include <math.h>
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <CollisionInfo.h>
#include <Ragdoll.h> // for VerletPoint
#include <ShapeCollider.h>
#include <SharedUtil.h>
#include <VerletCapsuleShape.h>
#include <VerletSphereShape.h>
#include <StreamUtils.h>
#include "VerletShapeTests.h"
const glm::vec3 origin(0.0f);
static const glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
static const glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
static const glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
void VerletShapeTests::setSpherePosition() {
float radius = 1.0f;
glm::vec3 offset(1.23f, 4.56f, 7.89f);
VerletPoint point;
VerletSphereShape sphere(radius, &point);
point._position = glm::vec3(0.f);
float d = glm::distance(glm::vec3(0.0f), sphere.getTranslation());
if (d != 0.0f) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should be at origin" << std::endl;
}
point._position = offset;
d = glm::distance(glm::vec3(0.0f), sphere.getTranslation());
if (d != glm::length(offset)) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should be at offset" << std::endl;
}
}
void VerletShapeTests::sphereMissesSphere() {
// non-overlapping spheres of unequal size
float radiusA = 7.0f;
float radiusB = 3.0f;
float alpha = 1.2f;
float beta = 1.3f;
glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f));
float offsetDistance = alpha * radiusA + beta * radiusB;
// create points for the sphere centers
VerletPoint points[2];
// give pointers to the spheres
VerletSphereShape sphereA(radiusA, (points + 0));
VerletSphereShape sphereB(radiusB, (points + 1));
// set the positions of the spheres by slamming the points directly
points[0]._position = origin;
points[1]._position = offsetDistance * offsetDirection;
CollisionList collisions(16);
// collide A to B...
{
bool touching = ShapeCollider::collideShapes(&sphereA, &sphereB, collisions);
if (touching) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
}
}
// collide B to A...
{
bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions);
if (touching) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
}
}
// also test shapeShape
{
bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions);
if (touching) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
}
}
if (collisions.size() > 0) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected empty collision list but size is " << collisions.size()
<< std::endl;
}
}
void VerletShapeTests::sphereTouchesSphere() {
// overlapping spheres of unequal size
float radiusA = 7.0f;
float radiusB = 3.0f;
float alpha = 0.2f;
float beta = 0.3f;
glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f));
float offsetDistance = alpha * radiusA + beta * radiusB;
float expectedPenetrationDistance = (1.0f - alpha) * radiusA + (1.0f - beta) * radiusB;
glm::vec3 expectedPenetration = expectedPenetrationDistance * offsetDirection;
// create two points for the sphere centers
VerletPoint points[2];
// give pointers to the spheres
VerletSphereShape sphereA(radiusA, points+0);
VerletSphereShape sphereB(radiusB, points+1);
// set the positions of the spheres by slamming the points directly
points[0]._position = origin;
points[1]._position = offsetDistance * offsetDirection;
CollisionList collisions(16);
int numCollisions = 0;
// collide A to B...
{
bool touching = ShapeCollider::collideShapes(&sphereA, &sphereB, collisions);
if (!touching) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphereA and sphereB should touch" << std::endl;
} else {
++numCollisions;
}
// verify state of collisions
if (numCollisions != collisions.size()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected collisions size of " << numCollisions << " but actual size is " << collisions.size()
<< std::endl;
}
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
if (!collision) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: null collision" << std::endl;
}
// penetration points from sphereA into sphereB
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad penetration: expected = " << expectedPenetration
<< " actual = " << collision->_penetration;
}
// contactPoint is on surface of sphereA
glm::vec3 AtoB = sphereB.getTranslation() - sphereA.getTranslation();
glm::vec3 expectedContactPoint = sphereA.getTranslation() + radiusA * glm::normalize(AtoB);
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
<< " actual = " << collision->_contactPoint;
}
}
// collide B to A...
{
bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions);
if (!touching) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphereA and sphereB should touch" << std::endl;
} else {
++numCollisions;
}
// penetration points from sphereA into sphereB
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
float inaccuracy = glm::length(collision->_penetration + expectedPenetration);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad penetration: expected = " << expectedPenetration
<< " actual = " << collision->_penetration;
}
// contactPoint is on surface of sphereA
glm::vec3 BtoA = sphereA.getTranslation() - sphereB.getTranslation();
glm::vec3 expectedContactPoint = sphereB.getTranslation() + radiusB * glm::normalize(BtoA);
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
<< " actual = " << collision->_contactPoint;
}
}
}
void VerletShapeTests::sphereMissesCapsule() {
// non-overlapping sphere and capsule
float radiusA = 1.5f;
float radiusB = 2.3f;
float totalRadius = radiusA + radiusB;
float halfHeightB = 1.7f;
float axialOffset = totalRadius + 1.1f * halfHeightB;
float radialOffset = 1.2f * radiusA + 1.3f * radiusB;
// create points for the sphere + capsule
VerletPoint points[3];
for (int i = 0; i < 3; ++i) {
points[i]._position = glm::vec3(0.0f);
}
// give the points to the shapes
VerletSphereShape sphereA(radiusA, points);
VerletCapsuleShape capsuleB(radiusB, points+1, points+2);
capsuleB.setHalfHeight(halfHeightB);
// give the capsule some arbitrary transform
float angle = 37.8f;
glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
glm::quat rotation = glm::angleAxis(angle, axis);
glm::vec3 translation(15.1f, -27.1f, -38.6f);
capsuleB.setRotation(rotation);
capsuleB.setTranslation(translation);
CollisionList collisions(16);
// walk sphereA along the local yAxis next to, but not touching, capsuleB
glm::vec3 localStartPosition(radialOffset, axialOffset, 0.0f);
int numberOfSteps = 10;
float delta = 1.3f * (totalRadius + halfHeightB) / (numberOfSteps - 1);
for (int i = 0; i < numberOfSteps; ++i) {
// translate sphereA into world-frame
glm::vec3 localPosition = localStartPosition + ((float)i * delta) * yAxis;
sphereA.setTranslation(rotation * localPosition + translation);
// sphereA agains capsuleB
if (ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphere and capsule should NOT touch"
<< std::endl;
}
// capsuleB against sphereA
if (ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphere and capsule should NOT touch"
<< std::endl;
}
}
if (collisions.size() > 0) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected empty collision list but size is " << collisions.size()
<< std::endl;
}
}
void VerletShapeTests::sphereTouchesCapsule() {
// overlapping sphere and capsule
float radiusA = 2.0f;
float radiusB = 1.0f;
float totalRadius = radiusA + radiusB;
float halfHeightB = 2.0f;
float alpha = 0.5f;
float beta = 0.5f;
float radialOffset = alpha * radiusA + beta * radiusB;
// create points for the sphere + capsule
VerletPoint points[3];
for (int i = 0; i < 3; ++i) {
points[i]._position = glm::vec3(0.0f);
}
// give the points to the shapes
VerletSphereShape sphereA(radiusA, points);
VerletCapsuleShape capsuleB(radiusB, points+1, points+2);
capsuleB.setHalfHeight(halfHeightB);
CollisionList collisions(16);
int numCollisions = 0;
{ // sphereA collides with capsuleB's cylindrical wall
sphereA.setTranslation(radialOffset * xAxis);
if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphere and capsule should touch"
<< std::endl;
} else {
++numCollisions;
}
// penetration points from sphereA into capsuleB
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
glm::vec3 expectedPenetration = (radialOffset - totalRadius) * xAxis;
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad penetration: expected = " << expectedPenetration
<< " actual = " << collision->_penetration;
}
// contactPoint is on surface of sphereA
glm::vec3 expectedContactPoint = sphereA.getTranslation() - radiusA * xAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
<< " actual = " << collision->_contactPoint;
}
// capsuleB collides with sphereA
if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and sphere should touch"
<< std::endl;
} else {
++numCollisions;
}
// penetration points from sphereA into capsuleB
collision = collisions.getCollision(numCollisions - 1);
expectedPenetration = - (radialOffset - totalRadius) * xAxis;
inaccuracy = glm::length(collision->_penetration - expectedPenetration);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad penetration: expected = " << expectedPenetration
<< " actual = " << collision->_penetration;
}
// contactPoint is on surface of capsuleB
glm::vec3 BtoA = sphereA.getTranslation() - capsuleB.getTranslation();
glm::vec3 closestApproach = capsuleB.getTranslation() + glm::dot(BtoA, yAxis) * yAxis;
expectedContactPoint = closestApproach + radiusB * glm::normalize(BtoA - closestApproach);
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
<< " actual = " << collision->_contactPoint;
}
}
{ // sphereA hits end cap at axis
glm::vec3 axialOffset = (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
sphereA.setTranslation(axialOffset * yAxis);
if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphere and capsule should touch"
<< std::endl;
} else {
++numCollisions;
}
// penetration points from sphereA into capsuleB
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
glm::vec3 expectedPenetration = - ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis;
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad penetration: expected = " << expectedPenetration
<< " actual = " << collision->_penetration;
}
// contactPoint is on surface of sphereA
glm::vec3 expectedContactPoint = sphereA.getTranslation() - radiusA * yAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
<< " actual = " << collision->_contactPoint;
}
// capsuleB collides with sphereA
if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and sphere should touch"
<< std::endl;
} else {
++numCollisions;
}
// penetration points from sphereA into capsuleB
collision = collisions.getCollision(numCollisions - 1);
expectedPenetration = ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis;
inaccuracy = glm::length(collision->_penetration - expectedPenetration);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad penetration: expected = " << expectedPenetration
<< " actual = " << collision->_penetration;
}
// contactPoint is on surface of capsuleB
glm::vec3 endPoint;
capsuleB.getEndPoint(endPoint);
expectedContactPoint = endPoint + radiusB * yAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
<< " actual = " << collision->_contactPoint;
}
}
{ // sphereA hits start cap at axis
glm::vec3 axialOffset = - (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
sphereA.setTranslation(axialOffset * yAxis);
if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphere and capsule should touch"
<< std::endl;
} else {
++numCollisions;
}
// penetration points from sphereA into capsuleB
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
glm::vec3 expectedPenetration = ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis;
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad penetration: expected = " << expectedPenetration
<< " actual = " << collision->_penetration;
}
// contactPoint is on surface of sphereA
glm::vec3 expectedContactPoint = sphereA.getTranslation() + radiusA * yAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
<< " actual = " << collision->_contactPoint;
}
// capsuleB collides with sphereA
if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and sphere should touch"
<< std::endl;
} else {
++numCollisions;
}
// penetration points from sphereA into capsuleB
collision = collisions.getCollision(numCollisions - 1);
expectedPenetration = - ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis;
inaccuracy = glm::length(collision->_penetration - expectedPenetration);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad penetration: expected = " << expectedPenetration
<< " actual = " << collision->_penetration;
}
// contactPoint is on surface of capsuleB
glm::vec3 startPoint;
capsuleB.getStartPoint(startPoint);
expectedContactPoint = startPoint - radiusB * yAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
<< " actual = " << collision->_contactPoint;
}
}
if (collisions.size() != numCollisions) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected " << numCollisions << " collisions but actual number is " << collisions.size()
<< std::endl;
}
}
void VerletShapeTests::capsuleMissesCapsule() {
// non-overlapping capsules
float radiusA = 2.0f;
float halfHeightA = 3.0f;
float radiusB = 3.0f;
float halfHeightB = 4.0f;
float totalRadius = radiusA + radiusB;
float totalHalfLength = totalRadius + halfHeightA + halfHeightB;
// create points for the shapes
VerletPoint points[4];
for (int i = 0; i < 4; ++i) {
points[i]._position = glm::vec3(0.0f);
}
// give the points to the shapes
VerletCapsuleShape capsuleA(radiusA, points+0, points+1);
VerletCapsuleShape capsuleB(radiusB, points+2, points+3);
capsuleA.setHalfHeight(halfHeightA);
capsuleA.setHalfHeight(halfHeightB);
CollisionList collisions(16);
// side by side
capsuleB.setTranslation((1.01f * totalRadius) * xAxis);
if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should NOT touch"
<< std::endl;
}
if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should NOT touch"
<< std::endl;
}
// end to end
capsuleB.setTranslation((1.01f * totalHalfLength) * xAxis);
if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should NOT touch"
<< std::endl;
}
if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should NOT touch"
<< std::endl;
}
// rotate B and move it to the side
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
capsuleB.setRotation(rotation);
capsuleB.setTranslation((1.01f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should NOT touch"
<< std::endl;
}
if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should NOT touch"
<< std::endl;
}
if (collisions.size() > 0) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected empty collision list but size is " << collisions.size()
<< std::endl;
}
}
void VerletShapeTests::capsuleTouchesCapsule() {
// overlapping capsules
float radiusA = 2.0f;
float halfHeightA = 3.0f;
float radiusB = 3.0f;
float halfHeightB = 4.0f;
float totalRadius = radiusA + radiusB;
float totalHalfLength = totalRadius + halfHeightA + halfHeightB;
// create points for the shapes
VerletPoint points[4];
for (int i = 0; i < 4; ++i) {
points[i]._position = glm::vec3(0.0f);
}
// give the points to the shapes
VerletCapsuleShape capsuleA(radiusA, points+0, points+1);
VerletCapsuleShape capsuleB(radiusB, points+2, points+3);
capsuleA.setHalfHeight(halfHeightA);
capsuleB.setHalfHeight(halfHeightB);
CollisionList collisions(16);
int numCollisions = 0;
{ // side by side
capsuleB.setTranslation((0.99f * totalRadius) * xAxis);
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
<< std::endl;
} else {
++numCollisions;
}
if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
<< std::endl;
} else {
++numCollisions;
}
}
{ // end to end
capsuleB.setTranslation((0.99f * totalHalfLength) * yAxis);
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
<< std::endl;
} else {
++numCollisions;
}
if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
<< std::endl;
} else {
++numCollisions;
}
}
{ // rotate B and move it to the side
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
capsuleB.setRotation(rotation);
capsuleB.setTranslation((0.99f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
<< std::endl;
} else {
++numCollisions;
}
if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
<< std::endl;
} else {
++numCollisions;
}
}
{ // again, but this time check collision details
float overlap = 0.1f;
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
capsuleB.setRotation(rotation);
glm::vec3 positionB = ((totalRadius + capsuleB.getHalfHeight()) - overlap) * xAxis;
capsuleB.setTranslation(positionB);
// capsuleA vs capsuleB
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
<< std::endl;
} else {
++numCollisions;
}
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
glm::vec3 expectedPenetration = overlap * xAxis;
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad penetration: expected = " << expectedPenetration
<< " actual = " << collision->_penetration;
}
glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * xAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
<< " actual = " << collision->_contactPoint;
}
// capsuleB vs capsuleA
if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
<< std::endl;
} else {
++numCollisions;
}
collision = collisions.getCollision(numCollisions - 1);
expectedPenetration = - overlap * xAxis;
inaccuracy = glm::length(collision->_penetration - expectedPenetration);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad penetration: expected = " << expectedPenetration
<< " actual = " << collision->_penetration
<< std::endl;
}
expectedContactPoint = capsuleB.getTranslation() - (radiusB + halfHeightB) * xAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
<< " actual = " << collision->_contactPoint
<< std::endl;
}
}
{ // collide cylinder wall against cylinder wall
float overlap = 0.137f;
float shift = 0.317f * halfHeightA;
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
capsuleB.setRotation(rotation);
glm::vec3 positionB = (totalRadius - overlap) * zAxis + shift * yAxis;
capsuleB.setTranslation(positionB);
// capsuleA vs capsuleB
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
<< std::endl;
} else {
++numCollisions;
}
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
glm::vec3 expectedPenetration = overlap * zAxis;
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad penetration: expected = " << expectedPenetration
<< " actual = " << collision->_penetration
<< std::endl;
}
glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * zAxis + shift * yAxis;
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
if (fabs(inaccuracy) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
<< " actual = " << collision->_contactPoint
<< std::endl;
}
}
}
void VerletShapeTests::runAllTests() {
setSpherePosition();
sphereMissesSphere();
sphereTouchesSphere();
sphereMissesCapsule();
sphereTouchesCapsule();
capsuleMissesCapsule();
capsuleTouchesCapsule();
}

View file

@ -0,0 +1,30 @@
//
// VerletShapeTests.h
// tests/physics/src
//
// Created by Andrew Meadows on 2014.06.18
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_VerletShapeTests_h
#define hifi_VerletShapeTests_h
namespace VerletShapeTests {
void setSpherePosition();
void sphereMissesSphere();
void sphereTouchesSphere();
void sphereMissesCapsule();
void sphereTouchesCapsule();
void capsuleMissesCapsule();
void capsuleTouchesCapsule();
void runAllTests();
}
#endif // hifi_VerletShapeTests_h

View file

@ -9,8 +9,10 @@
//
#include "ShapeColliderTests.h"
#include "VerletShapeTests.h"
int main(int argc, char** argv) {
ShapeColliderTests::runAllTests();
VerletShapeTests::runAllTests();
return 0;
}