chipmunk 5.3.4.5 → 6.1.3.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
}
|