#include "algorithm.h"
#include "contact.h"
#include "core.h"
#include "manifold.h"
#include "shape.h"
#include "box3d/base.h"
#include "box3d/collision.h"
#include "box3d/constants.h"
#include <stdbool.h>
#include <stddef.h>
typedef struct b3TriangleData
{
b3Vec3 v1, v2, v3;
b3Vec3 e1, e2, e3;
b3Plane plane;
int flags;
} b3TriangleData;
static const b3TriangleFeature s_triangleFeatures[8] = {
b3_featureNone, b3_featureVertex1, b3_featureVertex2, b3_featureEdge1, b3_featureVertex3, b3_featureEdge3, b3_featureEdge2, b3_featureTriangleFace, };
static b3TriangleFeature b3GetTriangleFeature( const b3SimplexCache* cache )
{
int count = cache->count;
B3_ASSERT( 0 < count && count < 4 );
int mask = 0;
for ( int i = 0; i < count; ++i )
{
B3_ASSERT( cache->indexA[i] < 3 );
mask |= 1 << cache->indexA[i];
}
return s_triangleFeatures[mask];
}
void b3CollideTriangleAndSphere( b3LocalManifold* manifold, int capacity, const b3Vec3* triangleA, const b3Sphere* sphereB )
{
manifold->pointCount = 0;
if ( capacity == 0 )
{
return;
}
b3Vec3 center = sphereB->center;
b3Vec3 v1 = triangleA[0], v2 = triangleA[1], v3 = triangleA[2];
b3Plane plane = b3MakePlaneFromPoints( v1, v2, v3 );
float offset = b3PlaneSeparation( plane, center );
if ( offset < 0.0f )
{
return;
}
float radius = sphereB->radius;
b3TrianglePoint closest = b3ClosestPointOnTriangle( v1, v2, v3, center );
float squaredDistance = b3DistanceSquared( closest.point, center );
float speculativeDistance = B3_SPECULATIVE_DISTANCE;
float maxDistance = radius + speculativeDistance;
if ( squaredDistance > maxDistance * maxDistance )
{
return;
}
float distance = sqrtf( squaredDistance );
b3Vec3 normal;
if ( distance * distance > 1000.0f * FLT_MIN )
{
normal = b3MulSV( 1.0f / distance, b3Sub( center, closest.point ) );
}
else
{
normal = b3Normalize( b3Cross( b3Sub( v2, v1 ), b3Sub( v3, v1 ) ) );
}
b3Vec3 contactPoint = b3MulSV( 0.5f, b3Add( b3Sub( center, b3MulSV( radius, normal ) ), closest.point ) );
manifold->normal = normal;
manifold->pointCount = 1;
manifold->feature = closest.feature;
manifold->squaredDistance = squaredDistance;
b3LocalManifoldPoint* mp = manifold->points + 0;
mp->point = contactPoint;
mp->separation = distance - radius;
mp->pair = b3FeaturePair_single;
}
static bool b3ClipSegmentToTriangleFace( b3ClipVertex segment[2], const b3Vec3* points, b3Plane plane )
{
b3Vec3 vertex1 = points[2];
for ( int i = 0; i < 3; ++i )
{
b3Vec3 vertex2 = points[i];
b3Vec3 tangent = b3Normalize( b3Sub( vertex2, vertex1 ) );
b3Vec3 binormal = b3Cross( tangent, plane.normal );
b3Plane clipPlane = b3MakePlaneFromNormalAndPoint( binormal, vertex1 );
int vertexCount = 0;
b3ClipVertex p1 = segment[0];
b3ClipVertex p2 = segment[1];
float distance1 = b3PlaneSeparation( clipPlane, p1.position );
float distance2 = b3PlaneSeparation( clipPlane, p2.position );
if ( distance1 <= 0.0f )
{
segment[vertexCount++] = p1;
}
if ( distance2 <= 0.0f )
{
segment[vertexCount++] = p2;
}
if ( distance1 * distance2 < 0.0f )
{
float t = distance1 / ( distance1 - distance2 );
segment[vertexCount].position = b3Lerp( p1.position, p2.position, t );
segment[vertexCount].pair = distance1 > 0.0f ? p1.pair : p2.pair;
vertexCount++;
}
if ( vertexCount != 2 )
{
return false;
}
vertex1 = vertex2;
}
return true;
}
static b3SeparatingAxis b3QueryTriangleFaceAndCapsule( b3Plane plane, const b3Capsule* capsule )
{
float separation1 = b3PlaneSeparation( plane, capsule->center1 );
float separation2 = b3PlaneSeparation( plane, capsule->center2 );
if ( separation1 < separation2 )
{
return (b3SeparatingAxis){
.normal = plane.normal,
.separation = separation1,
.indexA = 0,
.indexB = 0,
};
}
return (b3SeparatingAxis){
.normal = plane.normal,
.separation = separation2,
.indexA = 0,
.indexB = 1,
};
}
static b3SeparatingAxis b3QueryTriangleAndCapsuleEdges( const b3Vec3* vertices, b3Plane plane, const b3Capsule* capsule )
{
b3Vec3 p1 = capsule->center1;
b3Vec3 p2 = capsule->center2;
b3Vec3 capsuleEdge = b3Sub( p2, p1 );
b3Vec3 maxNormal = b3Vec3_zero;
float maxSeparation = -FLT_MAX;
int maxIndex1 = B3_NULL_INDEX;
int maxIndex2 = B3_NULL_INDEX;
float squaredTolerance = 0.005f * 0.005f;
int edgeIndex = 2;
b3Vec3 v1 = vertices[2];
for ( int index = 0; index < 3; ++index )
{
b3Vec3 v2 = vertices[index];
b3Vec3 triangleEdge = b3Sub( v2, v1 );
b3Vec3 sideNormal = b3Normalize( b3Cross( triangleEdge, plane.normal ) );
float a = b3Dot( capsuleEdge, plane.normal );
float b = b3Dot( capsuleEdge, sideNormal );
if ( a * a + b * b < squaredTolerance * b3LengthSquared( capsuleEdge ) )
{
continue;
}
b3Vec3 axis;
if ( a * b <= 0.0f )
{
float t = b / ( b - a );
axis = b3Lerp( sideNormal, plane.normal, t );
}
else
{
float t = b / ( a + b );
axis = b3Lerp( sideNormal, b3Neg( plane.normal ), t );
}
B3_VALIDATE( b3LengthSquared( axis ) > 1000.0f * FLT_MIN );
axis = b3Normalize( axis );
float separation = b3Dot( axis, b3Sub( p1, v1 ) );
if ( separation > maxSeparation )
{
maxNormal = axis;
maxSeparation = separation;
maxIndex1 = edgeIndex;
maxIndex2 = 0;
}
v1 = v2;
edgeIndex = index;
}
return (b3SeparatingAxis){
.normal = maxNormal,
.separation = maxSeparation,
.indexA = maxIndex1,
.indexB = maxIndex2,
};
}
static void b3BuildTriangleAndCapsuleFaceContact( b3LocalManifold* manifold, const b3Vec3* triangle, b3Plane plane,
const b3Capsule* capsule )
{
B3_ASSERT( manifold->pointCount == 0 );
b3ClipVertex segment[2];
segment[0].position = capsule->center1;
segment[0].separation = 0.0f;
segment[0].pair = b3MakeFeaturePair( b3_featureShapeA, 0, b3_featureShapeA, 0 );
segment[1].position = capsule->center2;
segment[1].separation = 0.0f;
segment[1].pair = b3MakeFeaturePair( b3_featureShapeA, 1, b3_featureShapeA, 1 );
bool havePoints = b3ClipSegmentToTriangleFace( segment, triangle, plane );
if ( havePoints == false )
{
return;
}
float radius = capsule->radius;
float distance1 = b3PlaneSeparation( plane, segment[0].position );
float distance2 = b3PlaneSeparation( plane, segment[1].position );
float speculativeDistance = B3_SPECULATIVE_DISTANCE;
if ( distance1 > speculativeDistance + radius && distance2 > speculativeDistance + radius )
{
return;
}
b3Vec3 point1 = b3MulSub( segment[0].position, 0.5f * ( distance1 + capsule->radius ), plane.normal );
b3Vec3 point2 = b3MulSub( segment[1].position, 0.5f * ( distance2 + capsule->radius ), plane.normal );
manifold->normal = plane.normal;
manifold->feature = b3_featureTriangleFace;
manifold->pointCount = 2;
b3LocalManifoldPoint* pt = manifold->points + 0;
pt->point = point1;
pt->separation = distance1 - capsule->radius;
pt->pair = segment[0].pair;
pt = manifold->points + 1;
pt->point = point2;
pt->separation = distance2 - capsule->radius;
pt->pair = segment[1].pair;
}
static void b3BuildTriangleAndCapsuleEdgeContact( b3LocalManifold* manifold, const b3Vec3* triangle, b3Plane plane,
const b3Capsule* capsule, b3SeparatingAxis query )
{
B3_ASSERT( 0 <= query.indexA && query.indexA < 3 );
b3Vec3 p1 = capsule->center1;
b3Vec3 p2 = capsule->center2;
b3Vec3 capsuleEdge = b3Sub( p2, p1 );
const b3Vec3* vs = triangle;
b3Vec3 v1 = vs[query.indexA];
b3Vec3 v2 = vs[( query.indexA + 1 ) % 3];
b3Vec3 triangleEdge = b3Sub( v2, v1 );
b3Vec3 sideNormal = b3Normalize( b3Cross( triangleEdge, plane.normal ) );
float a = b3Dot( capsuleEdge, plane.normal );
float b = b3Dot( capsuleEdge, sideNormal );
float squaredTolerance = 0.005f * 0.005f;
if ( a * a + b * b < squaredTolerance * b3LengthSquared( capsuleEdge ) )
{
return;
}
b3Vec3 normal = query.normal;
b3SegmentDistanceResult result = b3LineDistance( v1, triangleEdge, p1, capsuleEdge );
if ( result.fraction1 < 0.0f || 1.0f < result.fraction1 || result.fraction2 < 0.0f || 1.0f < result.fraction2 )
{
return;
}
b3Vec3 point = b3Lerp( b3MulSub( result.point1, capsule->radius, normal ), result.point2, 0.5f );
float separation = b3Dot( normal, b3Sub( p1, v1 ) );
B3_VALIDATE( b3AbsFloat( separation - query.separation ) < B3_LINEAR_SLOP );
manifold->normal = normal;
manifold->pointCount = 1;
b3TriangleFeature edgesFeatures[] = { b3_featureEdge1, b3_featureEdge2, b3_featureEdge3 };
manifold->feature = edgesFeatures[query.indexA];
b3LocalManifoldPoint* pt = manifold->points + 0;
pt->point = point;
pt->separation = separation - capsule->radius;
pt->pair = b3MakeFeaturePair( b3_featureShapeA, query.indexA, b3_featureShapeB, query.indexB );
}
void b3CollideTriangleAndCapsule( b3LocalManifold* manifold, int capacity, const b3Vec3* triangleA, const b3Capsule* capsuleB,
b3SimplexCache* cache )
{
manifold->pointCount = 0;
if ( capacity < 2 )
{
return;
}
b3Vec3 v1 = triangleA[0], v2 = triangleA[1], v3 = triangleA[2];
b3Plane plane = b3MakePlaneFromPoints( v1, v2, v3 );
b3Vec3 capsuleCenter = b3Lerp( capsuleB->center1, capsuleB->center2, 0.5f );
float offset = b3PlaneSeparation( plane, capsuleCenter );
if ( offset < 0.0f )
{
return;
}
b3DistanceInput distanceInput;
distanceInput.proxyA = (b3ShapeProxy){ triangleA, 3, 0.0f };
distanceInput.proxyB = (b3ShapeProxy){ &capsuleB->center1, 2, 0.0f };
distanceInput.transform = b3Transform_identity;
distanceInput.useRadii = false;
b3DistanceOutput distanceOutput = b3ShapeDistance( &distanceInput, cache, NULL, 0 );
float speculativeDistance = B3_SPECULATIVE_DISTANCE;
float radius = capsuleB->radius;
if ( distanceOutput.distance > radius + speculativeDistance )
{
return;
}
if ( distanceOutput.distance > 100.0f * FLT_EPSILON )
{
b3Vec3 delta = b3Normalize( b3Sub( distanceOutput.pointB, distanceOutput.pointA ) );
const float kTolerance = 0.2f;
float cosAngle = b3AbsFloat( b3Dot( plane.normal, delta ) );
if ( cosAngle > kTolerance )
{
b3ClipVertex segment[2];
segment[0].position = capsuleB->center1;
segment[0].separation = 0.0f;
segment[0].pair = b3MakeFeaturePair( b3_featureShapeA, 0, b3_featureShapeA, 0 );
segment[1].position = capsuleB->center2;
segment[1].separation = 0.0f;
segment[1].pair = b3MakeFeaturePair( b3_featureShapeA, 1, b3_featureShapeA, 1 );
bool havePoints = b3ClipSegmentToTriangleFace( segment, triangleA, plane );
if ( havePoints == true )
{
float distance1 = b3PlaneSeparation( plane, segment[0].position );
float distance2 = b3PlaneSeparation( plane, segment[1].position );
b3Vec3 normal = plane.normal;
b3Vec3 point1 = b3MulSub( segment[0].position, 0.5f * ( radius + distance1 ), normal );
b3Vec3 point2 = b3MulSub( segment[1].position, 0.5f * ( radius + distance2 ), normal );
manifold->normal = normal;
manifold->feature = b3_featureTriangleFace;
manifold->pointCount = 2;
b3LocalManifoldPoint* mp = manifold->points + 0;
mp->point = point1;
mp->separation = distance1 - radius;
mp->pair = segment[0].pair;
mp = manifold->points + 1;
mp->point = point2;
mp->separation = distance2 - radius;
mp->pair = segment[1].pair;
return;
}
}
b3Vec3 point = b3MulSV( 0.5f, b3Add( b3Sub( distanceOutput.pointA, b3MulSV( radius, delta ) ), distanceOutput.pointB ) );
manifold->normal = delta;
manifold->pointCount = 1;
manifold->feature = b3GetTriangleFeature( cache );
b3LocalManifoldPoint* mp = manifold->points + 0;
mp->point = point;
mp->separation = distanceOutput.distance - radius;
mp->pair = b3FeaturePair_single;
return;
}
b3SeparatingAxis faceQuery = b3QueryTriangleFaceAndCapsule( plane, capsuleB );
if ( faceQuery.separation > radius )
{
return;
}
b3SeparatingAxis edgeQuery = b3QueryTriangleAndCapsuleEdges( triangleA, plane, capsuleB );
if ( edgeQuery.separation > radius )
{
return;
}
float faceSeparation = faceQuery.separation - radius;
b3BuildTriangleAndCapsuleFaceContact( manifold, triangleA, plane, capsuleB );
B3_VALIDATE( manifold->pointCount == 0 || manifold->pointCount == 2 );
if ( manifold->pointCount == 2 )
{
faceSeparation = b3MinFloat( manifold->points[0].separation, manifold->points[1].separation );
}
float linearSlop = B3_LINEAR_SLOP;
float edgeSeparation = edgeQuery.separation - radius;
if ( manifold->pointCount == 0 || edgeSeparation > faceSeparation + linearSlop )
{
b3BuildTriangleAndCapsuleEdgeContact( manifold, triangleA, plane, capsuleB, edgeQuery );
}
}
static inline int b3GetTriangleSupport( b3Vec3* points, b3Vec3 direction )
{
int index = 0;
float distance = b3Dot( points[0], direction );
float d = b3Dot( points[1], direction );
if ( d > distance )
{
distance = d;
index = 1;
}
d = b3Dot( points[2], direction );
if ( d > distance )
{
return 2;
}
return index;
}
static b3SeparatingAxis b3QueryTriangleFace( const b3TriangleData* triangle, const b3HullData* hull )
{
const b3Vec3* hullPoints = b3GetHullPoints( hull );
b3Plane plane = triangle->plane;
b3Vec3 normal = b3Neg( plane.normal );
int vertexIndex = b3FindHullSupportVertex( hull, normal );
b3Vec3 support = hullPoints[vertexIndex];
float separation = b3PlaneSeparation( plane, support );
return (b3SeparatingAxis){
.normal = plane.normal,
.separation = separation,
.indexA = 0,
.indexB = vertexIndex,
.type = b3_faceAxisA,
};
}
static b3SeparatingAxis b3QueryHullFace( const b3TriangleData* triangle, const b3HullData* hull )
{
const b3Plane* hullPlanes = b3GetHullPlanes( hull );
int faceCount = hull->faceCount;
b3Vec3 trianglePoints[] = { triangle->v1, triangle->v2, triangle->v3 };
b3Vec3 maxNormal = b3Vec3_zero;
float maxFaceSeparation = -INFINITY;
int maxFaceIndex = B3_NULL_INDEX;
int maxVertexIndex = B3_NULL_INDEX;
for ( int faceIndex = 0; faceIndex < faceCount; ++faceIndex )
{
b3Plane plane = hullPlanes[faceIndex];
int vertexIndex = b3GetTriangleSupport( trianglePoints, b3Neg( plane.normal ) );
b3Vec3 support = trianglePoints[vertexIndex];
float separation = b3PlaneSeparation( plane, support );
if ( separation > maxFaceSeparation )
{
maxNormal = plane.normal;
maxFaceSeparation = separation;
maxFaceIndex = faceIndex;
maxVertexIndex = vertexIndex;
}
}
return (b3SeparatingAxis){
.normal = b3Neg( maxNormal ),
.separation = maxFaceSeparation,
.indexA = maxVertexIndex,
.indexB = maxFaceIndex,
.type = b3_faceAxisB,
};
}
static b3SeparatingAxis b3QueryTriangleAndHullEdges( const b3TriangleData* triangle, const b3HullData* hull )
{
b3SeparatingAxis result = {
.normal = b3Vec3_zero,
.separation = -INFINITY,
.indexA = B3_NULL_INDEX,
.indexB = B3_NULL_INDEX,
.type = b3_edgePairAxis,
};
b3Vec3 trianglePoints[] = { triangle->v1, triangle->v2, triangle->v3 };
b3Vec3 triangleEdges[] = { triangle->e1, triangle->e2, triangle->e3 };
#if B3_FORCE_GHOST_COLLISIONS
int triangleFlags = 0xFF;
#else
int triangleFlags = triangle->flags;
#endif
(void)triangleFlags;
b3Vec3 triNormal = triangle->plane.normal;
const b3HullHalfEdge* hullEdges = b3GetHullEdges( hull );
const b3Vec3* hullPoints = b3GetHullPoints( hull );
const b3Plane* hullPlanes = b3GetHullPlanes( hull );
int edgeCount = hull->edgeCount;
float squaredTolerance = 0.005f * 0.005f;
for ( int i = 0; i < edgeCount; i += 2 )
{
const b3HullHalfEdge* edge = hullEdges + i;
const b3HullHalfEdge* twin = hullEdges + i + 1;
B3_ASSERT( edge->twin == i + 1 && twin->twin == i );
b3Vec3 hullPoint = hullPoints[edge->origin];
b3Vec3 hullEdge = b3Sub( hullPoints[twin->origin], hullPoint );
b3Vec3 hullNormal1 = hullPlanes[edge->face].normal;
b3Vec3 hullNormal2 = hullPlanes[twin->face].normal;
for ( int j = 0; j < 3; ++j )
{
b3Vec3 triEdge = triangleEdges[j];
float cab = b3Dot( hullNormal1, triEdge );
float dab = b3Dot( hullNormal2, triEdge );
float bcd = b3Dot( triNormal, hullEdge );
if ( cab * dab >= 0.0f || cab * bcd <= 0.0f )
{
continue;
}
if ( b3MaxFloat( cab * cab, dab * dab ) < squaredTolerance * b3LengthSquared( triEdge ) )
{
continue;
}
float t = cab / ( cab - dab );
b3Vec3 axis = b3Lerp( hullNormal1, hullNormal2, t );
B3_VALIDATE( b3LengthSquared( axis ) > 1000.0f * FLT_MIN );
axis = b3Normalize( axis );
float separation = b3Dot( axis, b3Sub( trianglePoints[j], hullPoint ) );
if ( separation > result.separation )
{
result.normal = b3Neg( axis );
result.separation = separation;
result.indexA = j;
result.indexB = i;
}
}
}
return result;
}
static float b3CollideHullFace( b3LocalManifold* manifold, int pointCapacity, const b3TriangleData* triangle,
const b3HullData* hull, b3SeparatingAxis query, b3SATCache* cache, bool enableSpeculative )
{
B3_VALIDATE( query.type == b3_faceAxisB );
B3_VALIDATE( 0 <= query.indexA && query.indexA < 3 );
B3_VALIDATE( 0 <= query.indexB && query.indexB < hull->faceCount );
manifold->pointCount = 0;
const b3HullFace* hullFaces = b3GetHullFaces( hull );
const b3HullHalfEdge* hullEdges = b3GetHullEdges( hull );
const b3Plane* hullPlanes = b3GetHullPlanes( hull );
const b3Vec3* hullPoints = b3GetHullPoints( hull );
b3Plane refPlane = hullPlanes[query.indexB];
b3ClipVertex buffer1[B3_MAX_CLIP_POINTS], buffer2[B3_MAX_CLIP_POINTS];
b3Vec3 v1 = triangle->v1;
b3Vec3 v2 = triangle->v2;
b3Vec3 v3 = triangle->v3;
buffer1[0].position = v1;
buffer1[0].separation = b3PlaneSeparation( refPlane, v1 );
buffer1[0].pair = b3MakeFeaturePair( b3_featureShapeB, 2, b3_featureShapeB, 0 );
buffer1[1].position = v2;
buffer1[1].separation = b3PlaneSeparation( refPlane, v2 );
buffer1[1].pair = b3MakeFeaturePair( b3_featureShapeB, 0, b3_featureShapeB, 1 );
buffer1[2].position = v3;
buffer1[2].separation = b3PlaneSeparation( refPlane, v3 );
buffer1[2].pair = b3MakeFeaturePair( b3_featureShapeB, 1, b3_featureShapeB, 2 );
int pointCount = 3;
b3ClipVertex* input = buffer1;
b3ClipVertex* output = buffer2;
const b3HullFace* face = hullFaces + query.indexB;
int edgeIndex = face->edge;
do
{
const b3HullHalfEdge* edge = hullEdges + edgeIndex;
int nextEdgeIndex = edge->next;
const b3HullHalfEdge* next = hullEdges + nextEdgeIndex;
b3Vec3 vertex1 = hullPoints[edge->origin];
b3Vec3 vertex2 = hullPoints[next->origin];
b3Vec3 tangent = b3Normalize( b3Sub( vertex2, vertex1 ) );
b3Vec3 binormal = b3Cross( tangent, refPlane.normal );
b3Plane clipPlane = b3MakePlaneFromNormalAndPoint( binormal, vertex1 );
pointCount = b3ClipPolygon( output, input, pointCount, clipPlane, edgeIndex, refPlane );
B3_ASSERT( pointCount <= B3_MAX_CLIP_POINTS );
if ( pointCount < 3 )
{
*cache = (b3SATCache){ 0 };
return query.separation;
}
B3_SWAP( output, input );
edgeIndex = nextEdgeIndex;
}
while ( edgeIndex != face->edge );
pointCount = b3MinInt( pointCount, pointCapacity );
float minSeparation = FLT_MAX;
int finalPointCount = 0;
for ( int i = 0; i < pointCount; ++i )
{
b3ClipVertex* clipPoint = input + i;
minSeparation = b3MinFloat( minSeparation, clipPoint->separation );
if ( enableSpeculative == false && clipPoint->separation > 0.0f )
{
continue;
}
b3Vec3 point = b3MulSub( clipPoint->position, clipPoint->separation, refPlane.normal );
b3LocalManifoldPoint* pt = manifold->points + finalPointCount;
pt->point = point;
pt->separation = clipPoint->separation;
pt->pair = b3FlipPair( clipPoint->pair );
finalPointCount += 1;
}
float speculativeDistance = enableSpeculative ? B3_SPECULATIVE_DISTANCE : 0.0f;
if ( minSeparation > speculativeDistance )
{
manifold->pointCount = 0;
*cache = (b3SATCache){ 0 };
return minSeparation;
}
manifold->pointCount = finalPointCount;
manifold->normal = b3Neg( refPlane.normal );
manifold->feature = b3_featureHullFace;
cache->separation = minSeparation;
cache->type = b3_faceAxisB;
cache->indexA = (uint8_t)query.indexA;
cache->indexB = (uint8_t)query.indexB;
return minSeparation;
}
static float b3CollideTriangleFace( b3LocalManifold* manifold, int pointCapacity, const b3TriangleData* triangle,
const b3HullData* hull, b3SeparatingAxis query, b3SATCache* cache, bool enableSpeculative )
{
B3_VALIDATE( query.type == b3_faceAxisA );
B3_VALIDATE( query.indexA == 0 );
B3_VALIDATE( 0 <= query.indexB && query.indexB < hull->vertexCount );
B3_VALIDATE( manifold->pointCount == 0 );
const b3HullFace* hullFaces = b3GetHullFaces( hull );
const b3HullHalfEdge* hullEdges = b3GetHullEdges( hull );
const b3Vec3* hullPoints = b3GetHullPoints( hull );
b3Plane refPlane = triangle->plane;
int incFace = b3FindIncidentFace( hull, refPlane.normal, query.indexB );
b3ClipVertex buffer1[2 * B3_MAX_CLIP_POINTS], buffer2[2 * B3_MAX_CLIP_POINTS];
int pointCount = 0;
const b3HullFace* face = hullFaces + incFace;
int hullEdgeIndex = face->edge;
do
{
const b3HullHalfEdge* edge = hullEdges + hullEdgeIndex;
int nextEdgeIndex = edge->next;
const b3HullHalfEdge* next = hullEdges + nextEdgeIndex;
b3Vec3 hullPoint = hullPoints[next->origin];
buffer1[pointCount].position = hullPoint;
buffer1[pointCount].separation = b3PlaneSeparation( refPlane, hullPoint );
buffer1[pointCount].pair = b3MakeFeaturePair( b3_featureShapeB, hullEdgeIndex, b3_featureShapeB, nextEdgeIndex );
pointCount += 1;
hullEdgeIndex = nextEdgeIndex;
}
while ( hullEdgeIndex != face->edge && pointCount < 2 * B3_MAX_CLIP_POINTS );
B3_ASSERT( pointCount >= 3 );
b3ClipVertex* input = buffer1;
b3ClipVertex* output = buffer2;
b3Vec3 trianglePoints[] = { triangle->v1, triangle->v2, triangle->v3 };
b3Vec3 triangleEdges[] = { triangle->e1, triangle->e2, triangle->e3 };
for ( int i = 0; i < 3 && pointCount > 0; ++i )
{
b3Vec3 sideNormal = b3Cross( triangleEdges[i], refPlane.normal );
sideNormal = b3Normalize( sideNormal );
b3Plane clipPlane = b3MakePlaneFromNormalAndPoint( sideNormal, trianglePoints[i] );
pointCount = b3ClipPolygon( output, input, pointCount, clipPlane, i, refPlane );
B3_ASSERT( pointCount <= 2 * B3_MAX_CLIP_POINTS );
B3_SWAP( output, input );
}
if ( pointCount == 0 )
{
*cache = (b3SATCache){ 0 };
return FLT_MAX;
}
pointCount = b3MinInt( pointCount, pointCapacity );
float minSeparation = FLT_MAX;
int finalPointCount = 0;
for ( int i = 0; i < pointCount; ++i )
{
b3ClipVertex* clipPoint = input + i;
minSeparation = b3MinFloat( minSeparation, clipPoint->separation );
if ( enableSpeculative == false && clipPoint->separation > 0.0f )
{
continue;
}
b3Vec3 point = clipPoint->position;
b3LocalManifoldPoint* pt = manifold->points + finalPointCount;
pt->point = point;
pt->separation = clipPoint->separation;
pt->pair = clipPoint->pair;
finalPointCount += 1;
}
float speculativeDistance = enableSpeculative ? B3_SPECULATIVE_DISTANCE : 0.0f;
if ( minSeparation >= speculativeDistance )
{
*cache = (b3SATCache){ 0 };
return minSeparation;
}
manifold->pointCount = finalPointCount;
manifold->normal = refPlane.normal;
manifold->feature = b3_featureTriangleFace;
cache->separation = minSeparation;
cache->type = b3_faceAxisA;
cache->indexA = (uint8_t)query.indexA;
cache->indexB = (uint8_t)query.indexB;
return minSeparation;
}
static void b3CollideTriangleAndHullEdges( b3LocalManifold* manifold, int capacity, b3Vec3 trianglePoint, b3Vec3 triangleEdge,
const b3HullData* hull, b3SeparatingAxis query, b3SATCache* cache )
{
B3_VALIDATE( query.type == b3_edgePairAxis );
B3_VALIDATE( 0 <= query.indexA && query.indexA < 3 );
B3_VALIDATE( 0 <= query.indexB && query.indexB < hull->edgeCount );
B3_VALIDATE( query.separation <= 2.0f * B3_SPECULATIVE_DISTANCE );
b3Vec3 pA = trianglePoint;
b3Vec3 eA = triangleEdge;
const b3HullHalfEdge* edgesB = b3GetHullEdges( hull );
const b3Vec3* pointsB = b3GetHullPoints( hull );
const b3HullHalfEdge* edgeB = edgesB + query.indexB;
const b3HullHalfEdge* twinB = edgesB + edgeB->twin;
b3Vec3 pB = pointsB[edgeB->origin];
b3Vec3 qB = pointsB[twinB->origin];
b3Vec3 eB = b3Sub( qB, pB );
b3SegmentDistanceResult result = b3LineDistance( pA, eA, pB, eB );
if ( capacity == 0 || result.fraction1 < 0.0f || 1.0f < result.fraction1 || result.fraction2 < 0.0f ||
1.0f < result.fraction2 )
{
B3_ASSERT( manifold->pointCount == 0 );
*cache = (b3SATCache){ 0 };
return;
}
float separation = b3Dot( query.normal, b3Sub( pB, pA ) );
B3_VALIDATE( b3AbsFloat( separation - query.separation ) < B3_LINEAR_SLOP );
b3Vec3 point = b3MulSV( 0.5f, b3Add( result.point1, result.point2 ) );
b3LocalManifoldPoint* pt = manifold->points + 0;
pt->point = point;
pt->separation = separation;
pt->pair = b3MakeFeaturePair( b3_featureShapeA, query.indexA, b3_featureShapeB, query.indexB );
cache->separation = separation;
cache->type = b3_edgePairAxis;
cache->indexA = (uint8_t)query.indexA;
cache->indexB = (uint8_t)query.indexB;
manifold->normal = query.normal;
manifold->pointCount = 1;
b3TriangleFeature edgesFeatures[] = { b3_featureEdge1, b3_featureEdge2, b3_featureEdge3 };
manifold->feature = edgesFeatures[query.indexA];
}
b3AtomicInt b3_triangleConvexCalls;
b3AtomicInt b3_triangleCacheHits;
void b3CollideTriangleAndHull( b3LocalManifold* manifold, int capacity, b3Vec3 v1, b3Vec3 v2, b3Vec3 v3, int triangleFlags,
const b3HullData* hullB, b3SATCache* cache, bool enableSpeculative )
{
manifold->pointCount = 0;
manifold->feature = b3_featureNone;
if ( capacity < 4 )
{
return;
}
b3Plane trianglePlane = b3MakePlaneFromPoints( v1, v2, v3 );
float linearSlop = B3_LINEAR_SLOP;
float offset = b3PlaneSeparation( trianglePlane, hullB->center );
if ( cache->type == b3_backsideAxis )
{
if ( b3AbsFloat( cache->separation - offset ) < linearSlop )
{
return;
}
cache->type = b3_invalidAxis;
}
if ( offset < -linearSlop )
{
cache->type = b3_backsideAxis;
cache->separation = offset;
return;
}
b3Vec3 trianglePoints[] = { v1, v2, v3 };
b3Vec3 triangleEdges[] = { b3Sub( v2, v1 ), b3Sub( v3, v2 ), b3Sub( v1, v3 ) };
b3TriangleData triangle = {
.v1 = v1,
.v2 = v2,
.v3 = v3,
.e1 = triangleEdges[0],
.e2 = triangleEdges[1],
.e3 = triangleEdges[2],
.plane = trianglePlane,
.flags = triangleFlags,
};
const b3HullHalfEdge* edges = b3GetHullEdges( hullB );
const b3Plane* hullPlanes = b3GetHullPlanes( hullB );
const b3Vec3* hullPoints = b3GetHullPoints( hullB );
float speculativeDistance = enableSpeculative ? B3_SPECULATIVE_DISTANCE : 0.0f;
cache->hit = 1;
switch ( cache->type )
{
case b3_faceAxisA:
{
B3_ASSERT( cache->indexA == 0 );
int vertexIndex = b3FindHullSupportVertex( hullB, b3Neg( trianglePlane.normal ) );
b3Vec3 support = hullPoints[vertexIndex];
float separation = b3PlaneSeparation( trianglePlane, support );
if ( separation > speculativeDistance )
{
return;
}
b3SeparatingAxis faceQuery;
faceQuery.normal = trianglePlane.normal;
faceQuery.separation = separation;
faceQuery.indexA = cache->indexA;
faceQuery.indexB = vertexIndex;
faceQuery.type = b3_faceAxisA;
b3SATCache localCache = *cache;
float clippedSeparation =
b3CollideTriangleFace( manifold, capacity, &triangle, hullB, faceQuery, &localCache, enableSpeculative );
if ( manifold->pointCount > 0 && b3AbsFloat( cache->separation - clippedSeparation ) < linearSlop )
{
return;
}
manifold->pointCount = 0;
*cache = (b3SATCache){ 0 };
}
break;
case b3_faceAxisB:
{
B3_ASSERT( cache->indexB < hullB->faceCount );
b3Plane plane = hullPlanes[cache->indexB];
int vertexIndex = 0;
float distance = -b3Dot( v1, plane.normal );
for ( int i = 1; i < 3; ++i )
{
float d = -b3Dot( trianglePoints[i], plane.normal );
if ( d > distance )
{
distance = d;
vertexIndex = i;
}
}
b3Vec3 support = trianglePoints[vertexIndex];
float separation = b3PlaneSeparation( plane, support );
if ( separation > speculativeDistance )
{
return;
}
bool isDeep = separation < -2.0f * linearSlop;
if ( isDeep == false )
{
b3SeparatingAxis faceQuery;
faceQuery.normal = b3Neg( plane.normal );
faceQuery.separation = separation;
faceQuery.indexA = vertexIndex;
faceQuery.indexB = cache->indexB;
faceQuery.type = b3_faceAxisB;
b3SATCache localCache = *cache;
float clippedSeparation =
b3CollideHullFace( manifold, capacity, &triangle, hullB, faceQuery, &localCache, enableSpeculative );
if ( manifold->pointCount > 0 && b3AbsFloat( cache->separation - clippedSeparation ) < linearSlop )
{
return;
}
}
manifold->pointCount = 0;
*cache = (b3SATCache){ 0 };
}
break;
case b3_edgePairAxis:
{
B3_ASSERT( cache->indexA < 3 );
int indexA = cache->indexA;
b3Vec3 triPoint = trianglePoints[indexA];
b3Vec3 triEdge = triangleEdges[indexA];
B3_ASSERT( cache->indexB < hullB->edgeCount - 1 );
int indexB = cache->indexB;
const b3HullHalfEdge* edge2 = edges + indexB;
const b3HullHalfEdge* twin2 = edges + indexB + 1;
B3_ASSERT( edge2->twin == indexB + 1 && twin2->twin == indexB );
b3Vec3 hullPoint = hullPoints[edge2->origin];
b3Vec3 hullEdge = b3Sub( hullPoints[twin2->origin], hullPoint );
b3Vec3 hullNormal1 = hullPlanes[edge2->face].normal;
b3Vec3 hullNormal2 = hullPlanes[twin2->face].normal;
float cab = b3Dot( hullNormal1, triEdge );
float dab = b3Dot( hullNormal2, triEdge );
float bcd = b3Dot( trianglePlane.normal, hullEdge );
if ( cab * dab < 0.0f && cab * bcd > 0.0f )
{
float squaredTolerance = 0.005f * 0.005f;
if ( b3MaxFloat( cab * cab, dab * dab ) >= squaredTolerance * b3LengthSquared( triEdge ) )
{
float t = cab / ( cab - dab );
b3Vec3 axis = b3Lerp( hullNormal1, hullNormal2, t );
B3_VALIDATE( b3LengthSquared( axis ) > 1000.0f * FLT_MIN );
axis = b3Normalize( axis );
float separation = b3Dot( axis, b3Sub( triPoint, hullPoint ) );
if ( separation > speculativeDistance )
{
return;
}
if ( b3AbsFloat( cache->separation - separation ) < linearSlop )
{
b3SeparatingAxis edgeQuery;
edgeQuery.normal = b3Neg( axis );
edgeQuery.indexA = indexA;
edgeQuery.indexB = indexB;
edgeQuery.separation = separation;
edgeQuery.type = b3_edgePairAxis;
b3SATCache localCache = *cache;
b3CollideTriangleAndHullEdges( manifold, capacity, triPoint, triEdge, hullB, edgeQuery, &localCache );
if ( manifold->pointCount > 0 )
{
return;
}
}
}
}
*cache = (b3SATCache){ 0 };
}
break;
case b3_manualFaceAxisA:
{
b3SeparatingAxis query = b3QueryTriangleFace( &triangle, hullB );
b3CollideTriangleFace( manifold, capacity, &triangle, hullB, query, cache, enableSpeculative );
return;
}
case b3_manualFaceAxisB:
{
b3SeparatingAxis query = b3QueryHullFace( &triangle, hullB );
b3CollideHullFace( manifold, capacity, &triangle, hullB, query, cache, enableSpeculative );
return;
}
case b3_manualEdgePairAxis:
{
b3SeparatingAxis query = b3QueryTriangleAndHullEdges( &triangle, hullB );
if ( query.indexA != B3_NULL_INDEX )
{
b3Vec3 trianglePoint = trianglePoints[query.indexA];
b3Vec3 triangleEdge = triangleEdges[query.indexA];
b3CollideTriangleAndHullEdges( manifold, capacity, trianglePoint, triangleEdge, hullB, query, cache );
}
return;
}
default:
B3_ASSERT( cache->type == b3_invalidAxis );
break;
}
cache->hit = 0;
b3SeparatingAxis faceQueryA = b3QueryTriangleFace( &triangle, hullB );
if ( faceQueryA.separation > speculativeDistance )
{
cache->separation = faceQueryA.separation;
cache->type = b3_faceAxisA;
cache->indexA = (uint8_t)faceQueryA.indexA;
cache->indexB = (uint8_t)faceQueryA.indexB;
return;
}
b3SeparatingAxis faceQueryB = b3QueryHullFace( &triangle, hullB );
if ( faceQueryB.separation > speculativeDistance )
{
cache->separation = faceQueryB.separation;
cache->type = b3_faceAxisB;
cache->indexA = (uint8_t)faceQueryB.indexA;
cache->indexB = (uint8_t)faceQueryB.indexB;
return;
}
b3SeparatingAxis edgeQuery = b3QueryTriangleAndHullEdges( &triangle, hullB );
if ( edgeQuery.separation > speculativeDistance )
{
cache->separation = edgeQuery.separation;
cache->type = b3_edgePairAxis;
cache->indexA = (uint8_t)edgeQuery.indexA;
cache->indexB = (uint8_t)edgeQuery.indexB;
return;
}
float clipSeparation;
bool pushingDown = b3Dot( faceQueryB.normal, trianglePlane.normal ) < -0.25f;
if ( faceQueryB.separation >= faceQueryA.separation && pushingDown == false )
{
clipSeparation = b3CollideHullFace( manifold, capacity, &triangle, hullB, faceQueryB, cache, enableSpeculative );
}
else
{
clipSeparation = b3CollideTriangleFace( manifold, capacity, &triangle, hullB, faceQueryA, cache, enableSpeculative );
}
if ( edgeQuery.indexA != B3_NULL_INDEX )
{
float maxFaceSeparation = b3MaxFloat( faceQueryA.separation, faceQueryB.separation );
if ( ( manifold->pointCount == 0 && edgeQuery.separation > maxFaceSeparation ) ||
( manifold->pointCount == 1 && edgeQuery.separation > clipSeparation + linearSlop ) )
{
B3_ASSERT( 0 <= edgeQuery.indexA && edgeQuery.indexA < 3 );
b3Vec3 trianglePoint = trianglePoints[edgeQuery.indexA];
b3Vec3 triangleEdge = triangleEdges[edgeQuery.indexA];
manifold->pointCount = 0;
b3CollideTriangleAndHullEdges( manifold, capacity, trianglePoint, triangleEdge, hullB, edgeQuery, cache );
}
}
if ( manifold->pointCount == 0 )
{
b3Vec3 triangleB[] = { v1, v2, v3 };
b3DistanceInput input = { 0 };
input.proxyA = (b3ShapeProxy){
.points = triangleB,
.count = 3,
.radius = 0.0f,
};
input.proxyB = (b3ShapeProxy){ .points = hullPoints, .count = hullB->vertexCount, .radius = 0.0f };
input.transform = b3Transform_identity;
input.useRadii = false;
b3SimplexCache simplexCache = { 0 };
b3DistanceOutput output = b3ShapeDistance( &input, &simplexCache, NULL, 0 );
if ( output.distance > 0.0f )
{
B3_ASSERT( 0 < simplexCache.count && simplexCache.count <= 3 );
manifold->pointCount = 1;
manifold->feature = b3GetTriangleFeature( &simplexCache );
manifold->normal = output.normal;
manifold->points[0].point = output.pointB;
manifold->points[0].separation = output.distance;
manifold->points[0].pair = b3FeaturePair_single;
}
*cache = (b3SATCache){ 0 };
}
}