mirror of
https://github.com/lubosz/overte.git
synced 2025-04-09 08:22:30 +02:00
Adding CapsuleSphere collisions with tests.
This commit is contained in:
parent
0e28e0947c
commit
8a3640f016
13 changed files with 557 additions and 185 deletions
|
@ -6,6 +6,7 @@
|
|||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include "CapsuleShape.h"
|
||||
|
@ -13,6 +14,7 @@
|
|||
|
||||
|
||||
// default axis of CapsuleShape is Y-axis
|
||||
const glm::vec3 localAxis(0.f, 1.f, 0.f);
|
||||
|
||||
CapsuleShape::CapsuleShape() : Shape(Shape::CAPSULE_SHAPE) {}
|
||||
|
||||
|
@ -43,6 +45,21 @@ CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm:
|
|||
updateBoundingRadius();
|
||||
}
|
||||
|
||||
/// \param[out] startPoint is the center of start cap
|
||||
void CapsuleShape::getStartPoint(glm::vec3& startPoint) const {
|
||||
startPoint = getPosition() - _halfHeight * (_rotation * glm::vec3(0.f, 1.f, 0.f));
|
||||
}
|
||||
|
||||
/// \param[out] endPoint is the center of the end cap
|
||||
void CapsuleShape::getEndPoint(glm::vec3& endPoint) const {
|
||||
endPoint = getPosition() + _halfHeight * (_rotation * glm::vec3(0.f, 1.f, 0.f));
|
||||
}
|
||||
|
||||
void CapsuleShape::computeNormalizedAxis(glm::vec3& axis) const {
|
||||
// default axis of a capsule is along the yAxis
|
||||
axis = _rotation * glm::vec3(0.f, 1.f, 0.f);
|
||||
}
|
||||
|
||||
void CapsuleShape::setRadius(float radius) {
|
||||
_radius = radius;
|
||||
updateBoundingRadius();
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "Shape.h"
|
||||
|
||||
// adebug bookmark TODO: convert to new world-frame approach
|
||||
// default axis of CapsuleShape is Y-axis
|
||||
|
||||
class CapsuleShape : public Shape {
|
||||
|
@ -23,6 +24,14 @@ public:
|
|||
float getRadius() const { return _radius; }
|
||||
float getHalfHeight() const { return _halfHeight; }
|
||||
|
||||
/// \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;
|
||||
|
||||
void computeNormalizedAxis(glm::vec3& axis) const;
|
||||
|
||||
void setRadius(float radius);
|
||||
void setHalfHeight(float height);
|
||||
void setRadiusAndHalfHeight(float radius, float height);
|
||||
|
|
|
@ -8,17 +8,6 @@
|
|||
|
||||
#include "CollisionInfo.h"
|
||||
|
||||
void CollisionInfo::rotateThenTranslate(const glm::quat& rotation, const glm::vec3& translation) {
|
||||
_contactPoint = translation + rotation * _contactPoint;
|
||||
_penetration = rotation * _penetration;
|
||||
_addedVelocity = rotation * _addedVelocity;
|
||||
}
|
||||
|
||||
void CollisionInfo::translateThenRotate(const glm::vec3& translation, const glm::quat& rotation) {
|
||||
_contactPoint = rotation * (_contactPoint + translation);
|
||||
_penetration = rotation * _penetration;
|
||||
_addedVelocity = rotation * _addedVelocity;
|
||||
}
|
||||
|
||||
CollisionList::CollisionList(int maxSize) :
|
||||
_maxSize(maxSize),
|
||||
|
|
|
@ -56,12 +56,6 @@ public:
|
|||
|
||||
~CollisionInfo() {}
|
||||
|
||||
/// Rotate and translate the details.
|
||||
void rotateThenTranslate(const glm::quat& rotation, const glm::vec3& translation);
|
||||
|
||||
/// Translate then rotate the details
|
||||
void translateThenRotate(const glm::vec3& translation, const glm::quat& rotation);
|
||||
|
||||
qint32 _type; // type of Collision (will determine what is supposed to be in _data and _flags)
|
||||
void* _data; // pointer to user supplied data
|
||||
quint32 _flags; // 32 bits for whatever
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//
|
||||
// Shape.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.20
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
|
@ -20,6 +19,7 @@ public:
|
|||
SPHERE_SHAPE,
|
||||
CAPSULE_SHAPE,
|
||||
BOX_SHAPE,
|
||||
LIST_SHAPE
|
||||
};
|
||||
|
||||
Shape() : _type(UNKNOWN_SHAPE), _boundingRadius(0.f), _position(0.f), _rotation() { }
|
||||
|
@ -33,8 +33,12 @@ public:
|
|||
void setRotation(const glm::quat& rotation) { _rotation = rotation; }
|
||||
|
||||
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, const glm::vec3& position)
|
||||
: _type(type), _boundingRadius(0.f), _position(position), _rotation() {}
|
||||
|
||||
Shape(Type type, const glm::vec3& position, const glm::quat& rotation)
|
||||
: _type(type), _boundingRadius(0.f), _position(position), _rotation(rotation) {}
|
||||
|
||||
|
|
|
@ -6,42 +6,37 @@
|
|||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
#include "ShapeCollider.h"
|
||||
|
||||
namespace ShapeCollider {
|
||||
|
||||
bool shapeShape(const Shape* shapeA, const Shape* shapeB,
|
||||
const glm::quat& rotationAB, const glm::vec3& offsetA, CollisionInfo& collision) {
|
||||
bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionInfo& collision) {
|
||||
// ATM we only have two shape types so we just check every case.
|
||||
// TODO: make a fast lookup for correct method
|
||||
if (shapeA->getType() == Shape::SPHERE_SHAPE) {
|
||||
const SphereShape* sphereA = static_cast<const SphereShape*>(shapeA);
|
||||
if (shapeB->getType() == Shape::SPHERE_SHAPE) {
|
||||
return sphereSphere(sphereA, static_cast<const SphereShape*>(shapeB),
|
||||
rotationAB, offsetA, collision);
|
||||
return sphereSphere(sphereA, static_cast<const SphereShape*>(shapeB), collision);
|
||||
} else if (shapeB->getType() == Shape::CAPSULE_SHAPE) {
|
||||
return sphereCapsule(sphereA, static_cast<const CapsuleShape*>(shapeB),
|
||||
rotationAB, offsetA, collision);
|
||||
return sphereCapsule(sphereA, static_cast<const CapsuleShape*>(shapeB), collision);
|
||||
}
|
||||
} else if (shapeA->getType() == Shape::CAPSULE_SHAPE) {
|
||||
const CapsuleShape* capsuleA = static_cast<const CapsuleShape*>(shapeA);
|
||||
if (shapeB->getType() == Shape::SPHERE_SHAPE) {
|
||||
return capsuleSphere(capsuleA, static_cast<const SphereShape*>(shapeB),
|
||||
rotationAB, offsetA, collision);
|
||||
return capsuleSphere(capsuleA, static_cast<const SphereShape*>(shapeB), collision);
|
||||
} else if (shapeB->getType() == Shape::CAPSULE_SHAPE) {
|
||||
return capsuleCapsule(capsuleA, static_cast<const CapsuleShape*>(shapeB),
|
||||
rotationAB, offsetA, collision);
|
||||
return capsuleCapsule(capsuleA, static_cast<const CapsuleShape*>(shapeB), collision);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB,
|
||||
const glm::quat& rotationAB, const glm::vec3& offsetA, CollisionInfo& collision) {
|
||||
// A in B's frame
|
||||
glm::vec3 A = rotationAB * sphereA->getPosition() + offsetA;
|
||||
// BA = B - A = from A to B, in B's frame
|
||||
glm::vec3 BA = sphereB->getPosition() - A;
|
||||
bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionInfo& collision) {
|
||||
glm::vec3 BA = sphereB->getPosition() - sphereA->getPosition();
|
||||
float distanceSquared = glm::dot(BA, BA);
|
||||
float totalRadius = sphereA->getRadius() + sphereB->getRadius();
|
||||
if (distanceSquared < totalRadius * totalRadius) {
|
||||
|
@ -53,103 +48,124 @@ bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB,
|
|||
} else {
|
||||
BA /= distance;
|
||||
}
|
||||
// store the collision details in B's frame
|
||||
// penetration points from A into B
|
||||
collision._penetration = BA * (totalRadius - distance);
|
||||
collision._contactPoint = A + sphereA->getRadius() * BA;
|
||||
// contactPoint is on surface of A
|
||||
collision._contactPoint = sphereA->getPosition() + sphereA->getRadius() * BA;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// everything in the capsule's natural frame (where its axis is along yAxis)
|
||||
bool sphereCapsuleHelper(float sphereRadius, const glm::vec3 sphereCenter,
|
||||
const CapsuleShape* capsule, CollisionInfo& collision) {
|
||||
float xzSquared = sphereCenter.x * sphereCenter.x + sphereCenter.y * sphereCenter.y;
|
||||
float totalRadius = sphereRadius + capsule->getRadius();
|
||||
if (xzSquared < totalRadius * totalRadius) {
|
||||
float fabsY = fabs(sphereCenter.y);
|
||||
float halfHeight = capsule->getHalfHeight();
|
||||
if (fabsY < halfHeight) {
|
||||
// sphere collides with cylindrical wall
|
||||
glm::vec3 BA = -sphereCenter;
|
||||
BA.y = 0.f;
|
||||
float distance = sqrtf(xzSquared);
|
||||
if (distance < EPSILON) {
|
||||
// for now we don't handle this singular case
|
||||
bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionInfo& collision) {
|
||||
// find sphereA's closest approach to axis of capsuleB
|
||||
glm::vec3 BA = capsuleB->getPosition() - sphereA->getPosition();
|
||||
glm::vec3 capsuleAxis;
|
||||
capsuleB->computeNormalizedAxis(capsuleAxis);
|
||||
float axialDistance = - glm::dot(BA, capsuleAxis);
|
||||
float absAxialDistance = fabs(axialDistance);
|
||||
float totalRadius = sphereA->getRadius() + capsuleB->getRadius();
|
||||
if (absAxialDistance < totalRadius + capsuleB->getHalfHeight()) {
|
||||
glm::vec3 radialAxis = BA + axialDistance * capsuleAxis; // points from A to axis of B
|
||||
float radialDistance2 = glm::length2(radialAxis);
|
||||
if (radialDistance2 > totalRadius * totalRadius) {
|
||||
// sphere is too far from capsule axis
|
||||
return false;
|
||||
}
|
||||
if (absAxialDistance > capsuleB->getHalfHeight()) {
|
||||
// sphere hits capsule on a cap --> recompute radialAxis to point from spherA to cap center
|
||||
float sign = (axialDistance > 0.f) ? 1.f : -1.f;
|
||||
radialAxis = BA + (sign * capsuleB->getHalfHeight()) * capsuleAxis;
|
||||
radialDistance2 = glm::length2(radialAxis);
|
||||
}
|
||||
if (radialDistance2 > EPSILON * EPSILON) {
|
||||
// normalize the radialAxis
|
||||
float radialDistance = sqrtf(radialDistance2);
|
||||
radialAxis /= radialDistance;
|
||||
// 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;
|
||||
} else {
|
||||
// A is on B's axis, so the penetration is undefined...
|
||||
if (absAxialDistance > capsuleB->getHalfHeight()) {
|
||||
// ...for the cylinder case (for now we pretend the collision doesn't exist)
|
||||
return false;
|
||||
}
|
||||
BA /= distance;
|
||||
collision._penetration = BA * (totalRadius - distance);
|
||||
collision._contactPoint = BA * (sphereRadius - totalRadius + distance);
|
||||
collision._contactPoint.y = fabsY;
|
||||
if (sphereCenter.y < 0.f) {
|
||||
// negate the y elements of the collision info
|
||||
collision._penetration.y *= -1.f;
|
||||
collision._contactPoint.y *= -1.f;
|
||||
// ... but still defined for the cap case
|
||||
if (axialDistance < 0.f) {
|
||||
// we're hitting the start cap, so we negate the capsuleAxis
|
||||
capsuleAxis *= -1;
|
||||
}
|
||||
return true;
|
||||
// penetration points from A into B
|
||||
float sign = (axialDistance > 0.f) ? -1.f : 1.f;
|
||||
collision._penetration = (sign * (totalRadius + capsuleB->getHalfHeight() - absAxialDistance)) * capsuleAxis;
|
||||
// contactPoint is on surface of sphereA
|
||||
collision._contactPoint = sphereA->getPosition() + (sign * sphereA->getRadius()) * capsuleAxis;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionInfo& collision) {
|
||||
// find sphereB's closest approach to axis of capsuleA
|
||||
glm::vec3 AB = capsuleA->getPosition() - sphereB->getPosition();
|
||||
glm::vec3 capsuleAxis;
|
||||
capsuleA->computeNormalizedAxis(capsuleAxis);
|
||||
float axialDistance = - glm::dot(AB, capsuleAxis);
|
||||
float absAxialDistance = fabs(axialDistance);
|
||||
float totalRadius = sphereB->getRadius() + capsuleA->getRadius();
|
||||
if (absAxialDistance < totalRadius + capsuleA->getHalfHeight()) {
|
||||
glm::vec3 radialAxis = AB + axialDistance * capsuleAxis; // from sphereB to axis of capsuleA
|
||||
float radialDistance2 = glm::length2(radialAxis);
|
||||
if (radialDistance2 > totalRadius * totalRadius) {
|
||||
// sphere is too far from capsule axis
|
||||
return false;
|
||||
}
|
||||
|
||||
// closestApproach = point on capsuleA's axis that is closest to sphereB's center
|
||||
glm::vec3 closestApproach = capsuleA->getPosition() + axialDistance * capsuleAxis;
|
||||
|
||||
if (absAxialDistance > capsuleA->getHalfHeight()) {
|
||||
// sphere hits capsule on a cap
|
||||
// --> recompute radialAxis and closestApproach
|
||||
float sign = (axialDistance > 0.f) ? 1.f : -1.f;
|
||||
closestApproach = capsuleA->getPosition() + (sign * capsuleA->getHalfHeight()) * capsuleAxis;
|
||||
radialAxis = closestApproach - sphereB->getPosition();
|
||||
radialDistance2 = glm::length2(radialAxis);
|
||||
}
|
||||
if (radialDistance2 > EPSILON * EPSILON) {
|
||||
// normalize the radialAxis
|
||||
float radialDistance = sqrtf(radialDistance2);
|
||||
radialAxis /= radialDistance;
|
||||
// penetration points from A into B
|
||||
collision._penetration = (radialDistance - totalRadius) * radialAxis; // points from A into B
|
||||
// contactPoint is on surface of capsuleA
|
||||
collision._contactPoint = closestApproach - capsuleA->getRadius() * radialAxis;
|
||||
} else {
|
||||
// tansform into frame where cap is at origin
|
||||
float newY = fabsY - halfHeight;
|
||||
float distance = sqrtf(newY * newY + xzSquared);
|
||||
if (distance < totalRadius) {
|
||||
// sphere collides with cap
|
||||
|
||||
// BA points from sphere to cap
|
||||
glm::vec3 BA(-sphereCenter.x, newY, -sphereCenter.z);
|
||||
if (distance < EPSILON) {
|
||||
// for now we don't handle this singular case
|
||||
return false;
|
||||
// A is on B's axis, so the penetration is undefined...
|
||||
if (absAxialDistance > capsuleA->getHalfHeight()) {
|
||||
// ...for the cylinder case (for now we pretend the collision doesn't exist)
|
||||
return false;
|
||||
} else {
|
||||
// ... but still defined for the cap case
|
||||
if (axialDistance < 0.f) {
|
||||
// we're hitting the start cap, so we negate the capsuleAxis
|
||||
capsuleAxis *= -1;
|
||||
}
|
||||
BA /= distance;
|
||||
collision._penetration = BA * (totalRadius - distance);
|
||||
collision._contactPoint = BA * (sphereRadius - totalRadius + distance);
|
||||
collision._contactPoint.y += halfHeight;
|
||||
|
||||
if (sphereCenter.y < 0.f) {
|
||||
// negate the y elements of the collision info
|
||||
collision._penetration.y *= -1.f;
|
||||
collision._contactPoint.y *= -1.f;
|
||||
}
|
||||
return true;
|
||||
float sign = (axialDistance > 0.f) ? 1.f : -1.f;
|
||||
collision._penetration = (sign * (totalRadius + capsuleA->getHalfHeight() - absAxialDistance)) * capsuleAxis;
|
||||
// contactPoint is on surface of sphereA
|
||||
collision._contactPoint = closestApproach + (sign * capsuleA->getRadius()) * capsuleAxis;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB,
|
||||
const glm::quat& rotationAB, const glm::vec3& offsetA, CollisionInfo& collision) {
|
||||
// transform sphereA all the way to capsuleB's natural frame
|
||||
glm::quat rotationB = capsuleB->getRotation();
|
||||
glm::vec3 sphereCenter = rotationB * (offsetA + rotationAB * sphereA->getPosition() - capsuleB->getPosition());
|
||||
if (sphereCapsuleHelper(sphereA->getRadius(), sphereCenter, capsuleB, collision)) {
|
||||
// need to transform collision details back into B's offset frame
|
||||
collision.rotateThenTranslate(glm::inverse(rotationB), capsuleB->getPosition());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB,
|
||||
const glm::quat& rotationAB, const glm::vec3& offsetA, CollisionInfo& collision) {
|
||||
// transform sphereB all the way to capsuleA's natural frame
|
||||
glm::quat rotationBA = glm::inverse(rotationAB);
|
||||
glm::quat rotationA = capsuleA->getRotation();
|
||||
glm::vec3 offsetB = rotationBA * (-offsetA);
|
||||
glm::vec3 sphereCenter = rotationA * (offsetB + rotationBA * sphereB->getPosition() - capsuleA->getPosition());
|
||||
if (sphereCapsuleHelper(sphereB->getRadius(), sphereCenter, capsuleA, collision)) {
|
||||
// need to transform collision details back into B's offset frame
|
||||
// BUG: these back translations are probably not right
|
||||
collision.rotateThenTranslate(glm::inverse(rotationA), capsuleA->getPosition());
|
||||
collision.rotateThenTranslate(glm::inverse(rotationAB), -offsetA);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
|
||||
const glm::quat& rotationAB, const glm::vec3& offsetA, CollisionInfo& collision) {
|
||||
bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, CollisionInfo& collision) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,50 +16,35 @@
|
|||
|
||||
namespace ShapeCollider {
|
||||
|
||||
/// \param shapeA pointer to first shape
|
||||
/// \param shapeB pointer to second shape
|
||||
/// \param rotationAB rotation from A into reference frame of B
|
||||
/// \param offsetA offset of A (in B's frame)
|
||||
/// \param[out] collision where to store collision details
|
||||
/// \return true of shapes collide
|
||||
bool shapeShape(const Shape* shapeA, const Shape* shapeB,
|
||||
const glm::quat& rotationAB, const glm::vec3& offsetA, CollisionInfo& collision);
|
||||
/// \param shapeA pointer to first shape
|
||||
/// \param shapeB pointer to second shape
|
||||
/// \param[out] collision where to store collision details
|
||||
/// \return true of shapes collide
|
||||
bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionInfo& collision);
|
||||
|
||||
/// \param sphereA pointer to first shape (sphere)
|
||||
/// \param sphereB pointer to second shape (sphere)
|
||||
/// \param rotationAB rotation from A into reference frame of B
|
||||
/// \param offsetA offset of A (in B's frame)
|
||||
/// \param[out] collision where to store collision details
|
||||
/// \return true of shapes collide
|
||||
bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB,
|
||||
const glm::quat& rotationAB, const glm::vec3& offsetA, CollisionInfo& collision);
|
||||
/// \param sphereA pointer to first shape (sphere)
|
||||
/// \param sphereB pointer to second shape (sphere)
|
||||
/// \param[out] collision where to store collision details
|
||||
/// \return true of shapes collide
|
||||
bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionInfo& collision);
|
||||
|
||||
/// \param sphereA pointer to first shape (sphere)
|
||||
/// \param capsuleB pointer to second shape (capsule)
|
||||
/// \param rotationAB rotation from A into reference frame of B
|
||||
/// \param offsetA offset of A (in B's frame)
|
||||
/// \param[out] collision where to store collision details
|
||||
/// \return true of shapes collide
|
||||
bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB,
|
||||
const glm::quat& rotationAB, const glm::vec3& offsetA, CollisionInfo& collision);
|
||||
/// \param sphereA pointer to first shape (sphere)
|
||||
/// \param capsuleB pointer to second shape (capsule)
|
||||
/// \param[out] collision where to store collision details
|
||||
/// \return true of shapes collide
|
||||
bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionInfo& collision);
|
||||
|
||||
/// \param capsuleA pointer to first shape (capsule)
|
||||
/// \param sphereB pointer to second shape (sphere)
|
||||
/// \param rotationAB rotation from A into reference frame of B
|
||||
/// \param offsetA offset of A (in B's frame)
|
||||
/// \param[out] collision where to store collision details
|
||||
/// \return true of shapes collide
|
||||
bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB,
|
||||
const glm::quat& rotationAB, const glm::vec3& offsetA, CollisionInfo& collision);
|
||||
/// \param capsuleA pointer to first shape (capsule)
|
||||
/// \param sphereB pointer to second shape (sphere)
|
||||
/// \param[out] collision where to store collision details
|
||||
/// \return true of shapes collide
|
||||
bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionInfo& collision);
|
||||
|
||||
/// \param capsuleA pointer to first shape (capsule)
|
||||
/// \param capsuleB pointer to second shape (capsule)
|
||||
/// \param rotationAB rotation from A into reference frame of B
|
||||
/// \param offsetA offset of A (in B's frame)
|
||||
/// \param[out] collision where to store collision details
|
||||
/// \return true of shapes collide
|
||||
bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
|
||||
const glm::quat& rotationAB, const glm::vec3& offsetA, CollisionInfo& collision);
|
||||
/// \param capsuleA pointer to first shape (capsule)
|
||||
/// \param capsuleB pointer to second shape (capsule)
|
||||
/// \param[out] collision where to store collision details
|
||||
/// \return true of shapes collide
|
||||
bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, CollisionInfo& collision);
|
||||
|
||||
} // namespace ShapeCollider
|
||||
|
||||
|
|
|
@ -15,9 +15,12 @@ class SphereShape : public Shape {
|
|||
public:
|
||||
SphereShape() : Shape(Shape::SPHERE_SHAPE) {}
|
||||
|
||||
SphereShape(float radius, const glm::vec3& position) : Shape(Shape::SPHERE_SHAPE) {
|
||||
SphereShape(float radius) : Shape(Shape::SPHERE_SHAPE) {
|
||||
_boundingRadius = radius;
|
||||
}
|
||||
|
||||
SphereShape(float radius, const glm::vec3& position) : Shape(Shape::SPHERE_SHAPE, position) {
|
||||
_boundingRadius = radius;
|
||||
setPosition(position);
|
||||
}
|
||||
|
||||
float getRadius() const { return _boundingRadius; }
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "PhysicsTestUtil.h"
|
||||
|
||||
|
||||
/*
|
||||
void CollisionInfoTests::rotateThenTranslate() {
|
||||
CollisionInfo collision;
|
||||
collision._penetration = xAxis;
|
||||
|
@ -95,8 +96,9 @@ void CollisionInfoTests::translateThenRotate() {
|
|||
<< std::endl;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void CollisionInfoTests::runAllTests() {
|
||||
CollisionInfoTests::rotateThenTranslate();
|
||||
CollisionInfoTests::translateThenRotate();
|
||||
// CollisionInfoTests::rotateThenTranslate();
|
||||
// CollisionInfoTests::translateThenRotate();
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
namespace CollisionInfoTests {
|
||||
|
||||
void rotateThenTranslate();
|
||||
void translateThenRotate();
|
||||
// void rotateThenTranslate();
|
||||
// void translateThenRotate();
|
||||
|
||||
void runAllTests();
|
||||
}
|
||||
|
|
|
@ -22,33 +22,384 @@
|
|||
|
||||
const glm::vec3 origin(0.f);
|
||||
|
||||
void ShapeColliderTests::sphereSphere() {
|
||||
void ShapeColliderTests::sphereMissesSphere() {
|
||||
// non-overlapping spheres of unequal size
|
||||
float radiusA = 7.f;
|
||||
float radiusB = 3.f;
|
||||
float alpha = 1.2f;
|
||||
float beta = 1.3f;
|
||||
glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.f, 2.f, 3.f));
|
||||
float offsetDistance = alpha * radiusA + beta * radiusB;
|
||||
float expectedPenetrationDistance = 0.f;
|
||||
|
||||
SphereShape sphereA(radiusA, origin);
|
||||
SphereShape sphereB(radiusB, offsetDistance * offsetDirection);
|
||||
CollisionInfo collision;
|
||||
|
||||
float radius = 2.f;
|
||||
SphereShape sphereA(radius, origin);
|
||||
SphereShape sphereB(radius, origin);
|
||||
|
||||
glm::vec3 translation(0.5f * radius, 0.f, 0.f);
|
||||
glm::quat rotation;
|
||||
|
||||
// these spheres should be touching
|
||||
bool touching = ShapeCollider::sphereSphere(&sphereA, &sphereB, rotation, translation, collision);
|
||||
if (!touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB do not touch" << std::endl;
|
||||
// collide A to B...
|
||||
{
|
||||
bool touching = ShapeCollider::shapeShape(&sphereA, &sphereB, collision);
|
||||
if (touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: verify the collision info is good...
|
||||
// penetration should point from A into B
|
||||
// collide B to A...
|
||||
{
|
||||
bool touching = ShapeCollider::shapeShape(&sphereB, &sphereA, collision);
|
||||
if (touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// also test shapeShape
|
||||
{
|
||||
bool touching = ShapeCollider::shapeShape(&sphereB, &sphereA, collision);
|
||||
if (touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
void ShapeColliderTests::test2() {
|
||||
void ShapeColliderTests::sphereTouchesSphere() {
|
||||
// overlapping spheres of unequal size
|
||||
float radiusA = 7.f;
|
||||
float radiusB = 3.f;
|
||||
float alpha = 0.2f;
|
||||
float beta = 0.3f;
|
||||
glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.f, 2.f, 3.f));
|
||||
float offsetDistance = alpha * radiusA + beta * radiusB;
|
||||
float expectedPenetrationDistance = (1.f - alpha) * radiusA + (1.f - beta) * radiusB;
|
||||
glm::vec3 expectedPenetration = expectedPenetrationDistance * offsetDirection;
|
||||
|
||||
SphereShape sphereA(radiusA, origin);
|
||||
SphereShape sphereB(radiusB, offsetDistance * offsetDirection);
|
||||
CollisionInfo collision;
|
||||
|
||||
// collide A to B...
|
||||
{
|
||||
bool touching = ShapeCollider::shapeShape(&sphereA, &sphereB, collision);
|
||||
if (!touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should touch" << 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
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 AtoB = sphereB.getPosition() - sphereA.getPosition();
|
||||
glm::vec3 expectedContactPoint = sphereA.getPosition() + 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
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// collide B to A...
|
||||
{
|
||||
bool touching = ShapeCollider::shapeShape(&sphereB, &sphereA, collision);
|
||||
if (!touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should touch" << 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
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 BtoA = sphereA.getPosition() - sphereB.getPosition();
|
||||
glm::vec3 expectedContactPoint = sphereB.getPosition() + 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
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::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;
|
||||
|
||||
SphereShape sphereA(radiusA);
|
||||
CapsuleShape capsuleB(radiusB, halfHeightB);
|
||||
|
||||
// give the capsule some arbirary transform
|
||||
float angle = 37.8;
|
||||
glm::vec3 axis = glm::normalize( glm::vec3(-7.f, 2.8f, 9.3f) );
|
||||
glm::quat rotation = glm::angleAxis(angle, axis);
|
||||
glm::vec3 translation(15.1f, -27.1f, -38.6f);
|
||||
capsuleB.setRotation(rotation);
|
||||
capsuleB.setPosition(translation);
|
||||
|
||||
// walk sphereA along the local yAxis next to, but not touching, capsuleB
|
||||
glm::vec3 localStartPosition(radialOffset, axialOffset, 0.f);
|
||||
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.setPosition(rotation * localPosition + translation);
|
||||
|
||||
CollisionInfo collision;
|
||||
// sphereA agains capsuleB
|
||||
if (ShapeCollider::shapeShape(&sphereA, &capsuleB, collision))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// capsuleB against sphereA
|
||||
if (ShapeCollider::shapeShape(&capsuleB, &sphereA, collision))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::sphereTouchesCapsule() {
|
||||
// overlapping sphere and capsule
|
||||
float radiusA = 2.f;
|
||||
float radiusB = 1.f;
|
||||
float totalRadius = radiusA + radiusB;
|
||||
float halfHeightB = 2.f;
|
||||
float alpha = 0.5f;
|
||||
float beta = 0.5f;
|
||||
float axialOffset = 0.f;
|
||||
float radialOffset = alpha * radiusA + beta * radiusB;
|
||||
|
||||
SphereShape sphereA(radiusA);
|
||||
CapsuleShape capsuleB(radiusB, halfHeightB);
|
||||
|
||||
CollisionInfo collision;
|
||||
{ // sphereA collides with capsuleB's cylindrical wall
|
||||
sphereA.setPosition(radialOffset * xAxis);
|
||||
|
||||
if (!ShapeCollider::shapeShape(&sphereA, &capsuleB, collision))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
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
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 expectedContactPoint = sphereA.getPosition() - radiusA * 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;
|
||||
}
|
||||
|
||||
// capsuleB collides with sphereA
|
||||
if (!ShapeCollider::shapeShape(&capsuleB, &sphereA, collision))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and sphere should touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
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
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of capsuleB
|
||||
glm::vec3 BtoA = sphereA.getPosition() - capsuleB.getPosition();
|
||||
glm::vec3 closestApproach = capsuleB.getPosition() + 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
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
{ // sphereA hits end cap at axis
|
||||
glm::vec3 axialOffset = (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
|
||||
sphereA.setPosition(axialOffset * yAxis);
|
||||
|
||||
if (!ShapeCollider::shapeShape(&sphereA, &capsuleB, collision))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
glm::vec3 expectedPenetration = - ((1.f - alpha) * radiusA + (1.f - 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
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 expectedContactPoint = sphereA.getPosition() - radiusA * 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;
|
||||
}
|
||||
|
||||
// capsuleB collides with sphereA
|
||||
if (!ShapeCollider::shapeShape(&capsuleB, &sphereA, collision))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and sphere should touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
expectedPenetration = ((1.f - alpha) * radiusA + (1.f - 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
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// 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
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
{ // sphereA hits start cap at axis
|
||||
glm::vec3 axialOffset = - (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
|
||||
sphereA.setPosition(axialOffset * yAxis);
|
||||
|
||||
if (!ShapeCollider::shapeShape(&sphereA, &capsuleB, collision))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
glm::vec3 expectedPenetration = ((1.f - alpha) * radiusA + (1.f - 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
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 expectedContactPoint = sphereA.getPosition() + radiusA * 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;
|
||||
}
|
||||
|
||||
// capsuleB collides with sphereA
|
||||
if (!ShapeCollider::shapeShape(&capsuleB, &sphereA, collision))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and sphere should touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
expectedPenetration = - ((1.f - alpha) * radiusA + (1.f - 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
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// 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
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::capsuleMissesCapsule() {
|
||||
// TODO: implement this
|
||||
}
|
||||
|
||||
void ShapeColliderTests::capsuleTouchesCapsule() {
|
||||
// TODO: implement this
|
||||
}
|
||||
*/
|
||||
|
||||
void ShapeColliderTests::runAllTests() {
|
||||
ShapeColliderTests::sphereSphere();
|
||||
// ShapeColliderTests::test2();
|
||||
sphereMissesSphere();
|
||||
sphereTouchesSphere();
|
||||
|
||||
sphereMissesCapsule();
|
||||
sphereTouchesCapsule();
|
||||
|
||||
capsuleMissesCapsule();
|
||||
capsuleTouchesCapsule();
|
||||
}
|
||||
|
|
|
@ -11,8 +11,14 @@
|
|||
|
||||
namespace ShapeColliderTests {
|
||||
|
||||
void sphereSphere();
|
||||
//void test2();
|
||||
void sphereMissesSphere();
|
||||
void sphereTouchesSphere();
|
||||
|
||||
void sphereMissesCapsule();
|
||||
void sphereTouchesCapsule();
|
||||
|
||||
void capsuleMissesCapsule();
|
||||
void capsuleTouchesCapsule();
|
||||
|
||||
void runAllTests();
|
||||
}
|
||||
|
|
|
@ -3,13 +3,9 @@
|
|||
// physics-tests
|
||||
//
|
||||
|
||||
//#include <QDebug>
|
||||
|
||||
#include "ShapeColliderTests.h"
|
||||
#include "CollisionInfoTests.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
CollisionInfoTests::runAllTests();
|
||||
ShapeColliderTests::runAllTests();
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue