Browse Source

Fixed collision detection boolean/logical operations confusion.

Current implementation provided logical operations as if it were boolean
operations, which is wrong. Booleans might (or might not) be implemented
in the future, but currently the logical are enough.
pull/279/head
Vladimír Vondruš 14 years ago
parent
commit
04d58b3dbd
  1. 60
      doc/collision-detection.dox
  2. 8
      src/Physics/ShapeGroup.cpp
  3. 119
      src/Physics/ShapeGroup.h
  4. 12
      src/Physics/Test/ShapeGroupTest.cpp

60
doc/collision-detection.dox

@ -5,13 +5,13 @@ namespace Magnum { namespace Physics {
The essential thing in collision detection is to define a complex object with The essential thing in collision detection is to define a complex object with
collection of simple shapes, for which it is easy to detect collisions. These collection of simple shapes, for which it is easy to detect collisions. These
shapes can be either one-, two- or three-dimensional and they can be grouped shapes can be either one-, two- or three-dimensional and they can be grouped
together using five different set operations. together using various operations.
@tableofcontents @tableofcontents
@section CollisionDetectionShapeCollection Available shapes @section collision-detection-shape-collection Available shapes
@subsection CollisionDetectionShapes1D One-dimensional shapes @subsection collision-detection-shapes1D One-dimensional shapes
- Physics::Point - @copybrief Physics::Point - Physics::Point - @copybrief Physics::Point
- Physics::Line - @copybrief Physics::Line - Physics::Line - @copybrief Physics::Line
@ -20,11 +20,11 @@ together using five different set operations.
One-dimensional shapes don't provide collision detection with each other One-dimensional shapes don't provide collision detection with each other
because of numerical instability. because of numerical instability.
@subsection CollisionDetectionShapes2D Two-dimensional shapes @subsection collision-detection-shapes2D Two-dimensional shapes
- Physics::Plane - @copybrief Physics::Plane - Physics::Plane - @copybrief Physics::Plane
@subsection CollisionDetectionShapes3D Three-dimensional shapes @subsection collision-detection-shapes3D Three-dimensional shapes
- Physics::Sphere - @copybrief Physics::Sphere - Physics::Sphere - @copybrief Physics::Sphere
- Physics::Capsule - @copybrief Physics::Capsule - Physics::Capsule - @copybrief Physics::Capsule
@ -35,26 +35,30 @@ The easiest (and most efficient) shape combination for detecting collisions
is point and sphere, followed by two spheres. Computing collision of two boxes is point and sphere, followed by two spheres. Computing collision of two boxes
is least efficient. is least efficient.
@section CollisionDetectionShapeGroups Creating hierarchic groups of shapes @section collision-detection-shape-groups Creating hierarchic groups of shapes
Shapes can be grouped together using one of five set operations: complement, Shapes can be grouped together using one of three available logical
union, intersection, difference and XOR. These operations are mapped to operations: AND, OR and NOT. These operations are mapped to operator&&(),
operator~(), operator|(), operator&(), operator-() and operator^(), so for operator||() and operator!(), so for example creating negation of logical OR
example creating complement of union of sphere and box is simple as this: of line segment and point is simple as this:
@code @code
Physics::Sphere sphere; Physics::LineSegment segment;
Physics::Box box; Physics::Point point;
Physics::ShapeGroup group = ~(sphere|box); Physics::ShapeGroup group = !(segment || point);
@endcode @endcode
@note Logical operations are not the same as set operations -- intersection of
two spheres will not generate any collision if they are disjoint, but
logical AND will if the object collides with both of them.
The resulting object internally stores copies of both shapes, so the original The resulting object internally stores copies of both shapes, so the original
instances can be destroyed. For simple combinations appropriate resulting instances can be destroyed. For simple combinations appropriate resulting
shape is generated (e.g. intersection of line and three-dimensional object shape is generated (e.g. logical OR of point and sphere can be only the sphere,
can be a line segment) and stored inside ShapeGroup instead of two original if the point lies inside) and stored inside ShapeGroup instead of two original
objects. objects.
@subsection CollisionDetectionShapeReference Referencing the shapes for later changes @subsection collision-detection-shape-reference Referencing the shapes for later changes
Sometimes you may want to modify the shape based on changes of the object Sometimes you may want to modify the shape based on changes of the object
itself. In previous example all the shapes were copied into ShapeGroup, so it itself. In previous example all the shapes were copied into ShapeGroup, so it
@ -62,34 +66,36 @@ was not possible to change their properties such as sphere radius without
recreating the group again. You can, however, explicitly pass a reference to recreating the group again. You can, however, explicitly pass a reference to
original object, so you can change it later: original object, so you can change it later:
@code @code
Physics::Sphere sphere; Physics::LineSegment segment;
Physics::Box box; Physics::Point point;
Physics::ShapeGroup group = ~(std::ref(sphere)|box); Physics::ShapeGroup group = !(segment || std::ref(point));
sphere.setRadius(2.0f); point.setPosition({1.0f, -6.0f, 0.5f});
@endcode @endcode
Note that passing a reference implies that you must not destroy the original Note that passing a reference implies that you must not destroy the original
instance (in this case the sphere). Also because the referenced instance could instance (in this case the point). Also because the referenced instance could
change, there are no shape optimizations done, unlike above. change, there are no shape optimizations done, unlike above.
@subsection CollisionDetectionShapeSimplification Providing simplified version of shape for better performance @subsection collision-detection-shape-simplification Providing simplified version of shape for better performance
If there are many shapes grouped together, it might hurt performance of If there are many shapes grouped together, it might hurt performance of
collision detection, because it might be testing collision with more shapes collision detection, because it might be testing collision with more shapes
than necessary. It's then good to specify simplified version of such shape, than necessary. It's then good to specify simplified version of such shape,
so the collision detection is done on the original if and only if collision so the collision detection is done on the original if and only if collision
was detected with the simplified shape. It is in fact intersection group - was detected with the simplified shape. It is in fact logical AND using
the collision is initially detected on first (simplified) shape and then on operator&&() - the collision is initially detected on first (simplified) shape
the other: and then on the other:
@code @code
Physics::Sphere sphere;
Physics::Box box;
Physics::AxisAlignedBox simplified; Physics::AxisAlignedBox simplified;
Physics::ShapeGroup object = simplified & (sphere|box); Physics::ShapeGroup object = simplified && (sphere || box);
@endcode @endcode
@section CollisionDetectionShapeCollisions Detecting shape collisions @section collision-detection-shape-collisions Detecting shape collisions
Shape pairs which have collision detection implemented can be tested for Shape pairs which have collision detection implemented can be tested for
collision using operator%(), for example: collision using operator%(), for example:

8
src/Physics/ShapeGroup.cpp

@ -50,11 +50,9 @@ void ShapeGroup::applyTransformation(const Matrix4& transformation) {
bool ShapeGroup::collides(const AbstractShape* other) const { bool ShapeGroup::collides(const AbstractShape* other) const {
switch(operation & ~RefAB) { switch(operation & ~RefAB) {
case Complement: return !a->collides(other); case And: return a->collides(other) && b->collides(other);
case Union: return a->collides(other) || b->collides(other); case Or: return a->collides(other) || b->collides(other);
case Intersection: return a->collides(other) && b->collides(other); case Not: return !a->collides(other);
case Difference: return a->collides(other) && !b->collides(other);
case Xor: return a->collides(other) != b->collides(other);
case FirstObjectOnly: return a->collides(other); case FirstObjectOnly: return a->collides(other);
default: default:

119
src/Physics/ShapeGroup.h

@ -32,16 +32,19 @@ namespace Magnum { namespace Physics {
#endif #endif
/** /**
@brief Collider group with defined set operation @brief Shape group
Result of union, intersection, subtraction or XOR of two collider objects. Result of logical operations on shapes.
See @ref collision-detection for brief introduction. See @ref collision-detection for brief introduction.
*/ */
class PHYSICS_EXPORT ShapeGroup: public AbstractShape { class PHYSICS_EXPORT ShapeGroup: public AbstractShape {
#ifndef DOXYGEN_GENERATING_OUTPUT #ifndef DOXYGEN_GENERATING_OUTPUT
template<class T> friend constexpr enableIfIsBaseType operator~(const T& a); // template<class T> friend constexpr enableIfIsBaseType operator~(const T& a);
template<class T> friend constexpr enableIfIsBaseType operator~(T&& a); // template<class T> friend constexpr enableIfIsBaseType operator~(T&& a);
template<class T> friend constexpr enableIfIsBaseType operator~(T& a); // template<class T> friend constexpr enableIfIsBaseType operator~(T& a);
template<class T> friend constexpr enableIfIsBaseType operator!(const T& a);
template<class T> friend constexpr enableIfIsBaseType operator!(T&& a);
template<class T> friend constexpr enableIfIsBaseType operator!(T& a);
#define friendOp(char) \ #define friendOp(char) \
template<class T, class U> friend constexpr enableIfAreBaseType operator char(const T& a, const U& b); \ template<class T, class U> friend constexpr enableIfAreBaseType operator char(const T& a, const U& b); \
@ -53,10 +56,12 @@ class PHYSICS_EXPORT ShapeGroup: public AbstractShape {
template<class T, class U> friend constexpr enableIfAreBaseType operator char(std::reference_wrapper<T> a, const U& b); \ template<class T, class U> friend constexpr enableIfAreBaseType operator char(std::reference_wrapper<T> a, const U& b); \
template<class T, class U> friend constexpr enableIfAreBaseType operator char(std::reference_wrapper<T> a, U&& b); \ template<class T, class U> friend constexpr enableIfAreBaseType operator char(std::reference_wrapper<T> a, U&& b); \
template<class T, class U> friend constexpr enableIfAreBaseType operator char(std::reference_wrapper<T> a, std::reference_wrapper<U> b); template<class T, class U> friend constexpr enableIfAreBaseType operator char(std::reference_wrapper<T> a, std::reference_wrapper<U> b);
friendOp(|) // friendOp(|)
friendOp(&) // friendOp(&)
friendOp(-) // friendOp(-)
friendOp(^) // friendOp(^)
friendOp(&&)
friendOp(||)
#undef friendOp #undef friendOp
#endif #endif
@ -68,13 +73,16 @@ class PHYSICS_EXPORT ShapeGroup: public AbstractShape {
RefA = 0x01, RefA = 0x01,
RefB = 0x02, RefB = 0x02,
RefAB = 0x03, RefAB = 0x03,
Complement = 1 << 2, // Complement = 1 << 2,
Union = 2 << 2, // Union = 2 << 2,
Intersection = 3 << 2, // Intersection = 3 << 2,
Difference = 4 << 2, // Difference = 4 << 2,
Xor = 5 << 2, // Xor = 5 << 2,
FirstObjectOnly = 6 << 2, And = 6 << 2,
AlwaysFalse = 7 << 2 Or = 7 << 2,
Not = 8 << 2,
FirstObjectOnly = 9 << 2,
AlwaysFalse = 10 << 2
}; };
public: public:
@ -105,37 +113,64 @@ class PHYSICS_EXPORT ShapeGroup: public AbstractShape {
AbstractShape* b; AbstractShape* b;
}; };
/** @brief Complement of shape */ // /* @brief Complement of shape */
template<class T> inline constexpr enableIfIsBaseType operator~(const T& a) { // template<class T> inline constexpr enableIfIsBaseType operator~(const T& a) {
return ShapeGroup(ShapeGroup::Complement, new T(a), nullptr); // return ShapeGroup(ShapeGroup::Complement, new T(a), nullptr);
// }
// #ifndef DOXYGEN_GENERATING_OUTPUT
// template<class T> inline constexpr enableIfIsBaseType operator~(T&& a) {
// return ShapeGroup(ShapeGroup::Complement, new T(std::forward<T>(a)), nullptr);
// }
// template<class T> inline constexpr enableIfIsBaseType operator~(T& a) {
// return ShapeGroup(ShapeGroup::Complement|ShapeGroup::RefA, &a.get(), nullptr);
// }
// #endif
/** @relates ShapeGroup
@brief Logical NOT of shape
*/
template<class T> inline constexpr enableIfIsBaseType operator!(const T& a) {
return ShapeGroup(ShapeGroup::Not, new T(a), nullptr);
} }
#ifndef DOXYGEN_GENERATING_OUTPUT #ifndef DOXYGEN_GENERATING_OUTPUT
template<class T> inline constexpr enableIfIsBaseType operator~(T&& a) { template<class T> inline constexpr enableIfIsBaseType operator!(T&& a) {
return ShapeGroup(ShapeGroup::Complement, new T(std::forward<T>(a)), nullptr); return ShapeGroup(ShapeGroup::Not, new T(std::forward<T>(a)), nullptr);
} }
template<class T> inline constexpr enableIfIsBaseType operator~(T& a) { template<class T> inline constexpr enableIfIsBaseType operator!(T& a) {
return ShapeGroup(ShapeGroup::Complement|ShapeGroup::RefA, &a.get(), nullptr); return ShapeGroup(ShapeGroup::Not|ShapeGroup::RefA, &a.get(), nullptr);
} }
#endif #endif
#ifdef DOXYGEN_GENERATING_OUTPUT #ifdef DOXYGEN_GENERATING_OUTPUT
/** @brief Union of two shapes */ // /* @brief Union of two shapes */
template<class T, class U> inline constexpr ShapeGroup operator&(T a, U b); // template<class T, class U> inline constexpr ShapeGroup operator&(T a, U b);
//
/** // /* @brief Intersection of two shapes */
@brief Intersection of two shapes // template<class T, class U> inline constexpr ShapeGroup operator&(T a, U b);
//
Collision with @p a is computed first, so this operation can be also used for // /* @brief Difference of two shapes */
providing simplified version for shape @p b. See @ref CollisionDetectionShapeGroups // template<class T, class U> inline constexpr ShapeGroup operator-(T a, U b);
for an example. //
// /* @brief XOR of two shapes */
// template<class T, class U> inline constexpr ShapeGroup operator^(T a, U b);
/** @relates ShapeGroup
@brief Logical AND of two shapes
[Short-circuit evaluation](http://en.wikipedia.org/wiki/Short-circuit_evaluation)
is used here, so this operation can be used for providing simplified shape
version, because collision with @p b is computed only if @p a collides.
See @ref collision-detection-shape-simplification for an example.
*/ */
template<class T, class U> inline constexpr ShapeGroup operator&(T a, U b); template<class T, class U> inline constexpr ShapeGroup operator&&(T a, U b);
/** @brief Difference of two shapes */ /** @relates ShapeGroup
template<class T, class U> inline constexpr ShapeGroup operator-(T a, U b); @brief Logical OR of two shapes
/** @brief XOR of two shapes */ [Short-circuit evaluation](http://en.wikipedia.org/wiki/Short-circuit_evaluation)
template<class T, class U> inline constexpr ShapeGroup operator^(T a, U b); is used, so if collision with @p a is detected, collision with @p b is not
computed.
*/
template<class T, class U> inline constexpr ShapeGroup operator||(T a, U b);
#else #else
#define op(type, char) \ #define op(type, char) \
template<class T, class U> inline constexpr enableIfAreBaseType operator char(const T& a, const U& b) { \ template<class T, class U> inline constexpr enableIfAreBaseType operator char(const T& a, const U& b) { \
@ -165,10 +200,12 @@ template<class T, class U> inline constexpr enableIfAreBaseType operator char(st
template<class T, class U> inline constexpr enableIfAreBaseType operator char(std::reference_wrapper<T> a, std::reference_wrapper<U> b) { \ template<class T, class U> inline constexpr enableIfAreBaseType operator char(std::reference_wrapper<T> a, std::reference_wrapper<U> b) { \
return ShapeGroup(ShapeGroup::type|ShapeGroup::RefAB, &a.get(), &b.get()); \ return ShapeGroup(ShapeGroup::type|ShapeGroup::RefAB, &a.get(), &b.get()); \
} }
op(Union, |) // op(Union, |)
op(Intersection, &) // op(Intersection, &)
op(Difference, -) // op(Difference, -)
op(Xor, ^) // op(Xor, ^)
op(And, &&)
op(Or, ||)
#undef op #undef op
#endif #endif

12
src/Physics/Test/ShapeGroupTest.cpp

@ -17,7 +17,7 @@
#include "Math/Matrix4.h" #include "Math/Matrix4.h"
#include "Physics/Point.h" #include "Physics/Point.h"
#include "Physics/Sphere.h" #include "Physics/LineSegment.h"
#include "Physics/ShapeGroup.h" #include "Physics/ShapeGroup.h"
using namespace std; using namespace std;
@ -35,9 +35,9 @@ void ShapeGroupTest::copy() {
ShapeGroup group; ShapeGroup group;
{ {
Physics::Point point({1.0f, 2.0f, 3.0f}); Physics::Point point({1.0f, 2.0f, 3.0f});
Physics::Sphere sphere({2.0f, 1.0f, 30.0f}, 1.0f); Physics::LineSegment segment({2.0f, 1.0f, 30.0f}, {1.0f, -20.0f, 3.0f});
group = ~(point|sphere); group = !(point || segment);
} }
/* Just to test that it doesn't crash */ /* Just to test that it doesn't crash */
@ -48,14 +48,14 @@ void ShapeGroupTest::copy() {
void ShapeGroupTest::reference() { void ShapeGroupTest::reference() {
Physics::Point point({1.0f, 2.0f, 3.0f}); Physics::Point point({1.0f, 2.0f, 3.0f});
Physics::Sphere sphere({2.0f, 1.0f, 30.0f}, 1.0f); Physics::LineSegment segment({2.0f, 1.0f, 30.0f}, {1.0f, -20.0f, 3.0f});
ShapeGroup group = ~(ref(point)|ref(sphere)); ShapeGroup group = !(ref(point) || ref(segment));
group.applyTransformation(Matrix4::translation(Vector3(1.0f))); group.applyTransformation(Matrix4::translation(Vector3(1.0f)));
CORRADE_VERIFY((point.transformedPosition() == Vector3(2.0f, 3.0f, 4.0f))); CORRADE_VERIFY((point.transformedPosition() == Vector3(2.0f, 3.0f, 4.0f)));
CORRADE_VERIFY((sphere.transformedPosition() == Vector3(3.0f, 2.0f, 31.0f))); CORRADE_VERIFY((segment.transformedA() == Vector3(3.0f, 2.0f, 31.0f)));
} }
}}} }}}

Loading…
Cancel
Save