Skip to content

Commit 8deb220

Browse files
committed
Physics shape rework #2: new implementation of ShapeGroup.
This class now stores the tree in flat array, making it easier for user to query the contents, but the internals are much more complicated. This solution already reduces allocation count by count of nodes in the tree, future work might remove the per-shape allocation altogether by using large typeless array and placement-new etc.
1 parent b29f736 commit 8deb220

11 files changed

+796
-222
lines changed

Diff for: src/Physics/CMakeLists.txt

+7-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ set(MagnumPhysics_SRCS
3333
ObjectShape.cpp
3434
ObjectShapeGroup.cpp
3535
ShapeGroup.cpp
36-
Sphere.cpp)
36+
Sphere.cpp
37+
38+
shapeImplementation.cpp
39+
40+
Implementation/CollisionDispatch.cpp)
3741

3842
set(MagnumPhysics_HEADERS
3943
AbstractShape.h
@@ -50,7 +54,8 @@ set(MagnumPhysics_HEADERS
5054
ShapeGroup.h
5155
Sphere.h
5256

53-
magnumPhysicsVisibility.h)
57+
magnumPhysicsVisibility.h
58+
shapeImplementation.h)
5459

5560
add_library(MagnumPhysics ${SHARED_OR_STATIC} ${MagnumPhysics_SRCS})
5661
if(BUILD_STATIC_PIC)

Diff for: src/Physics/Implementation/CollisionDispatch.cpp

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
This file is part of Magnum.
3+
4+
Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš <[email protected]>
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a
7+
copy of this software and associated documentation files (the "Software"),
8+
to deal in the Software without restriction, including without limitation
9+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
10+
and/or sell copies of the Software, and to permit persons to whom the
11+
Software is furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included
14+
in all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22+
DEALINGS IN THE SOFTWARE.
23+
*/
24+
25+
#include "CollisionDispatch.h"
26+
27+
#include "Physics/AxisAlignedBox.h"
28+
#include "Physics/Box.h"
29+
#include "Physics/Capsule.h"
30+
#include "Physics/LineSegment.h"
31+
#include "Physics/Plane.h"
32+
#include "Physics/Point.h"
33+
#include "Physics/Sphere.h"
34+
#include "Physics/shapeImplementation.h"
35+
36+
namespace Magnum { namespace Physics { namespace Implementation {
37+
38+
namespace {
39+
inline constexpr UnsignedInt operator*(ShapeDimensionTraits<2>::Type a, ShapeDimensionTraits<2>::Type b) {
40+
return UnsignedInt(a)*UnsignedInt(b);
41+
}
42+
inline constexpr UnsignedInt operator*(ShapeDimensionTraits<3>::Type a, ShapeDimensionTraits<3>::Type b) {
43+
return UnsignedInt(a)*UnsignedInt(b);
44+
}
45+
}
46+
47+
template<> bool collides(const AbstractShape<2>* const a, const AbstractShape<2>* const b) {
48+
if(a->type() < b->type()) return collides(b, a);
49+
50+
switch(a->type()*b->type()) {
51+
case ShapeDimensionTraits<2>::Type::Sphere*ShapeDimensionTraits<2>::Type::Point:
52+
return static_cast<const Shape<Sphere2D>*>(a)->shape % static_cast<const Shape<Point2D>*>(b)->shape;
53+
case ShapeDimensionTraits<2>::Type::Sphere*ShapeDimensionTraits<2>::Type::Line:
54+
return static_cast<const Shape<Sphere2D>*>(a)->shape % static_cast<const Shape<Line2D>*>(b)->shape;
55+
case ShapeDimensionTraits<2>::Type::Sphere*ShapeDimensionTraits<2>::Type::LineSegment:
56+
return static_cast<const Shape<Sphere2D>*>(a)->shape % static_cast<const Shape<LineSegment2D>*>(b)->shape;
57+
case ShapeDimensionTraits<2>::Type::Sphere*ShapeDimensionTraits<2>::Type::Sphere:
58+
return static_cast<const Shape<Sphere2D>*>(a)->shape % static_cast<const Shape<Sphere2D>*>(b)->shape;
59+
60+
case ShapeDimensionTraits<2>::Type::Capsule*ShapeDimensionTraits<2>::Type::Point:
61+
return static_cast<const Shape<Capsule2D>*>(a)->shape % static_cast<const Shape<Point2D>*>(b)->shape;
62+
case ShapeDimensionTraits<2>::Type::Capsule*ShapeDimensionTraits<2>::Type::Sphere:
63+
return static_cast<const Shape<Capsule2D>*>(a)->shape % static_cast<const Shape<Sphere2D>*>(b)->shape;
64+
65+
case ShapeDimensionTraits<2>::Type::AxisAlignedBox*ShapeDimensionTraits<2>::Type::Point:
66+
return static_cast<const Shape<AxisAlignedBox2D>*>(a)->shape % static_cast<const Shape<Point2D>*>(b)->shape;
67+
}
68+
69+
return false;
70+
}
71+
72+
template<> bool collides(const AbstractShape<3>* const a, const AbstractShape<3>* const b) {
73+
if(a->type() < b->type()) return collides(b, a);
74+
75+
switch(a->type()*b->type()) {
76+
case ShapeDimensionTraits<3>::Type::Sphere*ShapeDimensionTraits<3>::Type::Point:
77+
return static_cast<const Shape<Sphere3D>*>(a)->shape % static_cast<const Shape<Point3D>*>(b)->shape;
78+
case ShapeDimensionTraits<3>::Type::Sphere*ShapeDimensionTraits<3>::Type::Line:
79+
return static_cast<const Shape<Sphere3D>*>(a)->shape % static_cast<const Shape<Line3D>*>(b)->shape;
80+
case ShapeDimensionTraits<3>::Type::Sphere*ShapeDimensionTraits<3>::Type::LineSegment:
81+
return static_cast<const Shape<Sphere3D>*>(a)->shape % static_cast<const Shape<LineSegment3D>*>(b)->shape;
82+
case ShapeDimensionTraits<3>::Type::Sphere*ShapeDimensionTraits<3>::Type::Sphere:
83+
return static_cast<const Shape<Sphere3D>*>(a)->shape % static_cast<const Shape<Sphere3D>*>(b)->shape;
84+
85+
case ShapeDimensionTraits<3>::Type::Capsule*ShapeDimensionTraits<3>::Type::Point:
86+
return static_cast<const Shape<Capsule3D>*>(a)->shape % static_cast<const Shape<Point3D>*>(b)->shape;
87+
case ShapeDimensionTraits<3>::Type::Capsule*ShapeDimensionTraits<3>::Type::Sphere:
88+
return static_cast<const Shape<Capsule3D>*>(a)->shape % static_cast<const Shape<Sphere3D>*>(b)->shape;
89+
90+
case ShapeDimensionTraits<3>::Type::AxisAlignedBox*ShapeDimensionTraits<3>::Type::Point:
91+
return static_cast<const Shape<AxisAlignedBox3D>*>(a)->shape % static_cast<const Shape<Point3D>*>(b)->shape;
92+
93+
case ShapeDimensionTraits<3>::Type::Plane*ShapeDimensionTraits<3>::Type::Line:
94+
return static_cast<const Shape<Plane>*>(a)->shape % static_cast<const Shape<Line3D>*>(b)->shape;
95+
case ShapeDimensionTraits<3>::Type::Plane*ShapeDimensionTraits<3>::Type::LineSegment:
96+
return static_cast<const Shape<Plane>*>(a)->shape % static_cast<const Shape<LineSegment3D>*>(b)->shape;
97+
}
98+
99+
return false;
100+
}
101+
102+
}}}

Diff for: src/Physics/Implementation/CollisionDispatch.h

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#ifndef Magnum_Physics_Implementation_CollisionDispatch_h
2+
#define Magnum_Physics_Implementation_CollisionDispatch_h
3+
/*
4+
This file is part of Magnum.
5+
6+
Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš <[email protected]>
7+
8+
Permission is hereby granted, free of charge, to any person obtaining a
9+
copy of this software and associated documentation files (the "Software"),
10+
to deal in the Software without restriction, including without limitation
11+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
12+
and/or sell copies of the Software, and to permit persons to whom the
13+
Software is furnished to do so, subject to the following conditions:
14+
15+
The above copyright notice and this permission notice shall be included
16+
in all copies or substantial portions of the Software.
17+
18+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24+
DEALINGS IN THE SOFTWARE.
25+
*/
26+
27+
#include "Types.h"
28+
29+
namespace Magnum { namespace Physics { namespace Implementation {
30+
31+
template<UnsignedInt> struct AbstractShape;
32+
33+
/*
34+
Shape collision double-dispatch:
35+
36+
The collision is symmetric, i.e. it doesn't matter if we test Point vs. Sphere
37+
or Sphere vs. Point. Each type is specified by unique prime number. Then we
38+
multiply the two numbers together and switch() on the result. Because of
39+
multiplying two prime numbers, there is no ambiguity (the result is unique for
40+
each combination).
41+
*/
42+
template<UnsignedInt dimensions> bool collides(const AbstractShape<dimensions>* a, const AbstractShape<dimensions>* b);
43+
44+
}}}
45+
46+
#endif

Diff for: src/Physics/ShapeGroup.cpp

+121-29
Original file line numberDiff line numberDiff line change
@@ -24,52 +24,144 @@
2424

2525
#include "ShapeGroup.h"
2626

27+
#include <algorithm>
28+
#include <Utility/Assert.h>
29+
30+
#include "Physics/Implementation/CollisionDispatch.h"
31+
2732
namespace Magnum { namespace Physics {
2833

29-
template<UnsignedInt dimensions> ShapeGroup<dimensions>::ShapeGroup(ShapeGroup<dimensions>&& other): operation(other.operation), a(other.a), b(other.b) {
30-
other.operation = Implementation::GroupOperation::AlwaysFalse;
31-
other.a = nullptr;
32-
other.b = nullptr;
34+
/*
35+
Hierarchy implementation notes:
36+
37+
The hierarchy is stored in flat array to provide easy access for the user and
38+
to save some allocations. Each node has zero, one or two subnodes. Value of
39+
`Node::rightNode` describes which child nodes exist:
40+
41+
* 0 - no child subnodes
42+
* 1 - only left subnode exists
43+
* 2 - only right subnode exists
44+
* >2 - both child nodes exist
45+
46+
If left node exists, it is right next to current one. If right node exists, it
47+
is at position `Node::rightNode-1` relative to current one (this applies also
48+
when `rightNode` is equal to 2, right node is right next to current one,
49+
because there are no left nodes).
50+
51+
The node also specifies which shapes belong to it. Root node owns whole shape
52+
array and `Node::rightShape` marks first shape belonging to the right child
53+
node, relatively to begin. This recurses into child nodes, thus left child node
54+
has shapes from parent's begin to parent's `rightShape`.
55+
56+
Shapes are merged together by concatenating its node and shape list and adding
57+
new node at the beginning with properly set `rightNode` and `rightShape`.
58+
Because these values are relative to parent, they don't need to be modified
59+
when concatenating.
60+
*/
61+
62+
template<UnsignedInt dimensions> ShapeGroup<dimensions>::ShapeGroup(const ShapeGroup<dimensions>& other): _shapeCount(other._shapeCount), _nodeCount(other._nodeCount) {
63+
copyShapes(0, other);
64+
copyNodes(0, other);
65+
}
66+
67+
template<UnsignedInt dimensions> ShapeGroup<dimensions>::ShapeGroup(ShapeGroup<dimensions>&& other): _shapeCount(other._shapeCount), _nodeCount(other._nodeCount), _shapes(other._shapes), _nodes(other._nodes) {
68+
other._shapes = nullptr;
69+
other._shapeCount = 0;
70+
71+
other._nodes = nullptr;
72+
other._nodeCount = 0;
3373
}
3474

3575
template<UnsignedInt dimensions> ShapeGroup<dimensions>::~ShapeGroup() {
36-
if(!(operation & Implementation::GroupOperation::RefA)) delete a;
37-
if(!(operation & Implementation::GroupOperation::RefB)) delete b;
76+
for(std::size_t i = 0; i != _shapeCount; ++i)
77+
delete _shapes[i];
78+
79+
delete[] _shapes;
80+
delete[] _nodes;
3881
}
3982

40-
template<UnsignedInt dimensions> ShapeGroup<dimensions>& ShapeGroup<dimensions>::operator=(ShapeGroup<dimensions>&& other) {
41-
if(!(operation & Implementation::GroupOperation::RefA)) delete a;
42-
if(!(operation & Implementation::GroupOperation::RefB)) delete b;
83+
template<UnsignedInt dimensions> ShapeGroup<dimensions>& ShapeGroup<dimensions>::operator=(const ShapeGroup<dimensions>& other) {
84+
for(std::size_t i = 0; i != _shapeCount; ++i)
85+
delete _shapes[i];
4386

44-
operation = other.operation;
45-
a = other.a;
46-
b = other.b;
87+
if(_shapeCount != other._shapeCount) {
88+
delete[] _shapes;
89+
_shapeCount = other._shapeCount;
90+
_shapes = new Implementation::AbstractShape<dimensions>*[_shapeCount];
91+
}
4792

48-
other.operation = Implementation::GroupOperation::AlwaysFalse;
49-
other.a = nullptr;
50-
other.b = nullptr;
93+
if(_nodeCount != other._nodeCount) {
94+
delete[] _nodes;
95+
_nodeCount = other._nodeCount;
96+
_nodes = new Node[_nodeCount];
97+
}
5198

99+
copyShapes(0, other);
100+
copyNodes(0, other);
52101
return *this;
53102
}
54103

55-
template<UnsignedInt dimensions> void ShapeGroup<dimensions>::applyTransformationMatrix(const typename DimensionTraits<dimensions>::MatrixType& matrix) {
56-
if(a) a->applyTransformationMatrix(matrix);
57-
if(b) b->applyTransformationMatrix(matrix);
104+
template<UnsignedInt dimensions> ShapeGroup<dimensions>& ShapeGroup<dimensions>::operator=(ShapeGroup<dimensions>&& other) {
105+
std::swap(other._shapeCount, _shapeCount);
106+
std::swap(other._nodeCount, _nodeCount);
107+
std::swap(other._shapes, _shapes);
108+
std::swap(other._nodes, _nodes);
109+
return *this;
58110
}
59111

60-
template<UnsignedInt dimensions> bool ShapeGroup<dimensions>::collides(const AbstractShape<dimensions>* other) const {
61-
switch(operation & ~Implementation::GroupOperation::RefAB) {
62-
case Implementation::GroupOperation::And: return a->collides(other) && b->collides(other);
63-
case Implementation::GroupOperation::Or: return a->collides(other) || b->collides(other);
64-
case Implementation::GroupOperation::Not: return !a->collides(other);
65-
case Implementation::GroupOperation::FirstObjectOnly: return a->collides(other);
112+
template<UnsignedInt dimensions> void ShapeGroup<dimensions>::copyShapes(const std::size_t offset, ShapeGroup<dimensions>&& other) {
113+
std::move(other._shapes, other._shapes+other._shapeCount, _shapes+offset);
114+
delete[] other._shapes;
115+
other._shapes = nullptr;
116+
other._shapeCount = 0;
117+
}
66118

67-
default:
68-
return false;
69-
}
119+
template<UnsignedInt dimensions> void ShapeGroup<dimensions>::copyShapes(const std::size_t offset, const ShapeGroup<dimensions>& other) {
120+
for(std::size_t i = 0; i != other._shapeCount; ++i)
121+
_shapes[i+offset] = other._shapes[i]->clone();
122+
}
123+
124+
template<UnsignedInt dimensions> void ShapeGroup<dimensions>::copyNodes(std::size_t offset, const ShapeGroup<dimensions>& other) {
125+
std::copy(other._nodes, other._nodes+other._nodeCount, _nodes+offset);
126+
}
127+
128+
template<UnsignedInt dimensions> ShapeGroup<dimensions> ShapeGroup<dimensions>::transformed(const typename DimensionTraits<dimensions>::MatrixType& matrix) const {
129+
ShapeGroup<dimensions> out(*this);
130+
for(std::size_t i = 0; i != _shapeCount; ++i)
131+
_shapes[i]->transform(matrix, out._shapes[i]);
132+
return out;
133+
}
134+
135+
template<UnsignedInt dimensions> bool ShapeGroup<dimensions>::collides(const Implementation::AbstractShape<dimensions>* const a, const std::size_t node, const std::size_t shapeBegin, const std::size_t shapeEnd) const {
136+
/* Empty group */
137+
if(shapeBegin == shapeEnd) return false;
138+
139+
CORRADE_INTERNAL_ASSERT(node < _nodeCount && shapeBegin < shapeEnd);
140+
141+
/* Collision on the left child. If the node is leaf one (no left child
142+
exists), do it directly, recurse instead. */
143+
const bool collidesLeft = (_nodes[node].rightNode == 0 || _nodes[node].rightNode == 2) ?
144+
Implementation::collides(a, _shapes[shapeBegin]) :
145+
collides(a, node+1, shapeBegin, shapeBegin+_nodes[node].rightShape);
146+
147+
/* NOT operation */
148+
if(_nodes[node].operation == ShapeOperation::Not)
149+
return !collidesLeft;
150+
151+
/* Short-circuit evaluation for AND/OR */
152+
if((_nodes[node].operation == ShapeOperation::Or) == collidesLeft)
153+
return collidesLeft;
154+
155+
/* Now the collision result depends only on the right child. Similar to
156+
collision on the left child. */
157+
return (_nodes[node].rightNode < 2) ?
158+
Implementation::collides(a, _shapes[shapeBegin+_nodes[node].rightShape]) :
159+
collides(a, node+_nodes[node].rightNode-1, shapeBegin+_nodes[node].rightShape, shapeEnd);
70160
}
71161

72-
template class ShapeGroup<2>;
73-
template class ShapeGroup<3>;
162+
#ifndef DOXYGEN_GENERATING_OUTPUT
163+
template class MAGNUM_PHYSICS_EXPORT ShapeGroup<2>;
164+
template class MAGNUM_PHYSICS_EXPORT ShapeGroup<3>;
165+
#endif
74166

75167
}}

0 commit comments

Comments
 (0)