-
Notifications
You must be signed in to change notification settings - Fork 266
/
collide_fine.cpp
703 lines (589 loc) · 21.5 KB
/
collide_fine.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
/*
* Implementation file for the fine grained collision detector.
*
* Part of the Cyclone physics system.
*
* Copyright (c) Icosagon 2003. All Rights Reserved.
*
* This software is distributed under licence. Use of this software
* implies agreement with all terms and conditions of the accompanying
* software licence.
*/
#include <cyclone/collide_fine.h>
#include <memory.h>
#include <assert.h>
#include <cstdlib>
#include <cstdio>
using namespace cyclone;
void CollisionPrimitive::calculateInternals()
{
transform = body->getTransform() * offset;
}
bool IntersectionTests::sphereAndHalfSpace(
const CollisionSphere &sphere,
const CollisionPlane &plane)
{
// Find the distance from the origin
real ballDistance =
plane.direction *
sphere.getAxis(3) -
sphere.radius;
// Check for the intersection
return ballDistance <= plane.offset;
}
bool IntersectionTests::sphereAndSphere(
const CollisionSphere &one,
const CollisionSphere &two)
{
// Find the vector between the objects
Vector3 midline = one.getAxis(3) - two.getAxis(3);
// See if it is large enough.
return midline.squareMagnitude() <
(one.radius+two.radius)*(one.radius+two.radius);
}
static inline real transformToAxis(
const CollisionBox &box,
const Vector3 &axis
)
{
return
box.halfSize.x * real_abs(axis * box.getAxis(0)) +
box.halfSize.y * real_abs(axis * box.getAxis(1)) +
box.halfSize.z * real_abs(axis * box.getAxis(2));
}
/**
* This function checks if the two boxes overlap
* along the given axis. The final parameter toCentre
* is used to pass in the vector between the boxes centre
* points, to avoid having to recalculate it each time.
*/
static inline bool overlapOnAxis(
const CollisionBox &one,
const CollisionBox &two,
const Vector3 &axis,
const Vector3 &toCentre
)
{
// Project the half-size of one onto axis
real oneProject = transformToAxis(one, axis);
real twoProject = transformToAxis(two, axis);
// Project this onto the axis
real distance = real_abs(toCentre * axis);
// Check for overlap
return (distance < oneProject + twoProject);
}
// This preprocessor definition is only used as a convenience
// in the boxAndBox intersection method.
#define TEST_OVERLAP(axis) overlapOnAxis(one, two, (axis), toCentre)
bool IntersectionTests::boxAndBox(
const CollisionBox &one,
const CollisionBox &two
)
{
// Find the vector between the two centres
Vector3 toCentre = two.getAxis(3) - one.getAxis(3);
return (
// Check on box one's axes first
TEST_OVERLAP(one.getAxis(0)) &&
TEST_OVERLAP(one.getAxis(1)) &&
TEST_OVERLAP(one.getAxis(2)) &&
// And on two's
TEST_OVERLAP(two.getAxis(0)) &&
TEST_OVERLAP(two.getAxis(1)) &&
TEST_OVERLAP(two.getAxis(2)) &&
// Now on the cross products
TEST_OVERLAP(one.getAxis(0) % two.getAxis(0)) &&
TEST_OVERLAP(one.getAxis(0) % two.getAxis(1)) &&
TEST_OVERLAP(one.getAxis(0) % two.getAxis(2)) &&
TEST_OVERLAP(one.getAxis(1) % two.getAxis(0)) &&
TEST_OVERLAP(one.getAxis(1) % two.getAxis(1)) &&
TEST_OVERLAP(one.getAxis(1) % two.getAxis(2)) &&
TEST_OVERLAP(one.getAxis(2) % two.getAxis(0)) &&
TEST_OVERLAP(one.getAxis(2) % two.getAxis(1)) &&
TEST_OVERLAP(one.getAxis(2) % two.getAxis(2))
);
}
#undef TEST_OVERLAP
bool IntersectionTests::boxAndHalfSpace(
const CollisionBox &box,
const CollisionPlane &plane
)
{
// Work out the projected radius of the box onto the plane direction
real projectedRadius = transformToAxis(box, plane.direction);
// Work out how far the box is from the origin
real boxDistance =
plane.direction *
box.getAxis(3) -
projectedRadius;
// Check for the intersection
return boxDistance <= plane.offset;
}
unsigned CollisionDetector::sphereAndTruePlane(
const CollisionSphere &sphere,
const CollisionPlane &plane,
CollisionData *data
)
{
// Make sure we have contacts
if (data->contactsLeft <= 0) return 0;
// Cache the sphere position
Vector3 position = sphere.getAxis(3);
// Find the distance from the plane
real centreDistance = plane.direction * position - plane.offset;
// Check if we're within radius
if (centreDistance*centreDistance > sphere.radius*sphere.radius)
{
return 0;
}
// Check which side of the plane we're on
Vector3 normal = plane.direction;
real penetration = -centreDistance;
if (centreDistance < 0)
{
normal *= -1;
penetration = -penetration;
}
penetration += sphere.radius;
// Create the contact - it has a normal in the plane direction.
Contact* contact = data->contacts;
contact->contactNormal = normal;
contact->penetration = penetration;
contact->contactPoint = position - plane.direction * centreDistance;
contact->setBodyData(sphere.body, NULL,
data->friction, data->restitution);
data->addContacts(1);
return 1;
}
unsigned CollisionDetector::sphereAndHalfSpace(
const CollisionSphere &sphere,
const CollisionPlane &plane,
CollisionData *data
)
{
// Make sure we have contacts
if (data->contactsLeft <= 0) return 0;
// Cache the sphere position
Vector3 position = sphere.getAxis(3);
// Find the distance from the plane
real ballDistance =
plane.direction * position -
sphere.radius - plane.offset;
if (ballDistance >= 0) return 0;
// Create the contact - it has a normal in the plane direction.
Contact* contact = data->contacts;
contact->contactNormal = plane.direction;
contact->penetration = -ballDistance;
contact->contactPoint =
position - plane.direction * (ballDistance + sphere.radius);
contact->setBodyData(sphere.body, NULL,
data->friction, data->restitution);
data->addContacts(1);
return 1;
}
unsigned CollisionDetector::sphereAndSphere(
const CollisionSphere &one,
const CollisionSphere &two,
CollisionData *data
)
{
// Make sure we have contacts
if (data->contactsLeft <= 0) return 0;
// Cache the sphere positions
Vector3 positionOne = one.getAxis(3);
Vector3 positionTwo = two.getAxis(3);
// Find the vector between the objects
Vector3 midline = positionOne - positionTwo;
real size = midline.magnitude();
// See if it is large enough.
if (size <= 0.0f || size >= one.radius+two.radius)
{
return 0;
}
// We manually create the normal, because we have the
// size to hand.
Vector3 normal = midline * (((real)1.0)/size);
Contact* contact = data->contacts;
contact->contactNormal = normal;
contact->contactPoint = positionOne + midline * (real)0.5;
contact->penetration = (one.radius+two.radius - size);
contact->setBodyData(one.body, two.body,
data->friction, data->restitution);
data->addContacts(1);
return 1;
}
/*
* This function checks if the two boxes overlap
* along the given axis, returning the ammount of overlap.
* The final parameter toCentre
* is used to pass in the vector between the boxes centre
* points, to avoid having to recalculate it each time.
*/
static inline real penetrationOnAxis(
const CollisionBox &one,
const CollisionBox &two,
const Vector3 &axis,
const Vector3 &toCentre
)
{
// Project the half-size of one onto axis
real oneProject = transformToAxis(one, axis);
real twoProject = transformToAxis(two, axis);
// Project this onto the axis
real distance = real_abs(toCentre * axis);
// Return the overlap (i.e. positive indicates
// overlap, negative indicates separation).
return oneProject + twoProject - distance;
}
static inline bool tryAxis(
const CollisionBox &one,
const CollisionBox &two,
Vector3 axis,
const Vector3& toCentre,
unsigned index,
// These values may be updated
real& smallestPenetration,
unsigned &smallestCase
)
{
// Make sure we have a normalized axis, and don't check almost parallel axes
if (axis.squareMagnitude() < 0.0001) return true;
axis.normalise();
real penetration = penetrationOnAxis(one, two, axis, toCentre);
if (penetration < 0) return false;
if (penetration < smallestPenetration) {
smallestPenetration = penetration;
smallestCase = index;
}
return true;
}
void fillPointFaceBoxBox(
const CollisionBox &one,
const CollisionBox &two,
const Vector3 &toCentre,
CollisionData *data,
unsigned best,
real pen
)
{
// This method is called when we know that a vertex from
// box two is in contact with box one.
Contact* contact = data->contacts;
// We know which axis the collision is on (i.e. best),
// but we need to work out which of the two faces on
// this axis.
Vector3 normal = one.getAxis(best);
if (one.getAxis(best) * toCentre > 0)
{
normal = normal * -1.0f;
}
// Work out which vertex of box two we're colliding with.
// Using toCentre doesn't work!
Vector3 vertex = two.halfSize;
if (two.getAxis(0) * normal < 0) vertex.x = -vertex.x;
if (two.getAxis(1) * normal < 0) vertex.y = -vertex.y;
if (two.getAxis(2) * normal < 0) vertex.z = -vertex.z;
// Create the contact data
contact->contactNormal = normal;
contact->penetration = pen;
contact->contactPoint = two.getTransform() * vertex;
contact->setBodyData(one.body, two.body,
data->friction, data->restitution);
}
static inline Vector3 contactPoint(
const Vector3 &pOne,
const Vector3 &dOne,
real oneSize,
const Vector3 &pTwo,
const Vector3 &dTwo,
real twoSize,
// If this is true, and the contact point is outside
// the edge (in the case of an edge-face contact) then
// we use one's midpoint, otherwise we use two's.
bool useOne)
{
Vector3 toSt, cOne, cTwo;
real dpStaOne, dpStaTwo, dpOneTwo, smOne, smTwo;
real denom, mua, mub;
smOne = dOne.squareMagnitude();
smTwo = dTwo.squareMagnitude();
dpOneTwo = dTwo * dOne;
toSt = pOne - pTwo;
dpStaOne = dOne * toSt;
dpStaTwo = dTwo * toSt;
denom = smOne * smTwo - dpOneTwo * dpOneTwo;
// Zero denominator indicates parrallel lines
if (real_abs(denom) < 0.0001f) {
return useOne?pOne:pTwo;
}
mua = (dpOneTwo * dpStaTwo - smTwo * dpStaOne) / denom;
mub = (smOne * dpStaTwo - dpOneTwo * dpStaOne) / denom;
// If either of the edges has the nearest point out
// of bounds, then the edges aren't crossed, we have
// an edge-face contact. Our point is on the edge, which
// we know from the useOne parameter.
if (mua > oneSize ||
mua < -oneSize ||
mub > twoSize ||
mub < -twoSize)
{
return useOne?pOne:pTwo;
}
else
{
cOne = pOne + dOne * mua;
cTwo = pTwo + dTwo * mub;
return cOne * 0.5 + cTwo * 0.5;
}
}
// This preprocessor definition is only used as a convenience
// in the boxAndBox contact generation method.
#define CHECK_OVERLAP(axis, index) \
if (!tryAxis(one, two, (axis), toCentre, (index), pen, best)) return 0;
unsigned CollisionDetector::boxAndBox(
const CollisionBox &one,
const CollisionBox &two,
CollisionData *data
)
{
//if (!IntersectionTests::boxAndBox(one, two)) return 0;
// Find the vector between the two centres
Vector3 toCentre = two.getAxis(3) - one.getAxis(3);
// We start assuming there is no contact
real pen = REAL_MAX;
unsigned best = 0xffffff;
// Now we check each axes, returning if it gives us
// a separating axis, and keeping track of the axis with
// the smallest penetration otherwise.
CHECK_OVERLAP(one.getAxis(0), 0);
CHECK_OVERLAP(one.getAxis(1), 1);
CHECK_OVERLAP(one.getAxis(2), 2);
CHECK_OVERLAP(two.getAxis(0), 3);
CHECK_OVERLAP(two.getAxis(1), 4);
CHECK_OVERLAP(two.getAxis(2), 5);
// Store the best axis-major, in case we run into almost
// parallel edge collisions later
unsigned bestSingleAxis = best;
CHECK_OVERLAP(one.getAxis(0) % two.getAxis(0), 6);
CHECK_OVERLAP(one.getAxis(0) % two.getAxis(1), 7);
CHECK_OVERLAP(one.getAxis(0) % two.getAxis(2), 8);
CHECK_OVERLAP(one.getAxis(1) % two.getAxis(0), 9);
CHECK_OVERLAP(one.getAxis(1) % two.getAxis(1), 10);
CHECK_OVERLAP(one.getAxis(1) % two.getAxis(2), 11);
CHECK_OVERLAP(one.getAxis(2) % two.getAxis(0), 12);
CHECK_OVERLAP(one.getAxis(2) % two.getAxis(1), 13);
CHECK_OVERLAP(one.getAxis(2) % two.getAxis(2), 14);
// Make sure we've got a result.
assert(best != 0xffffff);
// We now know there's a collision, and we know which
// of the axes gave the smallest penetration. We now
// can deal with it in different ways depending on
// the case.
if (best < 3)
{
// We've got a vertex of box two on a face of box one.
fillPointFaceBoxBox(one, two, toCentre, data, best, pen);
data->addContacts(1);
return 1;
}
else if (best < 6)
{
// We've got a vertex of box one on a face of box two.
// We use the same algorithm as above, but swap around
// one and two (and therefore also the vector between their
// centres).
fillPointFaceBoxBox(two, one, toCentre*-1.0f, data, best-3, pen);
data->addContacts(1);
return 1;
}
else
{
// We've got an edge-edge contact. Find out which axes
best -= 6;
unsigned oneAxisIndex = best / 3;
unsigned twoAxisIndex = best % 3;
Vector3 oneAxis = one.getAxis(oneAxisIndex);
Vector3 twoAxis = two.getAxis(twoAxisIndex);
Vector3 axis = oneAxis % twoAxis;
axis.normalise();
// The axis should point from box one to box two.
if (axis * toCentre > 0) axis = axis * -1.0f;
// We have the axes, but not the edges: each axis has 4 edges parallel
// to it, we need to find which of the 4 for each object. We do
// that by finding the point in the centre of the edge. We know
// its component in the direction of the box's collision axis is zero
// (its a mid-point) and we determine which of the extremes in each
// of the other axes is closest.
Vector3 ptOnOneEdge = one.halfSize;
Vector3 ptOnTwoEdge = two.halfSize;
for (unsigned i = 0; i < 3; i++)
{
if (i == oneAxisIndex) ptOnOneEdge[i] = 0;
else if (one.getAxis(i) * axis > 0) ptOnOneEdge[i] = -ptOnOneEdge[i];
if (i == twoAxisIndex) ptOnTwoEdge[i] = 0;
else if (two.getAxis(i) * axis < 0) ptOnTwoEdge[i] = -ptOnTwoEdge[i];
}
// Move them into world coordinates (they are already oriented
// correctly, since they have been derived from the axes).
ptOnOneEdge = one.transform * ptOnOneEdge;
ptOnTwoEdge = two.transform * ptOnTwoEdge;
// So we have a point and a direction for the colliding edges.
// We need to find out point of closest approach of the two
// line-segments.
Vector3 vertex = contactPoint(
ptOnOneEdge, oneAxis, one.halfSize[oneAxisIndex],
ptOnTwoEdge, twoAxis, two.halfSize[twoAxisIndex],
bestSingleAxis > 2
);
// We can fill the contact.
Contact* contact = data->contacts;
contact->penetration = pen;
contact->contactNormal = axis;
contact->contactPoint = vertex;
contact->setBodyData(one.body, two.body,
data->friction, data->restitution);
data->addContacts(1);
return 1;
}
return 0;
}
#undef CHECK_OVERLAP
unsigned CollisionDetector::boxAndPoint(
const CollisionBox &box,
const Vector3 &point,
CollisionData *data
)
{
// Transform the point into box coordinates
Vector3 relPt = box.transform.transformInverse(point);
Vector3 normal;
// Check each axis, looking for the axis on which the
// penetration is least deep.
real min_depth = box.halfSize.x - real_abs(relPt.x);
if (min_depth < 0) return 0;
normal = box.getAxis(0) * ((relPt.x < 0)?-1:1);
real depth = box.halfSize.y - real_abs(relPt.y);
if (depth < 0) return 0;
else if (depth < min_depth)
{
min_depth = depth;
normal = box.getAxis(1) * ((relPt.y < 0)?-1:1);
}
depth = box.halfSize.z - real_abs(relPt.z);
if (depth < 0) return 0;
else if (depth < min_depth)
{
min_depth = depth;
normal = box.getAxis(2) * ((relPt.z < 0)?-1:1);
}
// Compile the contact
Contact* contact = data->contacts;
contact->contactNormal = normal;
contact->contactPoint = point;
contact->penetration = min_depth;
// Note that we don't know what rigid body the point
// belongs to, so we just use NULL. Where this is called
// this value can be left, or filled in.
contact->setBodyData(box.body, NULL,
data->friction, data->restitution);
data->addContacts(1);
return 1;
}
unsigned CollisionDetector::boxAndSphere(
const CollisionBox &box,
const CollisionSphere &sphere,
CollisionData *data
)
{
// Transform the centre of the sphere into box coordinates
Vector3 centre = sphere.getAxis(3);
Vector3 relCentre = box.transform.transformInverse(centre);
// Early out check to see if we can exclude the contact
if (real_abs(relCentre.x) - sphere.radius > box.halfSize.x ||
real_abs(relCentre.y) - sphere.radius > box.halfSize.y ||
real_abs(relCentre.z) - sphere.radius > box.halfSize.z)
{
return 0;
}
Vector3 closestPt(0,0,0);
real dist;
// Clamp each coordinate to the box.
dist = relCentre.x;
if (dist > box.halfSize.x) dist = box.halfSize.x;
if (dist < -box.halfSize.x) dist = -box.halfSize.x;
closestPt.x = dist;
dist = relCentre.y;
if (dist > box.halfSize.y) dist = box.halfSize.y;
if (dist < -box.halfSize.y) dist = -box.halfSize.y;
closestPt.y = dist;
dist = relCentre.z;
if (dist > box.halfSize.z) dist = box.halfSize.z;
if (dist < -box.halfSize.z) dist = -box.halfSize.z;
closestPt.z = dist;
// Check we're in contact
dist = (closestPt - relCentre).squareMagnitude();
if (dist > sphere.radius * sphere.radius) return 0;
// Compile the contact
Vector3 closestPtWorld = box.transform.transform(closestPt);
Contact* contact = data->contacts;
contact->contactNormal = (closestPtWorld - centre);
contact->contactNormal.normalise();
contact->contactPoint = closestPtWorld;
contact->penetration = sphere.radius - real_sqrt(dist);
contact->setBodyData(box.body, sphere.body,
data->friction, data->restitution);
data->addContacts(1);
return 1;
}
unsigned CollisionDetector::boxAndHalfSpace(
const CollisionBox &box,
const CollisionPlane &plane,
CollisionData *data
)
{
// Make sure we have contacts
if (data->contactsLeft <= 0) return 0;
// Check for intersection
if (!IntersectionTests::boxAndHalfSpace(box, plane))
{
return 0;
}
// We have an intersection, so find the intersection points. We can make
// do with only checking vertices. If the box is resting on a plane
// or on an edge, it will be reported as four or two contact points.
// Go through each combination of + and - for each half-size
static real mults[8][3] = {{1,1,1},{-1,1,1},{1,-1,1},{-1,-1,1},
{1,1,-1},{-1,1,-1},{1,-1,-1},{-1,-1,-1}};
Contact* contact = data->contacts;
unsigned contactsUsed = 0;
for (unsigned i = 0; i < 8; i++) {
// Calculate the position of each vertex
Vector3 vertexPos(mults[i][0], mults[i][1], mults[i][2]);
vertexPos.componentProductUpdate(box.halfSize);
vertexPos = box.transform.transform(vertexPos);
// Calculate the distance from the plane
real vertexDistance = vertexPos * plane.direction;
// Compare this to the plane's distance
if (vertexDistance <= plane.offset)
{
// Create the contact data.
// The contact point is halfway between the vertex and the
// plane - we multiply the direction by half the separation
// distance and add the vertex location.
contact->contactPoint = plane.direction;
contact->contactPoint *= (vertexDistance-plane.offset);
contact->contactPoint += vertexPos;
contact->contactNormal = plane.direction;
contact->penetration = plane.offset - vertexDistance;
// Write the appropriate data
contact->setBodyData(box.body, NULL,
data->friction, data->restitution);
// Move onto the next contact
contact++;
contactsUsed++;
if (contactsUsed == (unsigned)data->contactsLeft) return contactsUsed;
}
}
data->addContacts(contactsUsed);
return contactsUsed;
}