#include "math_internal.h"
#include "shape.h"
#include "box3d/base.h"
#include "box3d/collision.h"
#include "box3d/constants.h"
b3MassData b3ComputeCapsuleMass( const b3Capsule* shape, float density )
{
b3Vec3 c1 = shape->center1;
b3Vec3 c2 = shape->center2;
float r = shape->radius;
float cylinderHeight = b3Distance( c1, c2 );
float cylinderVolume = B3_PI * r * r * cylinderHeight;
float cylinderMass = cylinderVolume * density;
float sphereVolume = ( 4.0f / 3.0f ) * B3_PI * r * r * r;
float sphereMass = sphereVolume * density;
b3Matrix3 inertia = b3AddMM( b3CylinderInertia( cylinderMass, r, cylinderHeight ), b3SphereInertia( sphereMass, r ) );
float steiner = 0.125f * sphereMass * ( 3.0f * r + 2.0f * cylinderHeight ) * cylinderHeight;
inertia.cx.x += steiner;
inertia.cz.z += steiner;
b3Matrix3 rotation = b3Mat3_identity;
if ( cylinderHeight * cylinderHeight > 1000.0f * FLT_MIN )
{
b3Vec3 direction = b3Normalize( b3Sub( c2, c1 ) );
b3Quat q = b3ComputeQuatBetweenUnitVectors( b3Vec3_axisY, direction );
rotation = b3MakeMatrixFromQuat( q );
}
float mass = sphereMass + cylinderMass;
b3Vec3 center = b3MulSV( 0.5f, b3Add( c1, c2 ) );
b3MassData out;
out.mass = mass;
out.center = center;
out.inertia = b3MulMM( rotation, b3MulMM( inertia, b3Transpose( rotation ) ) );
return out;
}
b3AABB b3ComputeCapsuleAABB( const b3Capsule* shape, b3Transform transform )
{
float r = shape->radius;
b3Vec3 center1 = b3TransformPoint( transform, shape->center1 );
b3Vec3 center2 = b3TransformPoint( transform, shape->center2 );
b3Vec3 extent = { r, r, r };
b3AABB aabb;
aabb.lowerBound = b3Sub( b3Min( center1, center2 ), extent );
aabb.upperBound = b3Add( b3Max( center1, center2 ), extent );
return aabb;
}
b3AABB b3ComputeSweptCapsuleAABB( const b3Capsule* shape, b3Transform xf1, b3Transform xf2 )
{
b3Vec3 r = { shape->radius, shape->radius, shape->radius };
b3Vec3 a = b3TransformPoint( xf1, shape->center1 );
b3Vec3 b = b3TransformPoint( xf1, shape->center2 );
b3Vec3 c = b3TransformPoint( xf2, shape->center1 );
b3Vec3 d = b3TransformPoint( xf2, shape->center2 );
b3AABB aabb = {
.lowerBound = b3Sub( b3Min( b3Min( a, b ), b3Min( c, d ) ), r ),
.upperBound = b3Add( b3Max( b3Max( a, b ), b3Max( c, d ) ), r ),
};
return aabb;
}
bool b3OverlapCapsule( const b3Capsule* shape, b3Transform shapeTransform, const b3ShapeProxy* proxy )
{
b3DistanceInput input;
input.proxyA = (b3ShapeProxy){ &shape->center1, 2, shape->radius };
input.proxyB = *proxy;
input.transform = b3InvMulTransforms( shapeTransform, b3Transform_identity );
input.useRadii = true;
b3SimplexCache cache = { 0 };
b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 );
return output.distance < B3_OVERLAP_SLOP;
}
b3CastOutput b3RayCastCapsule( const b3Capsule* shape, const b3RayCastInput* input )
{
B3_ASSERT( b3IsValidRay( input ) );
b3Vec3 c1 = shape->center1;
b3Vec3 c2 = shape->center2;
float r = shape->radius;
b3CastOutput output = { 0 };
b3Vec3 d = b3Sub( c2, c1 );
float tol = 0.01f * B3_LINEAR_SLOP;
float lengthSquared = b3LengthSquared( d );
if ( lengthSquared < tol * tol )
{
b3Vec3 sphereCenter = b3MulSV( 0.5f, b3Add( shape->center1, shape->center2 ) );
b3Sphere sphere = { sphereCenter, shape->radius };
return b3RayCastSphere( &sphere, input );
}
b3Vec3 s = b3Sub( input->origin, c1 );
float length = sqrtf( lengthSquared );
b3Vec3 axis = b3MulSV( 1.0f / length, d );
float u = b3Dot( s, axis );
b3Vec3 c = b3MulSV( u, axis );
b3Vec3 sc = b3Sub( s, c );
float sc2 = b3LengthSquared( sc );
if ( sc2 < r * r )
{
float uClamped = b3ClampFloat( u, 0.0f, length );
b3Vec3 cp = b3MulSV( uClamped, axis );
b3Vec3 scp = b3Sub( s, cp );
float scp2 = b3LengthSquared( scp );
if ( scp2 < r * r )
{
output.hit = true;
output.point = input->origin;
return output;
}
b3Sphere sphere = {
.center = b3Add( c1, cp ),
.radius = r,
};
return b3RayCastSphere( &sphere, input );
}
b3Vec3 dr = input->translation;
float rayLength;
b3Vec3 rayAxis = b3GetLengthAndNormalize( &rayLength, dr );
if ( rayLength == 0.0f )
{
return output;
}
float v = u + input->maxFraction * b3Dot( dr, axis );
if ( ( u < -r && v < -r ) || ( length + r < u && length + r < v ) )
{
return output;
}
b3Vec3 a1 = axis;
b3Vec3 a2 = rayAxis;
float a12 = b3Dot( a1, a2 );
float tr;
float det = 1.0f - a12 * a12;
if ( det < FLT_EPSILON )
{
b3Vec3 perp = b3MulSub( a2, a12, a1 );
float perp2 = b3LengthSquared( perp );
float beta = b3Dot( sc, perp );
float gamma = sc2 - r * r;
float disc = beta * beta - perp2 * gamma;
if ( beta >= 0.0f || disc < 0.0f )
{
return output;
}
tr = gamma / ( -beta + sqrtf( disc ) );
}
else
{
float invDet = 1.0f / det;
float sa1 = u;
float sa2 = b3Dot( s, a2 );
float t1 = ( sa1 - a12 * sa2 ) * invDet;
float t2 = ( a12 * sa1 - sa2 ) * invDet;
b3Vec3 p1 = b3MulSV( t1, a1 );
b3Vec3 p2 = b3MulAdd( s, t2, a2 );
b3Vec3 g = b3Sub( p2, p1 );
float g2 = b3LengthSquared( g );
if ( g2 > r * r )
{
return output;
}
float h = sqrtf( ( r * r - g2 ) * invDet );
tr = t2 - h;
}
if ( tr < 0.0f || input->maxFraction * rayLength < tr )
{
return output;
}
float tc = u + tr * a12;
if ( tc < 0.0f )
{
b3Sphere sphere = {
.center = c1,
.radius = r,
};
return b3RayCastSphere( &sphere, input );
}
if (length < tc)
{
b3Sphere sphere = {
.center = c2,
.radius = r,
};
return b3RayCastSphere( &sphere, input );
}
b3Vec3 p = b3MulAdd( s, tr, rayAxis );
b3Vec3 normal = b3MulSub( p, tc, axis );
normal = b3Normalize( normal );
output.point = b3Add( c1, p );
output.normal = normal;
output.fraction = b3ClampFloat( tr / rayLength, 0.0f, input->maxFraction );
output.hit = true;
return output;
}
b3CastOutput b3ShapeCastCapsule( const b3Capsule* capsule, const b3ShapeCastInput* input )
{
b3ShapeCastPairInput pairInput;
pairInput.proxyA = (b3ShapeProxy){ &capsule->center1, 2, capsule->radius };
pairInput.proxyB = input->proxy;
pairInput.transform = b3Transform_identity;
pairInput.translationB = input->translation;
pairInput.maxFraction = input->maxFraction;
pairInput.canEncroach = input->canEncroach;
b3CastOutput output = b3ShapeCast( &pairInput );
return output;
}
int b3CollideMoverAndCapsule( b3PlaneResult* result, const b3Capsule* shape, const b3Capsule* mover )
{
float totalRadius = mover->radius + shape->radius;
b3SegmentDistanceResult approach = b3SegmentDistance( shape->center1, shape->center2, mover->center1, mover->center2 );
float distance;
b3Vec3 normal = b3GetLengthAndNormalize( &distance, b3Sub( approach.point2, approach.point1 ) );
if ( distance > totalRadius )
{
return 0;
}
float linearSlop = B3_LINEAR_SLOP;
if ( distance < linearSlop )
{
float moverLength;
b3Vec3 moverAxis = b3GetLengthAndNormalize( &moverLength, b3Sub( mover->center2, mover->center1 ) );
normal = moverLength > linearSlop ? b3Perp( moverAxis ) : b3Vec3_axisY;
distance = 0.0f;
}
b3Plane plane = { normal, totalRadius - distance };
*result = (b3PlaneResult){ plane, approach.point1 };
return 1;
}