diff --git a/src/Shapes/Sphere.cpp b/src/Shapes/Sphere.cpp index f07bcb247..303e6e351 100644 --- a/src/Shapes/Sphere.cpp +++ b/src/Shapes/Sphere.cpp @@ -74,7 +74,29 @@ template bool Sphere::operator%(const LineSe } template bool Sphere::operator%(const Sphere& other) const { - return (other._position-_position).dot() < Math::pow<2>(_radius+other._radius); + return (_position - other._position).dot() < Math::pow<2>(_radius+other._radius); +} + +template Collision Sphere::operator/(const Sphere& other) const { + const Float minDistance = _radius + other._radius; + const typename DimensionTraits::VectorType separating = _position - other._position; + const Float dot = separating.dot(); + + /* No collision occured */ + if(dot > Math::pow<2>(minDistance)) return {}; + + /* Actual distance */ + const Float distance = Math::sqrt(dot); + + /* Separating normal. If can't decide on direction, just move up. */ + /** @todo How to handle this in a configurable way? */ + const typename DimensionTraits::VectorType separatingNormal = + Math::TypeTraits::equals(dot, 0.0f) ? + DimensionTraits::VectorType::yAxis() : + separating/distance; + + /* Contact position is on the surface of `other`, minDistace > distance */ + return Collision(other._position + separatingNormal*other._radius, separatingNormal, minDistance - distance); } #ifndef DOXYGEN_GENERATING_OUTPUT diff --git a/src/Shapes/Sphere.h b/src/Shapes/Sphere.h index 6580efa8e..9bd3a6118 100644 --- a/src/Shapes/Sphere.h +++ b/src/Shapes/Sphere.h @@ -95,6 +95,9 @@ template class MAGNUM_SHAPES_EXPORT Sphere { /** @brief %Collision occurence with sphere */ bool operator%(const Sphere& other) const; + /** @brief %Collision with sphere */ + Collision operator/(const Sphere& other) const; + private: typename DimensionTraits::VectorType _position; Float _radius; diff --git a/src/Shapes/Test/SphereTest.cpp b/src/Shapes/Test/SphereTest.cpp index 877176130..773cccccc 100644 --- a/src/Shapes/Test/SphereTest.cpp +++ b/src/Shapes/Test/SphereTest.cpp @@ -105,12 +105,29 @@ void SphereTest::collisionLineSegment() { } void SphereTest::collisionSphere() { - Shapes::Sphere3D sphere({1.0f, 2.0f, 3.0f}, 2.0f); - Shapes::Sphere3D sphere1({1.0f, 3.0f, 5.0f}, 1.0f); - Shapes::Sphere3D sphere2({1.0f, 3.0f, 0.0f}, 1.0f); + const Shapes::Sphere3D sphere({1.0f, 2.0f, 3.0f}, 2.0f); + + /* Collision */ + const Shapes::Sphere3D sphere1({3.5f, 2.0f, 3.0f}, 1.0f); + const Shapes::Collision3D collision = sphere/sphere1; + CORRADE_VERIFY(sphere%sphere1 && sphere1%sphere); + CORRADE_COMPARE(collision.position(), sphere1.position() - Vector3::xAxis(sphere1.radius())); + CORRADE_COMPARE(collision.separationNormal(), -Vector3::xAxis()); + CORRADE_COMPARE(collision.separationDistance(), 0.5f); - VERIFY_COLLIDES(sphere, sphere1); - VERIFY_NOT_COLLIDES(sphere, sphere2); + /* Collision, flipped */ + CORRADE_COMPARE(collision.separationNormal(), -(sphere1/sphere).separationNormal()); + + /* Collision with ambiguous separation vector */ + const Shapes::Sphere3D sphere2(sphere.position(), 0.5f); + const Shapes::Collision3D collision2 = sphere/sphere2; + CORRADE_COMPARE(collision2.position(), sphere2.position() + Vector3::yAxis(sphere2.radius())); + CORRADE_COMPARE(collision2.separationNormal(), Vector3::yAxis()); + CORRADE_COMPARE(collision2.separationDistance(), 2.5f); + + /* No collision */ + const Shapes::Sphere3D sphere3({-2.5f, 2.0f, 3.0f}, 1.0f); + CORRADE_VERIFY(!(sphere%sphere3) && !(sphere/sphere3)); } }}}