chipmunk 5.3.4.5 → 6.1.3.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/ext/chipmunk/chipmunk.c +199 -28
- data/ext/chipmunk/chipmunk.h +123 -68
- data/ext/chipmunk/chipmunk_ffi.h +129 -11
- data/ext/chipmunk/chipmunk_private.h +232 -16
- data/ext/chipmunk/chipmunk_types.h +94 -30
- data/ext/chipmunk/chipmunk_unsafe.h +12 -3
- data/ext/chipmunk/constraints/cpConstraint.h +90 -34
- data/ext/chipmunk/{cpDampedRotarySpring.h → constraints/cpDampedRotarySpring.h} +18 -8
- data/ext/chipmunk/{cpDampedSpring.h → constraints/cpDampedSpring.h} +27 -16
- data/ext/chipmunk/constraints/cpGearJoint.h +17 -7
- data/ext/chipmunk/constraints/cpGrooveJoint.h +19 -10
- data/ext/chipmunk/constraints/cpPinJoint.h +17 -8
- data/ext/chipmunk/constraints/cpPivotJoint.h +18 -9
- data/ext/chipmunk/constraints/cpRatchetJoint.h +17 -8
- data/ext/chipmunk/constraints/cpRotaryLimitJoint.h +16 -7
- data/ext/chipmunk/{cpSimpleMotor.h → constraints/cpSimpleMotor.h} +15 -6
- data/ext/chipmunk/constraints/cpSlideJoint.h +18 -9
- data/ext/chipmunk/constraints/util.h +36 -44
- data/ext/chipmunk/cpArbiter.c +159 -94
- data/ext/chipmunk/cpArbiter.h +135 -129
- data/ext/chipmunk/cpArray.c +37 -56
- data/ext/chipmunk/cpBB.c +1 -12
- data/ext/chipmunk/cpBB.h +80 -18
- data/ext/chipmunk/cpBBTree.c +891 -0
- data/ext/chipmunk/cpBody.c +185 -47
- data/ext/chipmunk/cpBody.h +156 -124
- data/ext/chipmunk/cpCollision.c +126 -115
- data/ext/chipmunk/cpConstraint.c +10 -6
- data/ext/chipmunk/cpDampedRotarySpring.c +26 -17
- data/ext/chipmunk/cpDampedSpring.c +25 -18
- data/ext/chipmunk/cpGearJoint.c +23 -17
- data/ext/chipmunk/cpGrooveJoint.c +26 -22
- data/ext/chipmunk/cpHashSet.c +51 -51
- data/ext/chipmunk/cpPinJoint.c +26 -19
- data/ext/chipmunk/cpPivotJoint.c +23 -19
- data/ext/chipmunk/cpPolyShape.c +93 -69
- data/ext/chipmunk/cpPolyShape.h +33 -69
- data/ext/chipmunk/cpRatchetJoint.c +26 -21
- data/ext/chipmunk/cpRotaryLimitJoint.c +28 -22
- data/ext/chipmunk/cpShape.c +122 -133
- data/ext/chipmunk/cpShape.h +146 -95
- data/ext/chipmunk/cpSimpleMotor.c +24 -17
- data/ext/chipmunk/cpSlideJoint.c +28 -26
- data/ext/chipmunk/cpSpace.c +251 -196
- data/ext/chipmunk/cpSpace.h +173 -103
- data/ext/chipmunk/cpSpaceComponent.c +236 -159
- data/ext/chipmunk/cpSpaceHash.c +259 -159
- data/ext/chipmunk/cpSpaceQuery.c +127 -59
- data/ext/chipmunk/cpSpaceStep.c +235 -197
- data/ext/chipmunk/cpSpatialIndex.c +69 -0
- data/ext/chipmunk/cpSpatialIndex.h +227 -0
- data/ext/chipmunk/cpSweep1D.c +254 -0
- data/ext/chipmunk/cpVect.c +11 -26
- data/ext/chipmunk/cpVect.h +76 -71
- data/ext/chipmunk/extconf.rb +4 -31
- data/ext/chipmunk/prime.h +1 -1
- data/ext/chipmunk/rb_chipmunk.c +36 -45
- data/ext/chipmunk/rb_chipmunk.h +6 -3
- data/ext/chipmunk/rb_cpArbiter.c +2 -2
- data/ext/chipmunk/rb_cpBB.c +116 -35
- data/ext/chipmunk/rb_cpBody.c +5 -12
- data/ext/chipmunk/rb_cpConstraint.c +144 -9
- data/ext/chipmunk/rb_cpShape.c +69 -78
- data/ext/chipmunk/rb_cpSpace.c +81 -76
- metadata +61 -61
- data/LICENSE +0 -22
- data/README +0 -110
- data/Rakefile +0 -102
- data/ext/chipmunk/cpArray.h +0 -49
- data/ext/chipmunk/cpCollision.h +0 -28
- data/ext/chipmunk/cpHashSet.h +0 -82
- data/ext/chipmunk/cpSpaceHash.h +0 -110
- data/lib/chipmunk.rb +0 -194
data/ext/chipmunk/cpSpaceQuery.c
CHANGED
@@ -19,25 +19,24 @@
|
|
19
19
|
* SOFTWARE.
|
20
20
|
*/
|
21
21
|
|
22
|
-
#include <stdlib.h>
|
23
|
-
|
24
22
|
#include "chipmunk_private.h"
|
25
23
|
|
26
|
-
|
24
|
+
//MARK: Point Query Functions
|
27
25
|
|
28
|
-
|
26
|
+
struct PointQueryContext {
|
27
|
+
cpVect point;
|
29
28
|
cpLayers layers;
|
30
29
|
cpGroup group;
|
31
30
|
cpSpacePointQueryFunc func;
|
32
31
|
void *data;
|
33
|
-
}
|
32
|
+
};
|
34
33
|
|
35
34
|
static void
|
36
|
-
|
35
|
+
PointQuery(struct PointQueryContext *context, cpShape *shape, void *data)
|
37
36
|
{
|
38
37
|
if(
|
39
38
|
!(shape->group && context->group == shape->group) && (context->layers&shape->layers) &&
|
40
|
-
cpShapePointQuery(shape,
|
39
|
+
cpShapePointQuery(shape, context->point)
|
41
40
|
){
|
42
41
|
context->func(shape, context->data);
|
43
42
|
}
|
@@ -46,16 +45,17 @@ pointQueryHelper(cpVect *point, cpShape *shape, pointQueryContext *context)
|
|
46
45
|
void
|
47
46
|
cpSpacePointQuery(cpSpace *space, cpVect point, cpLayers layers, cpGroup group, cpSpacePointQueryFunc func, void *data)
|
48
47
|
{
|
49
|
-
|
48
|
+
struct PointQueryContext context = {point, layers, group, func, data};
|
49
|
+
cpBB bb = cpBBNewForCircle(point, 0.0f);
|
50
50
|
|
51
51
|
cpSpaceLock(space); {
|
52
|
-
|
53
|
-
|
54
|
-
} cpSpaceUnlock(space);
|
52
|
+
cpSpatialIndexQuery(space->activeShapes, &context, bb, (cpSpatialIndexQueryFunc)PointQuery, data);
|
53
|
+
cpSpatialIndexQuery(space->staticShapes, &context, bb, (cpSpatialIndexQueryFunc)PointQuery, data);
|
54
|
+
} cpSpaceUnlock(space, cpTrue);
|
55
55
|
}
|
56
56
|
|
57
57
|
static void
|
58
|
-
|
58
|
+
PointQueryFirst(cpShape *shape, cpShape **outShape)
|
59
59
|
{
|
60
60
|
if(!shape->sensor) *outShape = shape;
|
61
61
|
}
|
@@ -64,23 +64,94 @@ cpShape *
|
|
64
64
|
cpSpacePointQueryFirst(cpSpace *space, cpVect point, cpLayers layers, cpGroup group)
|
65
65
|
{
|
66
66
|
cpShape *shape = NULL;
|
67
|
-
cpSpacePointQuery(space, point, layers, group, (cpSpacePointQueryFunc)
|
67
|
+
cpSpacePointQuery(space, point, layers, group, (cpSpacePointQueryFunc)PointQueryFirst, &shape);
|
68
68
|
|
69
69
|
return shape;
|
70
70
|
}
|
71
71
|
|
72
|
+
//MARK: Nearest Point Query Functions
|
73
|
+
|
74
|
+
struct NearestPointQueryContext {
|
75
|
+
cpVect point;
|
76
|
+
cpFloat maxDistance;
|
77
|
+
cpLayers layers;
|
78
|
+
cpGroup group;
|
79
|
+
cpSpaceNearestPointQueryFunc func;
|
80
|
+
};
|
81
|
+
|
82
|
+
static void
|
83
|
+
NearestPointQuery(struct NearestPointQueryContext *context, cpShape *shape, void *data)
|
84
|
+
{
|
85
|
+
if(
|
86
|
+
!(shape->group && context->group == shape->group) && (context->layers&shape->layers)
|
87
|
+
){
|
88
|
+
cpNearestPointQueryInfo info;
|
89
|
+
cpShapeNearestPointQuery(shape, context->point, &info);
|
90
|
+
|
91
|
+
if(info.shape && info.d < context->maxDistance) context->func(shape, info.d, info.p, data);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
void
|
96
|
+
cpSpaceNearestPointQuery(cpSpace *space, cpVect point, cpFloat maxDistance, cpLayers layers, cpGroup group, cpSpaceNearestPointQueryFunc func, void *data)
|
97
|
+
{
|
98
|
+
struct NearestPointQueryContext context = {point, maxDistance, layers, group, func};
|
99
|
+
cpBB bb = cpBBNewForCircle(point, cpfmax(maxDistance, 0.0f));
|
100
|
+
|
101
|
+
cpSpaceLock(space); {
|
102
|
+
cpSpatialIndexQuery(space->activeShapes, &context, bb, (cpSpatialIndexQueryFunc)NearestPointQuery, data);
|
103
|
+
cpSpatialIndexQuery(space->staticShapes, &context, bb, (cpSpatialIndexQueryFunc)NearestPointQuery, data);
|
104
|
+
} cpSpaceUnlock(space, cpTrue);
|
105
|
+
}
|
106
|
+
|
107
|
+
static void
|
108
|
+
NearestPointQueryNearest(struct NearestPointQueryContext *context, cpShape *shape, cpNearestPointQueryInfo *out)
|
109
|
+
{
|
110
|
+
if(
|
111
|
+
!(shape->group && context->group == shape->group) && (context->layers&shape->layers) && !shape->sensor
|
112
|
+
){
|
113
|
+
cpNearestPointQueryInfo info;
|
114
|
+
cpShapeNearestPointQuery(shape, context->point, &info);
|
115
|
+
|
116
|
+
if(info.d < out->d) (*out) = info;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
cpShape *
|
121
|
+
cpSpaceNearestPointQueryNearest(cpSpace *space, cpVect point, cpFloat maxDistance, cpLayers layers, cpGroup group, cpNearestPointQueryInfo *out)
|
122
|
+
{
|
123
|
+
cpNearestPointQueryInfo info = {NULL, cpvzero, maxDistance};
|
124
|
+
if(out){
|
125
|
+
(*out) = info;
|
126
|
+
} else {
|
127
|
+
out = &info;
|
128
|
+
}
|
129
|
+
|
130
|
+
struct NearestPointQueryContext context = {
|
131
|
+
point, maxDistance,
|
132
|
+
layers, group,
|
133
|
+
NULL
|
134
|
+
};
|
135
|
+
|
136
|
+
cpBB bb = cpBBNewForCircle(point, cpfmax(maxDistance, 0.0f));
|
137
|
+
cpSpatialIndexQuery(space->activeShapes, &context, bb, (cpSpatialIndexQueryFunc)NearestPointQueryNearest, out);
|
138
|
+
cpSpatialIndexQuery(space->staticShapes, &context, bb, (cpSpatialIndexQueryFunc)NearestPointQueryNearest, out);
|
139
|
+
|
140
|
+
return out->shape;
|
141
|
+
}
|
142
|
+
|
72
143
|
|
73
|
-
|
144
|
+
//MARK: Segment Query Functions
|
74
145
|
|
75
|
-
|
146
|
+
struct SegmentQueryContext {
|
76
147
|
cpVect start, end;
|
77
148
|
cpLayers layers;
|
78
149
|
cpGroup group;
|
79
150
|
cpSpaceSegmentQueryFunc func;
|
80
|
-
}
|
151
|
+
};
|
81
152
|
|
82
153
|
static cpFloat
|
83
|
-
|
154
|
+
SegmentQuery(struct SegmentQueryContext *context, cpShape *shape, void *data)
|
84
155
|
{
|
85
156
|
cpSegmentQueryInfo info;
|
86
157
|
|
@@ -97,37 +168,30 @@ segQueryFunc(segQueryContext *context, cpShape *shape, void *data)
|
|
97
168
|
void
|
98
169
|
cpSpaceSegmentQuery(cpSpace *space, cpVect start, cpVect end, cpLayers layers, cpGroup group, cpSpaceSegmentQueryFunc func, void *data)
|
99
170
|
{
|
100
|
-
|
171
|
+
struct SegmentQueryContext context = {
|
101
172
|
start, end,
|
102
173
|
layers, group,
|
103
174
|
func,
|
104
175
|
};
|
105
176
|
|
106
177
|
cpSpaceLock(space); {
|
107
|
-
|
108
|
-
|
109
|
-
} cpSpaceUnlock(space);
|
178
|
+
cpSpatialIndexSegmentQuery(space->staticShapes, &context, start, end, 1.0f, (cpSpatialIndexSegmentQueryFunc)SegmentQuery, data);
|
179
|
+
cpSpatialIndexSegmentQuery(space->activeShapes, &context, start, end, 1.0f, (cpSpatialIndexSegmentQueryFunc)SegmentQuery, data);
|
180
|
+
} cpSpaceUnlock(space, cpTrue);
|
110
181
|
}
|
111
182
|
|
112
|
-
typedef struct segQueryFirstContext {
|
113
|
-
cpVect start, end;
|
114
|
-
cpLayers layers;
|
115
|
-
cpGroup group;
|
116
|
-
} segQueryFirstContext;
|
117
|
-
|
118
183
|
static cpFloat
|
119
|
-
|
184
|
+
SegmentQueryFirst(struct SegmentQueryContext *context, cpShape *shape, cpSegmentQueryInfo *out)
|
120
185
|
{
|
121
186
|
cpSegmentQueryInfo info;
|
122
187
|
|
123
188
|
if(
|
124
|
-
!(shape->group && context->group == shape->group) &&
|
125
|
-
(context->layers&shape->layers) &&
|
189
|
+
!(shape->group && context->group == shape->group) && (context->layers&shape->layers) &&
|
126
190
|
!shape->sensor &&
|
127
191
|
cpShapeSegmentQuery(shape, context->start, context->end, &info) &&
|
128
192
|
info.t < out->t
|
129
193
|
){
|
130
|
-
*out = info;
|
194
|
+
(*out) = info;
|
131
195
|
}
|
132
196
|
|
133
197
|
return out->t;
|
@@ -143,65 +207,66 @@ cpSpaceSegmentQueryFirst(cpSpace *space, cpVect start, cpVect end, cpLayers laye
|
|
143
207
|
out = &info;
|
144
208
|
}
|
145
209
|
|
146
|
-
|
210
|
+
struct SegmentQueryContext context = {
|
147
211
|
start, end,
|
148
|
-
layers, group
|
212
|
+
layers, group,
|
213
|
+
NULL
|
149
214
|
};
|
150
215
|
|
151
|
-
|
152
|
-
|
216
|
+
cpSpatialIndexSegmentQuery(space->staticShapes, &context, start, end, 1.0f, (cpSpatialIndexSegmentQueryFunc)SegmentQueryFirst, out);
|
217
|
+
cpSpatialIndexSegmentQuery(space->activeShapes, &context, start, end, out->t, (cpSpatialIndexSegmentQueryFunc)SegmentQueryFirst, out);
|
153
218
|
|
154
219
|
return out->shape;
|
155
220
|
}
|
156
221
|
|
157
|
-
|
222
|
+
//MARK: BB Query Functions
|
158
223
|
|
159
|
-
|
224
|
+
struct BBQueryContext {
|
225
|
+
cpBB bb;
|
160
226
|
cpLayers layers;
|
161
227
|
cpGroup group;
|
162
228
|
cpSpaceBBQueryFunc func;
|
163
|
-
|
164
|
-
} bbQueryContext;
|
229
|
+
};
|
165
230
|
|
166
231
|
static void
|
167
|
-
|
232
|
+
BBQuery(struct BBQueryContext *context, cpShape *shape, void *data)
|
168
233
|
{
|
169
234
|
if(
|
170
235
|
!(shape->group && context->group == shape->group) && (context->layers&shape->layers) &&
|
171
|
-
|
236
|
+
cpBBIntersects(context->bb, shape->bb)
|
172
237
|
){
|
173
|
-
context->func(shape,
|
238
|
+
context->func(shape, data);
|
174
239
|
}
|
175
240
|
}
|
176
241
|
|
177
242
|
void
|
178
243
|
cpSpaceBBQuery(cpSpace *space, cpBB bb, cpLayers layers, cpGroup group, cpSpaceBBQueryFunc func, void *data)
|
179
244
|
{
|
180
|
-
|
245
|
+
struct BBQueryContext context = {bb, layers, group, func};
|
181
246
|
|
182
247
|
cpSpaceLock(space); {
|
183
|
-
|
184
|
-
|
185
|
-
} cpSpaceUnlock(space);
|
248
|
+
cpSpatialIndexQuery(space->activeShapes, &context, bb, (cpSpatialIndexQueryFunc)BBQuery, data);
|
249
|
+
cpSpatialIndexQuery(space->staticShapes, &context, bb, (cpSpatialIndexQueryFunc)BBQuery, data);
|
250
|
+
} cpSpaceUnlock(space, cpTrue);
|
186
251
|
}
|
187
252
|
|
188
|
-
|
253
|
+
//MARK: Shape Query Functions
|
189
254
|
|
190
|
-
|
255
|
+
struct ShapeQueryContext {
|
191
256
|
cpSpaceShapeQueryFunc func;
|
192
257
|
void *data;
|
193
258
|
cpBool anyCollision;
|
194
|
-
}
|
259
|
+
};
|
195
260
|
|
196
261
|
// Callback from the spatial hash.
|
197
262
|
static void
|
198
|
-
|
263
|
+
ShapeQuery(cpShape *a, cpShape *b, struct ShapeQueryContext *context)
|
199
264
|
{
|
200
265
|
// Reject any of the simple cases
|
201
266
|
if(
|
202
267
|
(a->group && a->group == b->group) ||
|
203
268
|
!(a->layers & b->layers) ||
|
204
|
-
a
|
269
|
+
a == b
|
205
270
|
) return;
|
206
271
|
|
207
272
|
cpContact contacts[CP_MAX_CONTACTS_PER_ARBITER];
|
@@ -216,13 +281,15 @@ shapeQueryHelper(cpShape *a, cpShape *b, shapeQueryContext *context)
|
|
216
281
|
}
|
217
282
|
|
218
283
|
if(numContacts){
|
219
|
-
context->anyCollision =
|
284
|
+
context->anyCollision = !(a->sensor || b->sensor);
|
220
285
|
|
221
286
|
if(context->func){
|
222
|
-
cpContactPointSet set
|
287
|
+
cpContactPointSet set;
|
288
|
+
set.count = numContacts;
|
289
|
+
|
223
290
|
for(int i=0; i<set.count; i++){
|
224
291
|
set.points[i].point = contacts[i].p;
|
225
|
-
set.points[i].normal = contacts[i].
|
292
|
+
set.points[i].normal = contacts[i].n;
|
226
293
|
set.points[i].dist = contacts[i].dist;
|
227
294
|
}
|
228
295
|
|
@@ -234,13 +301,14 @@ shapeQueryHelper(cpShape *a, cpShape *b, shapeQueryContext *context)
|
|
234
301
|
cpBool
|
235
302
|
cpSpaceShapeQuery(cpSpace *space, cpShape *shape, cpSpaceShapeQueryFunc func, void *data)
|
236
303
|
{
|
237
|
-
|
238
|
-
|
304
|
+
cpBody *body = shape->body;
|
305
|
+
cpBB bb = (body ? cpShapeUpdate(shape, body->p, body->rot) : shape->bb);
|
306
|
+
struct ShapeQueryContext context = {func, data, cpFalse};
|
239
307
|
|
240
308
|
cpSpaceLock(space); {
|
241
|
-
|
242
|
-
|
243
|
-
} cpSpaceUnlock(space);
|
309
|
+
cpSpatialIndexQuery(space->activeShapes, shape, bb, (cpSpatialIndexQueryFunc)ShapeQuery, &context);
|
310
|
+
cpSpatialIndexQuery(space->staticShapes, shape, bb, (cpSpatialIndexQueryFunc)ShapeQuery, &context);
|
311
|
+
} cpSpaceUnlock(space, cpTrue);
|
244
312
|
|
245
313
|
return context.anyCollision;
|
246
314
|
}
|
data/ext/chipmunk/cpSpaceStep.c
CHANGED
@@ -19,58 +19,98 @@
|
|
19
19
|
* SOFTWARE.
|
20
20
|
*/
|
21
21
|
|
22
|
-
#include <stdlib.h>
|
23
|
-
//#include <stdio.h>
|
24
|
-
#include <math.h>
|
25
|
-
|
26
22
|
#include "chipmunk_private.h"
|
27
23
|
|
28
|
-
|
29
|
-
|
30
|
-
typedef struct PostStepCallback {
|
31
|
-
cpPostStepFunc func;
|
32
|
-
void *obj;
|
33
|
-
void *data;
|
34
|
-
} PostStepCallback;
|
24
|
+
//MARK: Post Step Callback Functions
|
35
25
|
|
36
|
-
|
37
|
-
|
38
|
-
|
26
|
+
cpPostStepCallback *
|
27
|
+
cpSpaceGetPostStepCallback(cpSpace *space, void *key)
|
28
|
+
{
|
29
|
+
cpArray *arr = space->postStepCallbacks;
|
30
|
+
for(int i=0; i<arr->num; i++){
|
31
|
+
cpPostStepCallback *callback = (cpPostStepCallback *)arr->arr[i];
|
32
|
+
if(callback && callback->key == key) return callback;
|
33
|
+
}
|
34
|
+
|
35
|
+
return NULL;
|
39
36
|
}
|
40
37
|
|
41
|
-
static void *
|
42
|
-
|
38
|
+
static void PostStepDoNothing(cpSpace *space, void *obj, void *data){}
|
39
|
+
|
40
|
+
cpBool
|
41
|
+
cpSpaceAddPostStepCallback(cpSpace *space, cpPostStepFunc func, void *key, void *data)
|
43
42
|
{
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
cpAssertWarn(space->locked,
|
44
|
+
"Adding a post-step callback when the space is not locked is unnecessary. "
|
45
|
+
"Post-step callbacks will not called until the end of the next call to cpSpaceStep() or the next query.");
|
46
|
+
|
47
|
+
if(!cpSpaceGetPostStepCallback(space, key)){
|
48
|
+
cpPostStepCallback *callback = (cpPostStepCallback *)cpcalloc(1, sizeof(cpPostStepCallback));
|
49
|
+
callback->func = (func ? func : PostStepDoNothing);
|
50
|
+
callback->key = key;
|
51
|
+
callback->data = data;
|
52
|
+
|
53
|
+
cpArrayPush(space->postStepCallbacks, callback);
|
54
|
+
return cpTrue;
|
55
|
+
} else {
|
56
|
+
return cpFalse;
|
57
|
+
}
|
48
58
|
}
|
49
59
|
|
60
|
+
//MARK: Locking Functions
|
61
|
+
|
50
62
|
void
|
51
|
-
|
63
|
+
cpSpaceLock(cpSpace *space)
|
52
64
|
{
|
53
|
-
|
54
|
-
space->postStepCallbacks = cpHashSetNew(0, (cpHashSetEqlFunc)postStepFuncSetEql, (cpHashSetTransFunc)postStepFuncSetTrans);
|
55
|
-
}
|
56
|
-
|
57
|
-
PostStepCallback callback = {func, obj, data};
|
58
|
-
cpHashSetInsert(space->postStepCallbacks, (cpHashValue)(size_t)obj, &callback, NULL);
|
65
|
+
space->locked++;
|
59
66
|
}
|
60
67
|
|
61
|
-
void
|
62
|
-
|
68
|
+
void
|
69
|
+
cpSpaceUnlock(cpSpace *space, cpBool runPostStep)
|
63
70
|
{
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
71
|
+
space->locked--;
|
72
|
+
cpAssertHard(space->locked >= 0, "Internal Error: Space lock underflow.");
|
73
|
+
|
74
|
+
if(space->locked == 0){
|
75
|
+
cpArray *waking = space->rousedBodies;
|
76
|
+
|
77
|
+
for(int i=0, count=waking->num; i<count; i++){
|
78
|
+
cpSpaceActivateBody(space, (cpBody *)waking->arr[i]);
|
79
|
+
waking->arr[i] = NULL;
|
80
|
+
}
|
81
|
+
|
82
|
+
waking->num = 0;
|
83
|
+
|
84
|
+
if(space->locked == 0 && runPostStep && !space->skipPostStep){
|
85
|
+
space->skipPostStep = cpTrue;
|
86
|
+
|
87
|
+
cpArray *arr = space->postStepCallbacks;
|
88
|
+
for(int i=0; i<arr->num; i++){
|
89
|
+
cpPostStepCallback *callback = (cpPostStepCallback *)arr->arr[i];
|
90
|
+
cpPostStepFunc func = callback->func;
|
91
|
+
|
92
|
+
// Mark the func as NULL in case calling it calls cpSpaceRunPostStepCallbacks() again.
|
93
|
+
// TODO need more tests around this case I think.
|
94
|
+
callback->func = NULL;
|
95
|
+
if(func) func(space, callback->key, callback->data);
|
96
|
+
|
97
|
+
arr->arr[i] = NULL;
|
98
|
+
cpfree(callback);
|
99
|
+
}
|
100
|
+
|
101
|
+
arr->num = 0;
|
102
|
+
space->skipPostStep = cpFalse;
|
103
|
+
}
|
70
104
|
}
|
71
105
|
}
|
72
106
|
|
73
|
-
|
107
|
+
//MARK: Contact Buffer Functions
|
108
|
+
|
109
|
+
struct cpContactBufferHeader {
|
110
|
+
cpTimestamp stamp;
|
111
|
+
cpContactBufferHeader *next;
|
112
|
+
unsigned int numContacts;
|
113
|
+
};
|
74
114
|
|
75
115
|
#define CP_CONTACTS_BUFFER_SIZE ((CP_BUFFER_BYTES - sizeof(cpContactBufferHeader))/sizeof(cpContact))
|
76
116
|
typedef struct cpContactBuffer {
|
@@ -81,7 +121,7 @@ typedef struct cpContactBuffer {
|
|
81
121
|
static cpContactBufferHeader *
|
82
122
|
cpSpaceAllocContactBuffer(cpSpace *space)
|
83
123
|
{
|
84
|
-
cpContactBuffer *buffer = (cpContactBuffer *)
|
124
|
+
cpContactBuffer *buffer = (cpContactBuffer *)cpcalloc(1, sizeof(cpContactBuffer));
|
85
125
|
cpArrayPush(space->allocatedBuffers, buffer);
|
86
126
|
return (cpContactBufferHeader *)buffer;
|
87
127
|
}
|
@@ -96,7 +136,7 @@ cpContactBufferHeaderInit(cpContactBufferHeader *header, cpTimestamp stamp, cpCo
|
|
96
136
|
return header;
|
97
137
|
}
|
98
138
|
|
99
|
-
|
139
|
+
void
|
100
140
|
cpSpacePushFreshContactBuffer(cpSpace *space)
|
101
141
|
{
|
102
142
|
cpTimestamp stamp = space->stamp;
|
@@ -106,7 +146,7 @@ cpSpacePushFreshContactBuffer(cpSpace *space)
|
|
106
146
|
if(!head){
|
107
147
|
// No buffers have been allocated, make one
|
108
148
|
space->contactBuffersHead = cpContactBufferHeaderInit(cpSpaceAllocContactBuffer(space), stamp, NULL);
|
109
|
-
} else if(stamp - head->next->stamp >
|
149
|
+
} else if(stamp - head->next->stamp > space->collisionPersistence){
|
110
150
|
// The tail buffer is available, rotate the ring
|
111
151
|
cpContactBufferHeader *tail = head->next;
|
112
152
|
space->contactBuffersHead = cpContactBufferHeaderInit(tail, stamp, tail);
|
@@ -118,7 +158,7 @@ cpSpacePushFreshContactBuffer(cpSpace *space)
|
|
118
158
|
}
|
119
159
|
|
120
160
|
|
121
|
-
|
161
|
+
cpContact *
|
122
162
|
cpContactBufferGetArray(cpSpace *space)
|
123
163
|
{
|
124
164
|
if(space->contactBuffersHead->numContacts + CP_MAX_CONTACTS_PER_ARBITER > CP_CONTACTS_BUFFER_SIZE){
|
@@ -130,47 +170,65 @@ cpContactBufferGetArray(cpSpace *space)
|
|
130
170
|
return ((cpContactBuffer *)head)->contacts + head->numContacts;
|
131
171
|
}
|
132
172
|
|
133
|
-
|
134
|
-
cpSpacePushContacts(cpSpace *space, int count)
|
135
|
-
|
173
|
+
void
|
174
|
+
cpSpacePushContacts(cpSpace *space, int count)
|
175
|
+
{
|
176
|
+
cpAssertHard(count <= CP_MAX_CONTACTS_PER_ARBITER, "Internal Error: Contact buffer overflow!");
|
136
177
|
space->contactBuffersHead->numContacts += count;
|
137
178
|
}
|
138
179
|
|
139
|
-
static
|
180
|
+
static void
|
140
181
|
cpSpacePopContacts(cpSpace *space, int count){
|
141
182
|
space->contactBuffersHead->numContacts -= count;
|
142
183
|
}
|
143
184
|
|
144
|
-
|
185
|
+
//MARK: Collision Detection Functions
|
186
|
+
|
187
|
+
static void *
|
188
|
+
cpSpaceArbiterSetTrans(cpShape **shapes, cpSpace *space)
|
189
|
+
{
|
190
|
+
if(space->pooledArbiters->num == 0){
|
191
|
+
// arbiter pool is exhausted, make more
|
192
|
+
int count = CP_BUFFER_BYTES/sizeof(cpArbiter);
|
193
|
+
cpAssertHard(count, "Internal Error: Buffer size too small.");
|
194
|
+
|
195
|
+
cpArbiter *buffer = (cpArbiter *)cpcalloc(1, CP_BUFFER_BYTES);
|
196
|
+
cpArrayPush(space->allocatedBuffers, buffer);
|
197
|
+
|
198
|
+
for(int i=0; i<count; i++) cpArrayPush(space->pooledArbiters, buffer + i);
|
199
|
+
}
|
200
|
+
|
201
|
+
return cpArbiterInit((cpArbiter *)cpArrayPop(space->pooledArbiters), shapes[0], shapes[1]);
|
202
|
+
}
|
145
203
|
|
146
204
|
static inline cpBool
|
147
205
|
queryReject(cpShape *a, cpShape *b)
|
148
206
|
{
|
149
|
-
return
|
207
|
+
return (
|
150
208
|
// BBoxes must overlap
|
151
|
-
!
|
209
|
+
!cpBBIntersects(a->bb, b->bb)
|
152
210
|
// Don't collide shapes attached to the same body.
|
153
211
|
|| a->body == b->body
|
154
212
|
// Don't collide objects in the same non-zero group
|
155
213
|
|| (a->group && a->group == b->group)
|
156
214
|
// Don't collide objects that don't share at least on layer.
|
157
|
-
|| !(a->layers & b->layers)
|
215
|
+
|| !(a->layers & b->layers)
|
216
|
+
// Don't collide infinite mass objects
|
217
|
+
|| (a->body->m == INFINITY && b->body->m == INFINITY)
|
218
|
+
);
|
158
219
|
}
|
159
220
|
|
160
221
|
// Callback from the spatial hash.
|
161
|
-
|
162
|
-
|
222
|
+
void
|
223
|
+
cpSpaceCollideShapes(cpShape *a, cpShape *b, cpSpace *space)
|
163
224
|
{
|
164
225
|
// Reject any of the simple cases
|
165
226
|
if(queryReject(a,b)) return;
|
166
227
|
|
167
|
-
|
168
|
-
struct{cpCollisionType a, b;} ids = {a->collision_type, b->collision_type};
|
169
|
-
cpHashValue collHashID = CP_HASH_PAIR(a->collision_type, b->collision_type);
|
170
|
-
cpCollisionHandler *handler = (cpCollisionHandler *)cpHashSetFind(space->collFuncSet, collHashID, &ids);
|
228
|
+
cpCollisionHandler *handler = cpSpaceLookupHandler(space, a->collision_type, b->collision_type);
|
171
229
|
|
172
230
|
cpBool sensor = a->sensor || b->sensor;
|
173
|
-
if(sensor && handler == &
|
231
|
+
if(sensor && handler == &cpDefaultCollisionHandler) return;
|
174
232
|
|
175
233
|
// Shape 'a' should have the lower shape type. (required by cpCollideShapes() )
|
176
234
|
if(a->klass->type > b->klass->type){
|
@@ -185,11 +243,11 @@ queryFunc(cpShape *a, cpShape *b, cpSpace *space)
|
|
185
243
|
if(!numContacts) return; // Shapes are not colliding.
|
186
244
|
cpSpacePushContacts(space, numContacts);
|
187
245
|
|
188
|
-
// Get an arbiter from space->
|
246
|
+
// Get an arbiter from space->arbiterSet for the two shapes.
|
189
247
|
// This is where the persistant contact magic comes from.
|
190
248
|
cpShape *shape_pair[] = {a, b};
|
191
|
-
cpHashValue arbHashID = CP_HASH_PAIR((
|
192
|
-
cpArbiter *arb = (cpArbiter *)cpHashSetInsert(space->
|
249
|
+
cpHashValue arbHashID = CP_HASH_PAIR((cpHashValue)a, (cpHashValue)b);
|
250
|
+
cpArbiter *arb = (cpArbiter *)cpHashSetInsert(space->cachedArbiters, arbHashID, shape_pair, space, (cpHashSetTransFunc)cpSpaceArbiterSetTrans);
|
193
251
|
cpArbiterUpdate(arb, contacts, numContacts, handler, a, b);
|
194
252
|
|
195
253
|
// Call the begin function first if it's the first step
|
@@ -212,8 +270,8 @@ queryFunc(cpShape *a, cpShape *b, cpSpace *space)
|
|
212
270
|
arb->contacts = NULL;
|
213
271
|
arb->numContacts = 0;
|
214
272
|
|
215
|
-
// Normally arbiters are set as used after calling the post-
|
216
|
-
// However, post-
|
273
|
+
// Normally arbiters are set as used after calling the post-solve callback.
|
274
|
+
// However, post-solve callbacks are not called for sensors or arbiters rejected from pre-solve.
|
217
275
|
if(arb->state != cpArbiterStateIgnore) arb->state = cpArbiterStateNormal;
|
218
276
|
}
|
219
277
|
|
@@ -221,45 +279,31 @@ queryFunc(cpShape *a, cpShape *b, cpSpace *space)
|
|
221
279
|
arb->stamp = space->stamp;
|
222
280
|
}
|
223
281
|
|
224
|
-
// Iterator for active/static hash collisions.
|
225
|
-
static void
|
226
|
-
active2staticIter(cpShape *shape, cpSpace *space)
|
227
|
-
{
|
228
|
-
cpSpaceHashQuery(space->staticShapes, shape, shape->bb, (cpSpaceHashQueryFunc)queryFunc, space);
|
229
|
-
}
|
230
|
-
|
231
282
|
// Hashset filter func to throw away old arbiters.
|
232
|
-
|
233
|
-
|
283
|
+
cpBool
|
284
|
+
cpSpaceArbiterSetFilter(cpArbiter *arb, cpSpace *space)
|
234
285
|
{
|
235
|
-
if(space->sleepTimeThreshold != INFINITY){
|
236
|
-
cpBody *a = arb->a->body;
|
237
|
-
cpBody *b = arb->b->body;
|
238
|
-
|
239
|
-
// both bodies are either static or sleeping
|
240
|
-
cpBool sleepingNow =
|
241
|
-
(cpBodyIsStatic(a) || cpBodyIsSleeping(a)) &&
|
242
|
-
(cpBodyIsStatic(b) || cpBodyIsSleeping(b));
|
243
|
-
|
244
|
-
if(sleepingNow){
|
245
|
-
arb->state = cpArbiterStateSleep;
|
246
|
-
return cpTrue;
|
247
|
-
} else if(arb->state == cpArbiterStateSleep){
|
248
|
-
// wake up the arbiter and continue as normal
|
249
|
-
arb->state = cpArbiterStateNormal;
|
250
|
-
// TODO is it possible that cpArbiterStateIgnore should be set here instead?
|
251
|
-
}
|
252
|
-
}
|
253
|
-
|
254
286
|
cpTimestamp ticks = space->stamp - arb->stamp;
|
255
287
|
|
256
|
-
|
288
|
+
cpBody *a = arb->body_a, *b = arb->body_b;
|
289
|
+
|
290
|
+
// TODO should make an arbiter state for this so it doesn't require filtering arbiters for dangling body pointers on body removal.
|
291
|
+
// Preserve arbiters on sensors and rejected arbiters for sleeping objects.
|
292
|
+
// This prevents errant separate callbacks from happenening.
|
293
|
+
if(
|
294
|
+
(cpBodyIsStatic(a) || cpBodyIsSleeping(a)) &&
|
295
|
+
(cpBodyIsStatic(b) || cpBodyIsSleeping(b))
|
296
|
+
){
|
297
|
+
return cpTrue;
|
298
|
+
}
|
299
|
+
|
300
|
+
// Arbiter was used last frame, but not this one
|
257
301
|
if(ticks >= 1 && arb->state != cpArbiterStateCached){
|
258
|
-
arb->handler->separate(arb, space, arb->handler->data);
|
259
302
|
arb->state = cpArbiterStateCached;
|
303
|
+
cpArbiterCallSeparate(arb, space);
|
260
304
|
}
|
261
305
|
|
262
|
-
if(ticks >=
|
306
|
+
if(ticks >= space->collisionPersistence){
|
263
307
|
arb->contacts = NULL;
|
264
308
|
arb->numContacts = 0;
|
265
309
|
|
@@ -270,129 +314,123 @@ contactSetFilter(cpArbiter *arb, cpSpace *space)
|
|
270
314
|
return cpTrue;
|
271
315
|
}
|
272
316
|
|
273
|
-
//
|
274
|
-
|
275
|
-
|
317
|
+
//MARK: All Important cpSpaceStep() Function
|
318
|
+
|
319
|
+
void
|
320
|
+
cpShapeUpdateFunc(cpShape *shape, void *unused)
|
276
321
|
{
|
277
|
-
|
278
|
-
|
322
|
+
cpBody *body = shape->body;
|
323
|
+
cpShapeUpdate(shape, body->p, body->rot);
|
279
324
|
}
|
280
325
|
|
281
|
-
#pragma mark All Important cpSpaceStep() Function
|
282
|
-
|
283
|
-
void cpSpaceProcessComponents(cpSpace *space, cpFloat dt);
|
284
|
-
|
285
|
-
static void updateBBCache(cpShape *shape, void *unused){cpShapeCacheBB(shape);}
|
286
|
-
|
287
326
|
void
|
288
327
|
cpSpaceStep(cpSpace *space, cpFloat dt)
|
289
328
|
{
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
cpArray *bodies = space->bodies;
|
294
|
-
cpArray *constraints = space->constraints;
|
295
|
-
|
296
|
-
// Empty the arbiter list.
|
297
|
-
space->arbiters->num = 0;
|
298
|
-
|
299
|
-
// Integrate positions.
|
300
|
-
for(int i=0; i<bodies->num; i++){
|
301
|
-
cpBody *body = (cpBody *)bodies->arr[i];
|
302
|
-
body->position_func(body, dt);
|
303
|
-
}
|
304
|
-
|
305
|
-
// Pre-cache BBoxes and shape data.
|
306
|
-
cpSpaceHashEach(space->activeShapes, (cpSpaceHashIterator)updateBBCache, NULL);
|
307
|
-
|
308
|
-
cpSpaceLock(space);
|
309
|
-
|
310
|
-
// Collide!
|
311
|
-
cpSpacePushFreshContactBuffer(space);
|
312
|
-
if(space->staticShapes->handleSet->entries)
|
313
|
-
cpSpaceHashEach(space->activeShapes, (cpSpaceHashIterator)active2staticIter, space);
|
314
|
-
cpSpaceHashQueryRehash(space->activeShapes, (cpSpaceHashQueryFunc)queryFunc, space);
|
315
|
-
|
316
|
-
cpSpaceUnlock(space);
|
329
|
+
// don't step if the timestep is 0!
|
330
|
+
if(dt == 0.0f) return;
|
317
331
|
|
318
|
-
|
319
|
-
if(space->sleepTimeThreshold != INFINITY){
|
320
|
-
cpSpaceProcessComponents(space, dt);
|
321
|
-
bodies = space->bodies; // rebuilt by processContactComponents()
|
322
|
-
}
|
332
|
+
space->stamp++;
|
323
333
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
334
|
+
cpFloat prev_dt = space->curr_dt;
|
335
|
+
space->curr_dt = dt;
|
336
|
+
|
337
|
+
cpArray *bodies = space->bodies;
|
338
|
+
cpArray *constraints = space->constraints;
|
328
339
|
cpArray *arbiters = space->arbiters;
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
for(int i=0; i<space->elasticIterations; i++){
|
339
|
-
for(int j=0; j<arbiters->num; j++)
|
340
|
-
cpArbiterApplyImpulse((cpArbiter *)arbiters->arr[j], 1.0f);
|
341
|
-
|
342
|
-
for(int j=0; j<constraints->num; j++){
|
343
|
-
cpConstraint *constraint = (cpConstraint *)constraints->arr[j];
|
344
|
-
constraint->klass->applyImpulse(constraint);
|
340
|
+
|
341
|
+
// Reset and empty the arbiter lists.
|
342
|
+
for(int i=0; i<arbiters->num; i++){
|
343
|
+
cpArbiter *arb = (cpArbiter *)arbiters->arr[i];
|
344
|
+
arb->state = cpArbiterStateNormal;
|
345
|
+
|
346
|
+
// If both bodies are awake, unthread the arbiter from the contact graph.
|
347
|
+
if(!cpBodyIsSleeping(arb->body_a) && !cpBodyIsSleeping(arb->body_b)){
|
348
|
+
cpArbiterUnthread(arb);
|
345
349
|
}
|
346
350
|
}
|
351
|
+
arbiters->num = 0;
|
347
352
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
353
|
+
cpSpaceLock(space); {
|
354
|
+
// Integrate positions
|
355
|
+
for(int i=0; i<bodies->num; i++){
|
356
|
+
cpBody *body = (cpBody *)bodies->arr[i];
|
357
|
+
body->position_func(body, dt);
|
358
|
+
}
|
359
|
+
|
360
|
+
// Find colliding pairs.
|
361
|
+
cpSpacePushFreshContactBuffer(space);
|
362
|
+
cpSpatialIndexEach(space->activeShapes, (cpSpatialIndexIteratorFunc)cpShapeUpdateFunc, NULL);
|
363
|
+
cpSpatialIndexReindexQuery(space->activeShapes, (cpSpatialIndexQueryFunc)cpSpaceCollideShapes, space);
|
364
|
+
} cpSpaceUnlock(space, cpFalse);
|
365
|
+
|
366
|
+
// Rebuild the contact graph (and detect sleeping components if sleeping is enabled)
|
367
|
+
cpSpaceProcessComponents(space, dt);
|
368
|
+
|
369
|
+
cpSpaceLock(space); {
|
370
|
+
// Clear out old cached arbiters and call separate callbacks
|
371
|
+
cpHashSetFilter(space->cachedArbiters, (cpHashSetFilterFunc)cpSpaceArbiterSetFilter, space);
|
372
|
+
|
373
|
+
// Prestep the arbiters and constraints.
|
374
|
+
cpFloat slop = space->collisionSlop;
|
375
|
+
cpFloat biasCoef = 1.0f - cpfpow(space->collisionBias, dt);
|
376
|
+
for(int i=0; i<arbiters->num; i++){
|
377
|
+
cpArbiterPreStep((cpArbiter *)arbiters->arr[i], dt, slop, biasCoef);
|
378
|
+
}
|
354
379
|
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
// run the old-style elastic solver if elastic iterations are disabled
|
359
|
-
cpFloat elasticCoef = (space->elasticIterations ? 0.0f : 1.0f);
|
360
|
-
|
361
|
-
// Run the impulse solver.
|
362
|
-
for(int i=0; i<space->iterations; i++){
|
363
|
-
for(int j=0; j<arbiters->num; j++)
|
364
|
-
cpArbiterApplyImpulse((cpArbiter *)arbiters->arr[j], elasticCoef);
|
380
|
+
for(int i=0; i<constraints->num; i++){
|
381
|
+
cpConstraint *constraint = (cpConstraint *)constraints->arr[i];
|
365
382
|
|
366
|
-
|
367
|
-
|
368
|
-
|
383
|
+
cpConstraintPreSolveFunc preSolve = constraint->preSolve;
|
384
|
+
if(preSolve) preSolve(constraint, space);
|
385
|
+
|
386
|
+
constraint->klass->preStep(constraint, dt);
|
369
387
|
}
|
370
|
-
}
|
371
388
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
389
|
+
// Integrate velocities.
|
390
|
+
cpFloat damping = cpfpow(space->damping, dt);
|
391
|
+
cpVect gravity = space->gravity;
|
392
|
+
for(int i=0; i<bodies->num; i++){
|
393
|
+
cpBody *body = (cpBody *)bodies->arr[i];
|
394
|
+
body->velocity_func(body, gravity, damping, dt);
|
395
|
+
}
|
377
396
|
|
378
|
-
|
379
|
-
|
397
|
+
// Apply cached impulses
|
398
|
+
cpFloat dt_coef = (prev_dt == 0.0f ? 0.0f : dt/prev_dt);
|
399
|
+
for(int i=0; i<arbiters->num; i++){
|
400
|
+
cpArbiterApplyCachedImpulse((cpArbiter *)arbiters->arr[i], dt_coef);
|
401
|
+
}
|
380
402
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
// Run the post step callbacks
|
387
|
-
// Loop because post step callbacks may create more post step callbacks
|
388
|
-
while(space->postStepCallbacks){
|
389
|
-
cpHashSet *callbacks = space->postStepCallbacks;
|
390
|
-
space->postStepCallbacks = NULL;
|
403
|
+
for(int i=0; i<constraints->num; i++){
|
404
|
+
cpConstraint *constraint = (cpConstraint *)constraints->arr[i];
|
405
|
+
constraint->klass->applyCachedImpulse(constraint, dt_coef);
|
406
|
+
}
|
391
407
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
408
|
+
// Run the impulse solver.
|
409
|
+
for(int i=0; i<space->iterations; i++){
|
410
|
+
for(int j=0; j<arbiters->num; j++){
|
411
|
+
cpArbiterApplyImpulse((cpArbiter *)arbiters->arr[j]);
|
412
|
+
}
|
413
|
+
|
414
|
+
for(int j=0; j<constraints->num; j++){
|
415
|
+
cpConstraint *constraint = (cpConstraint *)constraints->arr[j];
|
416
|
+
constraint->klass->applyImpulse(constraint, dt);
|
417
|
+
}
|
418
|
+
}
|
419
|
+
|
420
|
+
// Run the constraint post-solve callbacks
|
421
|
+
for(int i=0; i<constraints->num; i++){
|
422
|
+
cpConstraint *constraint = (cpConstraint *)constraints->arr[i];
|
423
|
+
|
424
|
+
cpConstraintPostSolveFunc postSolve = constraint->postSolve;
|
425
|
+
if(postSolve) postSolve(constraint, space);
|
426
|
+
}
|
427
|
+
|
428
|
+
// run the post-solve callbacks
|
429
|
+
for(int i=0; i<arbiters->num; i++){
|
430
|
+
cpArbiter *arb = (cpArbiter *) arbiters->arr[i];
|
431
|
+
|
432
|
+
cpCollisionHandler *handler = arb->handler;
|
433
|
+
handler->postSolve(arb, space, handler->data);
|
434
|
+
}
|
435
|
+
} cpSpaceUnlock(space, cpTrue);
|
398
436
|
}
|