mirror of
https://github.com/lubosz/overte.git
synced 2025-04-23 23:33:48 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into sit_on_a_model
This commit is contained in:
commit
c660986480
54 changed files with 2699 additions and 1134 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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; }");
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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><html><head/><body><p><span style=" font-size:18pt;">Running Scripts</span></p></body></html></string>
|
||||
<string><html><head/><body><p><span style=" font-size:18px;">Running Scripts</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html></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">
|
||||
|
|
|
@ -277,7 +277,7 @@ padding-left:20px;</string>
|
|||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Helvetica'; font-size:14pt; font-weight:400; font-style:normal;">
|
||||
</style></head><body style=" font-family:'Helvetica'; font-size:14px; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
211
libraries/shared/src/PhysicsEntity.cpp
Normal file
211
libraries/shared/src/PhysicsEntity.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
78
libraries/shared/src/PhysicsEntity.h
Normal file
78
libraries/shared/src/PhysicsEntity.h
Normal 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
|
244
libraries/shared/src/PhysicsSimulation.cpp
Normal file
244
libraries/shared/src/PhysicsSimulation.cpp
Normal 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;
|
||||
}
|
||||
}
|
60
libraries/shared/src/PhysicsSimulation.h
Normal file
60
libraries/shared/src/PhysicsSimulation.h
Normal 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
|
|
@ -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;
|
||||
|
|
121
libraries/shared/src/Ragdoll.cpp
Normal file
121
libraries/shared/src/Ragdoll.cpp
Normal 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;
|
||||
}
|
||||
|
107
libraries/shared/src/Ragdoll.h
Normal file
107
libraries/shared/src/Ragdoll.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
166
libraries/shared/src/VerletCapsuleShape.cpp
Normal file
166
libraries/shared/src/VerletCapsuleShape.cpp
Normal 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();
|
||||
}
|
83
libraries/shared/src/VerletCapsuleShape.h
Normal file
83
libraries/shared/src/VerletCapsuleShape.h
Normal 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
|
50
libraries/shared/src/VerletSphereShape.cpp
Normal file
50
libraries/shared/src/VerletSphereShape.cpp
Normal 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();
|
||||
}
|
47
libraries/shared/src/VerletSphereShape.h
Normal file
47
libraries/shared/src/VerletSphereShape.h
Normal 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
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
769
tests/physics/src/VerletShapeTests.cpp
Normal file
769
tests/physics/src/VerletShapeTests.cpp
Normal 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();
|
||||
}
|
30
tests/physics/src/VerletShapeTests.h
Normal file
30
tests/physics/src/VerletShapeTests.h
Normal 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
|
|
@ -9,8 +9,10 @@
|
|||
//
|
||||
|
||||
#include "ShapeColliderTests.h"
|
||||
#include "VerletShapeTests.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ShapeColliderTests::runAllTests();
|
||||
VerletShapeTests::runAllTests();
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue