2 Physics Effects Copyright(C) 2010 Sony Computer Entertainment Inc.
\r
5 Physics Effects is open software; you can redistribute it and/or
\r
6 modify it under the terms of the BSD License.
\r
8 Physics Effects is distributed in the hope that it will be useful,
\r
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
\r
11 See the BSD License for more details.
\r
13 A copy of the BSD License is distributed with
\r
14 Physics Effects under the filename: physics_effects_license.txt
\r
17 #include "../../../include/physics_effects/base_level/collision/pfx_box.h"
\r
18 #include "../../../include/physics_effects/base_level/collision/pfx_capsule.h"
\r
19 #include "pfx_contact_box_capsule.h"
\r
22 namespace PhysicsEffects {
\r
24 enum BoxCapsSepAxisType
\r
26 BOX_AXIS, CROSS_AXIS
\r
29 //-------------------------------------------------------------------------------------------------
\r
30 // voronoiTol: bevels Voronoi planes slightly which helps when features are parallel.
\r
31 //-------------------------------------------------------------------------------------------------
\r
33 static const PfxFloat voronoiTol = -1.0e-5f;
\r
35 //-------------------------------------------------------------------------------------------------
\r
36 // lenSqrTol: minimum square of length for safe normalize.
\r
37 //-------------------------------------------------------------------------------------------------
\r
39 static const PfxFloat lenSqrTol = 1.0e-30f;
\r
41 //-------------------------------------------------------------------------------------------------
\r
42 // separating axis tests: gaps along each axis are computed, and the axis with the maximum
\r
43 // gap is stored. cross product axes are normalized.
\r
44 //-------------------------------------------------------------------------------------------------
\r
46 #define AaxisTest( dim, letter, first ) \
\r
50 maxGap = gapsA.get##letter(); \
\r
51 if ( maxGap - capsuleB.m_radius > distanceThreshold ) return maxGap - capsuleB.m_radius; \
\r
52 axisType = BOX_AXIS; \
\r
54 axisA = ident[dim]; \
\r
58 PfxFloat gap = gapsA.get##letter(); \
\r
59 if ( gap - capsuleB.m_radius > distanceThreshold ) return gap - capsuleB.m_radius; \
\r
60 else if ( gap > maxGap ) \
\r
63 axisType = BOX_AXIS; \
\r
65 axisA = ident[dim]; \
\r
70 #define CrossAxisTest( dima, lettera ) \
\r
72 const PfxFloat lsqr_tolerance = 1.0e-30f; \
\r
75 lsqr = lsqrs.get##lettera(); \
\r
77 if ( lsqr > lsqr_tolerance ) \
\r
79 PfxFloat l_recip = 1.0f / sqrtf( lsqr ); \
\r
80 PfxFloat gap = PfxFloat(gapsAxB.get##lettera()) * l_recip; \
\r
82 if ( gap - capsuleB.m_radius > distanceThreshold ) \
\r
84 return gap - capsuleB.m_radius; \
\r
87 if ( gap > maxGap ) \
\r
90 axisType = CROSS_AXIS; \
\r
92 axisA = crossProdMat.getCol##dima() * l_recip; \
\r
97 //-------------------------------------------------------------------------------------------------
\r
98 // tests whether a vertex of box B and a face of box A are the closest features
\r
99 //-------------------------------------------------------------------------------------------------
\r
104 PfxBool& inVoronoi,
\r
107 PfxVector3& ptsVec,
\r
108 const PfxVector3& hA,
\r
109 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG offsetAB,
\r
110 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG capsDirection,
\r
114 // compute endpoint of capsule in box's coordinate system
\r
116 PfxVector3 endpoint = PfxVector3( offsetAB + capsDirection * scaleB );
\r
118 // compute the parameters of the point on the box face closest to this corner.
\r
125 else if ( t0 < -hA[0] )
\r
129 else if ( t1 < -hA[1] )
\r
132 // get vector from face point to capsule endpoint
\r
136 ptsVec = PfxVector3(endpoint);
\r
138 // do the Voronoi test: already know the point on B is in the Voronoi region of the
\r
139 // point on A, check the reverse.
\r
141 inVoronoi = ( -signB * dot(ptsVec,capsDirection) >= voronoiTol );
\r
143 return (lengthSqr(ptsVec));
\r
146 #define VertexBFaceA_SetNewMin() \
\r
148 minDistSqr = distSqr; \
\r
149 closestPtsVec = ptsVec; \
\r
150 localPointA.setX(t0); \
\r
151 localPointA.setY(t1); \
\r
152 segmentParamB = scaleB; \
\r
158 PfxFloat& minDistSqr,
\r
159 PfxVector3& closestPtsVec,
\r
160 PfxPoint3& localPointA,
\r
161 PfxFloat& segmentParamB,
\r
162 const PfxVector3& hA,
\r
163 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG offsetAB,
\r
164 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG capsDirection,
\r
165 PfxFloat signB, PfxFloat scaleB,
\r
172 // test endpoint of capsule nearest to face
\r
174 distSqr = VertexBFaceATest( done, t0, t1, ptsVec, hA, offsetAB, capsDirection, signB, scaleB );
\r
177 VertexBFaceA_SetNewMin();
\r
179 if ( distSqr < minDistSqr ) {
\r
180 VertexBFaceA_SetNewMin();
\r
190 // test other endpoint if necessary
\r
192 distSqr = VertexBFaceATest( done, t0, t1, ptsVec, hA, offsetAB, capsDirection, signB, scaleB );
\r
194 if ( distSqr < minDistSqr ) {
\r
195 VertexBFaceA_SetNewMin();
\r
199 //-------------------------------------------------------------------------------------------------
\r
202 // tests whether a pair of edges are the closest features
\r
204 // note on the shorthand:
\r
205 // 'a' & 'b' refer to the edges.
\r
206 // 'c' is the dimension of the axis that points from the face center to the edge Center
\r
207 // 'd' is the dimension of the edge Direction
\r
208 // the dimension of the face normal is 2
\r
209 //-------------------------------------------------------------------------------------------------
\r
211 #define EdgeEdgeTest( ac, ac_letter, ad, ad_letter ) \
\r
213 /* get vector between edge centers */ \
\r
215 ptsVec = offsetAB; \
\r
216 ptsVec.set##ac_letter( ptsVec.get##ac_letter() - scalesA.get##ac_letter() ); \
\r
218 /* find parameters of closest points on line segments. */ \
\r
220 PfxFloat capsDirection_ad = capsDirection.get##ad_letter(); \
\r
221 PfxFloat ptsVec_ad = ptsVec.get##ad_letter(); \
\r
222 PfxFloat capsDirDotPtsVec = dot(capsDirection,ptsVec); \
\r
223 PfxFloat denom = 1.0f - capsDirection_ad * capsDirection_ad; \
\r
225 if ( denom == 0.0f ) \
\r
231 tA = ( ptsVec_ad - capsDirDotPtsVec * capsDirection_ad ) / denom; \
\r
232 if ( tA < -hA[ad] ) tA = -hA[ad]; \
\r
233 else if ( tA > hA[ad] ) tA = hA[ad]; \
\r
236 tB = tA * capsDirection_ad - capsDirDotPtsVec; \
\r
241 tA = tB * capsDirection_ad + ptsVec_ad; \
\r
243 if ( tA < -hA[ad] ) tA = -hA[ad]; \
\r
244 else if ( tA > hA[ad] ) tA = hA[ad]; \
\r
246 else if ( tB > hB ) \
\r
249 tA = tB * capsDirection_ad + ptsVec_ad; \
\r
251 if ( tA < -hA[ad] ) tA = -hA[ad]; \
\r
252 else if ( tA > hA[ad] ) tA = hA[ad]; \
\r
255 /* make vector to point at tB on edge B from the center of edge A. */ \
\r
256 /* test that it lies inside edge A's voronoi region. */ \
\r
258 ptsVec += capsDirection * tB; \
\r
260 PfxVector3 cptsVec( mulPerElem( ptsVec, signsA ) ); \
\r
262 inVoronoi = ( cptsVec[ac] >= voronoiTol * cptsVec[2] ) && \
\r
263 ( cptsVec[2] >= voronoiTol * cptsVec[ac] ); \
\r
265 ptsVec.set##ad_letter( ptsVec.get##ad_letter() - tA ); \
\r
267 return lengthSqr(ptsVec); \
\r
272 PfxBool& inVoronoi,
\r
275 PfxVector3& ptsVec,
\r
276 const PfxVector3& hA,
\r
278 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG offsetAB,
\r
279 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG capsDirection,
\r
280 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG signsA,
\r
281 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG scalesA )
\r
283 EdgeEdgeTest( 0, X, 1, Y );
\r
288 PfxBool& inVoronoi,
\r
291 PfxVector3& ptsVec,
\r
292 const PfxVector3& hA,
\r
294 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG offsetAB,
\r
295 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG capsDirection,
\r
296 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG signsA,
\r
297 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG scalesA )
\r
299 EdgeEdgeTest( 1, Y, 0, X );
\r
302 #define EdgeEdge_SetNewMin( ac_letter, ad_letter ) \
\r
304 minDistSqr = distSqr; \
\r
305 closestPtsVec = ptsVec; \
\r
306 localPointA.set##ac_letter(scalesA.get##ac_letter()); \
\r
307 localPointA.set##ad_letter(tA); \
\r
308 segmentParamB = tB; \
\r
309 otherFaceDimA = testOtherFaceDimA; \
\r
315 PfxFloat& minDistSqr,
\r
316 PfxVector3& closestPtsVec,
\r
317 PfxPoint3& localPointA,
\r
318 PfxFloat& segmentParamB,
\r
319 int & otherFaceDimA,
\r
320 const PfxVector3& hA,
\r
322 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG offsetAB,
\r
323 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG capsDirection,
\r
324 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG signsA,
\r
325 PfxVector3 SCE_VECTORMATH_AOS_VECTOR_ARG scalesA,
\r
330 int testOtherFaceDimA;
\r
332 testOtherFaceDimA = 0;
\r
334 PfxFloat distSqr = EdgeEdgeTest_01( done, tA, tB, ptsVec, hA, hB,
\r
335 offsetAB, capsDirection, signsA, scalesA );
\r
338 EdgeEdge_SetNewMin( X, Y );
\r
340 if ( distSqr < minDistSqr ) {
\r
341 EdgeEdge_SetNewMin( X, Y );
\r
348 signsA.setX( -signsA.getX() );
\r
349 scalesA.setX( -scalesA.getX() );
\r
351 distSqr = EdgeEdgeTest_01( done, tA, tB, ptsVec, hA, hB,
\r
352 offsetAB, capsDirection, signsA, scalesA );
\r
354 if ( distSqr < minDistSqr ) {
\r
355 EdgeEdge_SetNewMin( X, Y );
\r
361 testOtherFaceDimA = 1;
\r
363 distSqr = EdgeEdgeTest_10( done, tA, tB, ptsVec, hA, hB,
\r
364 offsetAB, capsDirection, signsA, scalesA );
\r
366 if ( distSqr < minDistSqr ) {
\r
367 EdgeEdge_SetNewMin( Y, X );
\r
373 signsA.setY( -signsA.getY() );
\r
374 scalesA.setY( -scalesA.getY() );
\r
376 distSqr = EdgeEdgeTest_10( done, tA, tB, ptsVec, hA, hB,
\r
377 offsetAB, capsDirection, signsA, scalesA );
\r
379 if ( distSqr < minDistSqr ) {
\r
380 EdgeEdge_SetNewMin( Y, X );
\r
384 PfxFloat pfxContactBoxCapsule(
\r
385 PfxVector3 &normal,PfxPoint3 &pointA,PfxPoint3 &pointB,
\r
386 void *shapeA,const PfxTransform3 &transformA,
\r
387 void *shapeB,const PfxTransform3 &transformB,
\r
388 PfxFloat distanceThreshold)
\r
390 PfxBox boxA = *((PfxBox*)shapeA);
\r
391 PfxCapsule capsuleB = *((PfxCapsule*)shapeB);
\r
393 PfxVector3 ident[3] = {
\r
394 PfxVector3(1.0,0.0,0.0),
\r
395 PfxVector3(0.0,1.0,0.0),
\r
396 PfxVector3(0.0,0.0,1.0),
\r
399 // get capsule position and direction in box's coordinate system
\r
401 PfxMatrix3 matrixA = transformA.getUpper3x3();
\r
402 PfxMatrix3 matrixAinv = transpose(matrixA);
\r
404 PfxVector3 directionB = transformB.getUpper3x3().getCol0();
\r
405 PfxVector3 translationB = transformB.getTranslation();
\r
407 PfxVector3 capsDirection = matrixAinv * directionB;
\r
408 PfxVector3 absCapsDirection = absPerElem(capsDirection);
\r
409 PfxVector3 offsetAB = matrixAinv * (translationB - transformA.getTranslation());
\r
411 // find separating axis with largest gap between projections
\r
413 BoxCapsSepAxisType axisType;
\r
416 int faceDimA = 0, edgeDimA = 0;
\r
420 // can compute all the gaps at once with VU0
\r
422 PfxVector3 gapsA = absPerElem(offsetAB) - boxA.m_half - absCapsDirection * capsuleB.m_halfLen;
\r
424 AaxisTest( 0, X, true );
\r
425 AaxisTest( 1, Y, false );
\r
426 AaxisTest( 2, Z, false );
\r
428 // cross product axes
\r
430 // compute gaps on all cross product axes using some VU0 math. suppose there's a tradeoff
\r
431 // between doing this with SIMD all at once or without SIMD in each cross product test, since
\r
432 // some test might exit early.
\r
434 PfxVector3 lsqrs, projOffset, projAhalf;
\r
436 PfxMatrix3 crossProdMat = crossMatrix(capsDirection) * PfxMatrix3::identity();
\r
437 PfxMatrix3 crossProdMatT = crossMatrix(-capsDirection) * PfxMatrix3::identity();
\r
439 lsqrs = mulPerElem( crossProdMatT.getCol0(), crossProdMatT.getCol0() ) +
\r
440 mulPerElem( crossProdMatT.getCol1(), crossProdMatT.getCol1() ) +
\r
441 mulPerElem( crossProdMatT.getCol2(), crossProdMatT.getCol2() );
\r
443 projOffset = crossProdMatT * offsetAB;
\r
444 projAhalf = absPerElem(crossProdMatT) * boxA.m_half;
\r
446 PfxVector3 gapsAxB = absPerElem(projOffset) - projAhalf;
\r
448 CrossAxisTest( 0, X );
\r
449 CrossAxisTest( 1, Y );
\r
450 CrossAxisTest( 2, Z );
\r
452 // make axis point from box center towards capsule center.
\r
454 if ( dot(axisA,offsetAB) < 0.0f )
\r
457 // find the face on box whose normal best matches the separating axis. will use the entire
\r
458 // face only in degenerate cases.
\r
460 // to make things simpler later, change the coordinate system so that the face normal is the z
\r
461 // direction. if an edge cross product axis was chosen above, also align the box edge to the y
\r
462 // axis. this saves the later tests from having to know which face was chosen. changing the
\r
463 // coordinate system involves permuting vector elements, so construct a permutation matrix.
\r
464 // I believe this is a faster way to permute a bunch of vectors than using arrays.
\r
468 if ( axisType == CROSS_AXIS ) {
\r
469 PfxVector3 absAxisA = PfxVector3(absPerElem(axisA));
\r
471 dimA[1] = edgeDimA;
\r
473 if ( edgeDimA == 0 ) {
\r
474 if ( absAxisA[1] > absAxisA[2] ) {
\r
481 } else if ( edgeDimA == 1 ) {
\r
482 if ( absAxisA[2] > absAxisA[0] ) {
\r
490 if ( absAxisA[0] > absAxisA[1] ) {
\r
499 dimA[2] = faceDimA;
\r
500 dimA[0] = (faceDimA+1)%3;
\r
501 dimA[1] = (faceDimA+2)%3;
\r
504 PfxMatrix3 aperm_col;
\r
506 aperm_col.setCol0(ident[dimA[0]]);
\r
507 aperm_col.setCol1(ident[dimA[1]]);
\r
508 aperm_col.setCol2(ident[dimA[2]]);
\r
510 PfxMatrix3 aperm_row = transpose(aperm_col);
\r
512 // permute vectors to be in face coordinate system.
\r
514 PfxVector3 offsetAB_perm = aperm_row * offsetAB;
\r
515 PfxVector3 halfA_perm = aperm_row * boxA.m_half;
\r
516 PfxVector3 signsA_perm = copySignPerElem(PfxVector3(1.0f), aperm_row * axisA);
\r
517 PfxVector3 scalesA_perm = mulPerElem( signsA_perm, halfA_perm );
\r
518 PfxVector3 capsDirection_perm = aperm_row * capsDirection;
\r
519 PfxFloat signB = (-dot(capsDirection,axisA) > 0.0f)? 1.0f : -1.0f;
\r
520 PfxFloat scaleB = signB * capsuleB.m_halfLen;
\r
522 // compute the vector between the center of the box face and the capsule center
\r
524 offsetAB_perm.setZ( offsetAB_perm.getZ() - scalesA_perm.getZ() );
\r
526 // if box and capsule overlap, this will separate them for finding points of penetration.
\r
528 if ( maxGap < 0.0f ) {
\r
529 offsetAB_perm -= aperm_row * axisA * maxGap * 1.01f;
\r
532 // for each vertex/face or edge/edge pair of box face and line segment, find the closest
\r
535 // these points each have an associated feature (vertex, edge, or face). if each
\r
536 // point is in the external Voronoi region of the other's feature, they are the
\r
537 // closest points of the objects, and the algorithm can exit.
\r
539 // the feature pairs are arranged so that in the general case, the first test will
\r
540 // succeed. degenerate cases (line segment parallel to face) may require up to all tests
\r
541 // in the worst case.
\r
543 // if for some reason no case passes the Voronoi test, the features with the minimum
\r
544 // distance are returned.
\r
546 PfxVector3 closestPtsVec_perm;
\r
547 PfxPoint3 localPointA_perm;
\r
548 PfxFloat minDistSqr;
\r
549 PfxFloat segmentParamB;
\r
552 localPointA_perm.setZ( scalesA_perm.getZ() );
\r
553 scalesA_perm.setZ(0.0f);
\r
555 PfxVector3 hA_perm( halfA_perm );
\r
559 if ( axisType == CROSS_AXIS ) {
\r
560 EdgeEdgeTests( done, minDistSqr, closestPtsVec_perm, localPointA_perm, segmentParamB,
\r
562 hA_perm, capsuleB.m_halfLen, offsetAB_perm, capsDirection_perm, signsA_perm,
\r
563 scalesA_perm, true );
\r
566 VertexBFaceATests( done, minDistSqr, closestPtsVec_perm, localPointA_perm, segmentParamB,
\r
567 hA_perm, offsetAB_perm, capsDirection_perm, signB, scaleB, false );
\r
570 VertexBFaceATests( done, minDistSqr, closestPtsVec_perm, localPointA_perm, segmentParamB,
\r
571 hA_perm, offsetAB_perm, capsDirection_perm, signB, scaleB, true );
\r
574 EdgeEdgeTests( done, minDistSqr, closestPtsVec_perm, localPointA_perm, segmentParamB,
\r
576 hA_perm, capsuleB.m_halfLen, offsetAB_perm, capsDirection_perm, signsA_perm,
\r
577 scalesA_perm, false );
\r
583 PfxBool centerInside = ( signsA_perm.getZ() * closestPtsVec_perm.getZ() < 0.0f );
\r
585 if ( centerInside || ( minDistSqr < lenSqrTol ) ) {
\r
586 normal = matrixA * axisA;
\r
588 PfxVector3 closestPtsVec = aperm_col * closestPtsVec_perm;
\r
589 normal = matrixA * ( closestPtsVec * (1.0f/sqrtf( minDistSqr )) );
\r
592 // compute box point
\r
594 pointA = PfxPoint3( aperm_col * PfxVector3( localPointA_perm ) );
\r
596 // compute capsule point
\r
598 pointB = PfxPoint3( transpose(transformB.getUpper3x3()) * ( directionB * segmentParamB - normal * capsuleB.m_radius ) );
\r
600 if ( centerInside ) {
\r
601 return (-sqrtf( minDistSqr ) - capsuleB.m_radius);
\r
603 return (sqrtf( minDistSqr ) - capsuleB.m_radius);
\r
607 } //namespace PhysicsEffects
\r