1 #include <bz/collision/collision.h>
3 #include <bz/debug/assert.h>
4 #include <bz/math/math.h>
5 #include <bz/memory/allocator.h>
7 struct BZCollisionBody
{
8 BZCollisionBodyType type
;
9 BZCollisionShape shape
;
10 BZIdentifierHash tagHash
;
14 struct BZCollisionSpace
{
17 BZCollisionBody bodies
[];
20 const BZCollisionShapeType BZCollisionShapeTypeMax
= BZCollisionShapeTypeLine
;
22 enum CollisionTestResult
{
23 CollisionTestResultNoCollision
,
24 CollisionTestResultCollision
,
25 CollisionTestResultCollisionInverted
,
27 typedef enum CollisionTestResult CollisionTestResult
;
29 struct CollisionDetails
{
33 typedef struct CollisionDetails CollisionDetails
;
35 static bool collisionCircleCircleTest(CollisionDetails
*detailsOut
, float *penetrationOut
, const BZCollisionShape
*shape1
, const BZCollisionShape
*shape2
) {
36 bool colliding
= false;
39 bzVectorSubtract(&delta
, &shape2
->circleOrigin
, &shape1
->circleOrigin
);
40 float radius
= shape1
->circleRadius
+ shape2
->circleRadius
;
42 float deltaMagnitude
= bzVectorMagnitude(&delta
);
43 colliding
= (deltaMagnitude
> 0.01f
&& deltaMagnitude
<= radius
);
45 bzVectorInverse(&delta
, &delta
);
46 bzVectorNormalized(&detailsOut
->normal
, &delta
);
47 bzVectorScale(&detailsOut
->position
, &detailsOut
->normal
, radius
);
48 bzVectorAdd(&detailsOut
->position
, &detailsOut
->position
, &shape2
->circleOrigin
);
49 *penetrationOut
= (radius
- deltaMagnitude
);
55 static bool collisionCircleCirclePerimeterTest(CollisionDetails
*detailsOut
, float *penetrationOut
, const BZCollisionShape
*shape1
, const BZCollisionShape
*shape2
) {
56 bool colliding
= false;
59 bzVectorSubtract(&delta
, &shape1
->circleOrigin
, &shape2
->circleOrigin
);
60 float innerRadius
= shape2
->circleRadius
- shape1
->circleRadius
;
62 float deltaMagnitude
= bzVectorMagnitude(&delta
);
63 colliding
= deltaMagnitude
> innerRadius
;// (deltaMagnitude > 0.01f && deltaMagnitude <= radius);
65 bzVectorNormalized(&detailsOut
->normal
, &delta
);
66 bzVectorScale(&detailsOut
->position
, &detailsOut
->normal
, shape2
->circleRadius
);
67 bzVectorAdd(&detailsOut
->position
, &detailsOut
->position
, &shape2
->circleOrigin
);
69 bzVectorInverse(&detailsOut
->normal
, &detailsOut
->normal
);
71 *penetrationOut
= shape1
->circleRadius
;
77 static bool collisionLineLineTest(CollisionDetails
*detailsOut
, float *penetrationOut
, const BZCollisionShape
*shape1
, const BZCollisionShape
*shape2
) {
78 bool colliding
= false;
81 bzVectorSubtract(&delta
, &shape2
->lineOrigin
, &shape1
->lineOrigin
);
83 float lineCross
= bzVectorCross(&shape1
->lineDirection
, &shape2
->lineDirection
);
85 float t
= bzVectorCross(&delta
, &shape2
->lineDirection
) / lineCross
;
86 if (t
>= 0.0f
&& t
<= shape1
->lineMagnitude
) {
87 float u
= bzVectorCross(&delta
, &shape1
->lineDirection
) / lineCross
;
88 if (u
>= 0.0f
&& u
<= shape2
->lineMagnitude
) {
89 bzVectorScale(&delta
, &shape1
->lineDirection
, t
);
90 bzVectorAdd(&detailsOut
->position
, &delta
, &shape1
->lineOrigin
);
98 static bool collisionCircleLineTest(CollisionDetails
*detailsOut
, float *penetrationOut
, const BZCollisionShape
*shape1
, const BZCollisionShape
*shape2
) {
99 bool colliding
= false;
102 bzVectorSubtract(&delta
, &shape1
->circleOrigin
, &shape2
->lineOrigin
);
104 float dot
= bzVectorDot(&delta
, &shape2
->lineDirection
);
105 if (dot
> 0.01f
&& dot
<= shape2
->lineMagnitude
) {
106 BZVector projection
, test
;
107 bzVectorScale(&projection
, &shape2
->lineDirection
, dot
);
108 bzVectorSubtract(&test
, &delta
, &projection
);
109 float magnitude
= bzVectorMagnitude(&test
);
110 colliding
= (magnitude
> 0.01f
&& magnitude
<= shape1
->circleRadius
);
112 bzVectorAdd(&detailsOut
->position
, &projection
, &shape2
->lineOrigin
);
113 bzVectorNormalized(&detailsOut
->normal
, &test
);
114 *penetrationOut
= bzAbs(magnitude
- shape1
->circleRadius
);
123 static bool collisionCircleTriangleTest(CollisionDetails
*detailsOut
, float *penetrationOut
, const BZCollisionShape
*shape1
, const BZCollisionShape
*shape2
) {
124 // https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle
126 #define tsign(x1, y1, x2, y2, x3, y3) (((x1)-(x3))*((y2)-(y3))-((x2)-(x3))*((y1)-(y3)))
128 float x
= shape1
->circleOrigin
.x
;
129 float y
= shape1
->circleOrigin
.y
;
131 float x1
= shape2
->trianglePoint1
.x
;
132 float y1
= shape2
->trianglePoint1
.y
;
133 float x2
= shape2
->trianglePoint2
.x
;
134 float y2
= shape2
->trianglePoint2
.y
;
135 float x3
= shape2
->trianglePoint3
.x
;
136 float y3
= shape2
->trianglePoint3
.y
;
138 float a
= tsign(x
,y
,x1
,y1
,x2
,y2
);
139 float b
= tsign(x
,y
,x2
,y2
,x3
,y3
);
140 float c
= tsign(x
,y
,x3
,y3
,x1
,y1
);
142 // FIXME, need to do radius.
144 return ! ((a
<0 || b
<0 || c
<0) && (a
>0 || b
>0 || c
>0));
146 /*bool colliding = false;
149 bzVectorSubtract(&delta, &shape1->circleOrigin, &shape2->lineOrigin);
151 float dot = bzVectorDot(&delta, &shape2->lineDirection);
152 if (dot > 0.01f && dot <= shape2->lineMagnitude) {
153 BZVector projection, test;
154 bzVectorScale(&projection, &shape2->lineDirection, dot);
155 bzVectorSubtract(&test, &delta, &projection);
156 float magnitude = bzVectorMagnitude(&test);
157 colliding = (magnitude > 0.01f && magnitude <= shape1->circleRadius);
159 bzVectorAdd(&detailsOut->position, &projection, &shape2->lineOrigin);
160 bzVectorNormalized(&detailsOut->normal, &test);
161 *penetrationOut = bzAbs(magnitude - shape1->circleRadius);
169 static CollisionTestResult
collisionTest(CollisionDetails
*detailsOut
, float *penetrationOut
, const BZCollisionShape
*shape1
, const BZCollisionShape
*shape2
) {
170 #define TEST_MASK(type1, type2) (((type1) << BZCollisionShapeTypeMax) | (type2))
172 switch (TEST_MASK(shape1
->type
, shape2
->type
)) {
173 case TEST_MASK(BZCollisionShapeTypeCircle
, BZCollisionShapeTypeCircle
):
174 return collisionCircleCircleTest(detailsOut
, penetrationOut
, shape1
, shape2
) ? CollisionTestResultCollision
: CollisionTestResultNoCollision
;
176 case TEST_MASK(BZCollisionShapeTypeCircle
, BZCollisionShapeTypeCirclePerimeter
):
177 return collisionCircleCirclePerimeterTest(detailsOut
, penetrationOut
, shape1
, shape2
) ? CollisionTestResultCollision
: CollisionTestResultNoCollision
;
179 case TEST_MASK(BZCollisionShapeTypeCirclePerimeter
, BZCollisionShapeTypeCircle
):
180 return collisionCircleCirclePerimeterTest(detailsOut
, penetrationOut
, shape2
, shape1
) ? CollisionTestResultCollisionInverted
: CollisionTestResultNoCollision
;
182 case TEST_MASK(BZCollisionShapeTypeLine
, BZCollisionShapeTypeLine
):
183 return collisionLineLineTest(detailsOut
, penetrationOut
, shape1
, shape2
) ? CollisionTestResultCollision
: CollisionTestResultNoCollision
;
185 case TEST_MASK(BZCollisionShapeTypeCircle
, BZCollisionShapeTypeLine
):
186 return collisionCircleLineTest(detailsOut
, penetrationOut
, shape1
, shape2
) ? CollisionTestResultCollision
: CollisionTestResultNoCollision
;
188 case TEST_MASK(BZCollisionShapeTypeLine
, BZCollisionShapeTypeCircle
):
189 return collisionCircleLineTest(detailsOut
, penetrationOut
, shape2
, shape1
) ? CollisionTestResultCollisionInverted
: CollisionTestResultNoCollision
;
191 case TEST_MASK(BZCollisionShapeTypeCircle
, BZCollisionShapeTypeTriangle
):
192 return collisionCircleTriangleTest(detailsOut
, penetrationOut
, shape1
, shape2
) ? CollisionTestResultCollision
: CollisionTestResultNoCollision
;
194 case TEST_MASK(BZCollisionShapeTypeTriangle
, BZCollisionShapeTypeCircle
):
195 return collisionCircleTriangleTest(detailsOut
, penetrationOut
, shape2
, shape1
) ? CollisionTestResultCollisionInverted
: CollisionTestResultNoCollision
;
199 bzAssertMessage(false, "Invalid collision test");
216 BZCollisionSpaceID
bzCollisionMakeSpace(BZMemoryArenaID arena
, size_t maxBodies
, const char *identifierFmt
, ...) {
217 BZCollisionSpaceID space
= (BZCollisionSpace
*)bzMemoryAlloc(arena
, sizeof(BZCollisionSpace
) + maxBodies
* sizeof(BZCollisionBody
));
218 space
->maxBodies
= maxBodies
;
219 bzCollisionResetSpace(space
);
223 void bzCollisionResetSpace(BZCollisionSpaceID space
) {
227 BZCollisionBodyID
bzCollisionAddBody(BZCollisionSpaceID space
, BZCollisionBodyType bodyType
, const BZCollisionShape
*shape
, BZIdentifierHash tagHash
, void *userParameter
) {
228 bzAssert(space
->nextBody
< space
->maxBodies
);
229 BZCollisionBodyID body
= &space
->bodies
[space
->nextBody
++];
230 body
->type
= bodyType
;
231 body
->shape
= *shape
;
232 body
->tagHash
= tagHash
;
233 body
->userParameter
= userParameter
;
238 bool bzCollisionResolve(BZVector
*positionOut
, void **userParameterOut
, BZCollisionSpaceID space
, const BZCollisionShape
*shape
, BZIdentifierHash tagHash
) {
239 CollisionDetails details
;
241 for (size_t i
= 0; i
< space
->nextBody
; ++i
) {
242 BZCollisionBody
*body
= &space
->bodies
[i
];
243 if (body
->tagHash
== tagHash
) {
244 CollisionTestResult testResult
= collisionTest(&details
, &penetration
, shape
, &body
->shape
);
245 if (testResult
!= CollisionTestResultNoCollision
) {
246 if (positionOut
!= NULL
) {
248 bzVectorScale(&delta
, &details
.normal
, penetration
);
249 bzVectorAdd(positionOut
, &details
.position
, &delta
);
251 if (userParameterOut
!= NULL
) {
252 *userParameterOut
= body
->userParameter
;
265 #include <bz/gfx/gfx.h>
266 void bzCollisionDrawDebug(BZCollisionSpaceID space
) {
267 for (size_t i
= 0; i
< space
->nextBody
; ++i
) {
268 switch (space
->bodies
[i
].shape
.type
) {
269 case BZCollisionShapeTypeCircle
:
270 bzCirc(space
->bodies
[i
].shape
.circleOrigin
.x
, space
->bodies
[i
].shape
.circleOrigin
.y
, space
->bodies
[i
].shape
.circleRadius
, 2);