133 lines
4.4 KiB
C
133 lines
4.4 KiB
C
|
|
/**
|
||
|
|
* @file collision.c
|
||
|
|
* @brief The collision narrowphase, and the only place libccd is visible.
|
||
|
|
*
|
||
|
|
* Nothing else in the tree includes `<ccd/ccd.h>`. The translation between
|
||
|
|
* libakgl's shapes and libccd's support-function protocol is `static` here, so
|
||
|
|
* a change of narrowphase library is a change to one file and no header.
|
||
|
|
*
|
||
|
|
* At present this holds only the arena's end-to-end check; the shapes, the
|
||
|
|
* contact and the resolver arrive with the rest of the collision work.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <akerror.h>
|
||
|
|
|
||
|
|
#include <akgl/collision_arena.h>
|
||
|
|
#include <akgl/error.h>
|
||
|
|
#include <akgl/types.h>
|
||
|
|
|
||
|
|
#include <ccd/ccd.h>
|
||
|
|
#include <ccd/quat.h>
|
||
|
|
#include <ccd/vec3.h>
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Ceiling on narrowphase iterations.
|
||
|
|
*
|
||
|
|
* libccd's own default, set by `CCD_INIT`, is `(unsigned long)-1` -- an
|
||
|
|
* unbounded loop inside a frame. A pair that reaches this cap is reported as
|
||
|
|
* not colliding, which is the same answer a missed collision gives, but it
|
||
|
|
* terminates. Bounding the iterations also bounds what the arena can be asked
|
||
|
|
* for, since the polytope grows with them.
|
||
|
|
*/
|
||
|
|
#define AKGL_COLLISION_MAX_ITERATIONS 100
|
||
|
|
|
||
|
|
/** @brief An axis-aligned box, in the shape libccd's callbacks want. */
|
||
|
|
typedef struct {
|
||
|
|
ccd_vec3_t pos; /**< World centre. */
|
||
|
|
ccd_vec3_t half; /**< Half-extents on each axis. */
|
||
|
|
} collision_box;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Furthest point of a box in a given direction.
|
||
|
|
*
|
||
|
|
* The support function *is* the shape as far as libccd is concerned: hand it a
|
||
|
|
* direction, get back the extreme point. For an axis-aligned box that is the
|
||
|
|
* corner in the direction's octant, which is three sign tests.
|
||
|
|
*
|
||
|
|
* There is no rotation here, and none anywhere in this library yet -- util.h
|
||
|
|
* says so and both example games depend on it. Adding it means rotating @p dir
|
||
|
|
* into the box's local frame at the top of this function and rotating the
|
||
|
|
* result back, and nothing else in the narrowphase changes. See TODO.md,
|
||
|
|
* "Actor rotation".
|
||
|
|
*/
|
||
|
|
static void collision_box_support(const void *obj, const ccd_vec3_t *dir, ccd_vec3_t *dest)
|
||
|
|
{
|
||
|
|
const collision_box *box = (const collision_box *)obj;
|
||
|
|
|
||
|
|
ccdVec3Set(dest,
|
||
|
|
ccdSign(ccdVec3X(dir)) * ccdVec3X(&box->half),
|
||
|
|
ccdSign(ccdVec3Y(dir)) * ccdVec3Y(&box->half),
|
||
|
|
ccdSign(ccdVec3Z(dir)) * ccdVec3Z(&box->half));
|
||
|
|
ccdVec3Add(dest, &box->pos);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Centre of a box.
|
||
|
|
*
|
||
|
|
* MPR requires this and GJK does not: `ccdMPRPenetration` calls `center1` and
|
||
|
|
* `center2` unconditionally, so leaving either `NULL` is a null dereference on
|
||
|
|
* the first contact rather than a degraded answer.
|
||
|
|
*/
|
||
|
|
static void collision_box_center(const void *obj, ccd_vec3_t *dest)
|
||
|
|
{
|
||
|
|
const collision_box *box = (const collision_box *)obj;
|
||
|
|
|
||
|
|
ccdVec3Copy(dest, &box->pos);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akgl_collision_arena_selftest(float32_t separation, bool *hit, float32_t *depth)
|
||
|
|
{
|
||
|
|
collision_box a;
|
||
|
|
collision_box b;
|
||
|
|
ccd_t ccd;
|
||
|
|
ccd_real_t ccddepth = 0.0;
|
||
|
|
ccd_vec3_t dir;
|
||
|
|
ccd_vec3_t pos;
|
||
|
|
int result = 0;
|
||
|
|
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
FAIL_ZERO_RETURN(errctx, hit, AKERR_NULLPOINTER, "NULL hit flag reference");
|
||
|
|
FAIL_ZERO_RETURN(errctx, depth, AKERR_NULLPOINTER, "NULL depth reference");
|
||
|
|
|
||
|
|
CCD_INIT(&ccd);
|
||
|
|
ccd.support1 = collision_box_support;
|
||
|
|
ccd.support2 = collision_box_support;
|
||
|
|
ccd.center1 = collision_box_center;
|
||
|
|
ccd.center2 = collision_box_center;
|
||
|
|
ccd.max_iterations = AKGL_COLLISION_MAX_ITERATIONS;
|
||
|
|
|
||
|
|
ccdVec3Set(&a.pos, 0.0, 0.0, 0.0);
|
||
|
|
ccdVec3Set(&a.half, 1.0, 1.0, 1.0);
|
||
|
|
ccdVec3Set(&b.pos, (ccd_real_t)separation, 0.0, 0.0);
|
||
|
|
ccdVec3Set(&b.half, 1.0, 1.0, 1.0);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The arena's lifetime is one query. Resetting on entry rather than on exit
|
||
|
|
* means a query that returns early still leaves the arena clean for the
|
||
|
|
* next one.
|
||
|
|
*/
|
||
|
|
akgl_ccd_arena_reset();
|
||
|
|
|
||
|
|
/*
|
||
|
|
* ccdGJKPenetration and not ccdMPRPenetration, deliberately. This is the
|
||
|
|
* EPA path, and EPA is the half of libccd that allocates -- so it is the
|
||
|
|
* half that proves the arena is wired up. A narrowphase on the frame path
|
||
|
|
* would prefer MPR, which allocates nothing at all.
|
||
|
|
*/
|
||
|
|
result = ccdGJKPenetration(&a, &b, &ccd, &ccddepth, &dir, &pos);
|
||
|
|
|
||
|
|
if ( result == -2 ) {
|
||
|
|
FAIL_RETURN(
|
||
|
|
errctx,
|
||
|
|
AKGL_ERR_COLLISION,
|
||
|
|
"Collision arena exhausted at %zu of %d bytes; raise AKGL_CCD_ARENA_BYTES",
|
||
|
|
akgl_ccd_arena_highwater(),
|
||
|
|
AKGL_CCD_ARENA_BYTES
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
*hit = (result == 0);
|
||
|
|
*depth = (float32_t)ccddepth;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|