Skip to content

Commit

Permalink
Fix frStepWorld() not discarding invalid cache entries
Browse files Browse the repository at this point in the history
  • Loading branch information
jdeokkim committed Sep 28, 2024
1 parent 6bd5c3f commit f178650
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 59 deletions.
2 changes: 1 addition & 1 deletion .clang-format
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2023 Jaedeok Kim <[email protected]>
# Copyright (c) 2021-2024 Jaedeok Kim <[email protected]>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2023 Jaedeok Kim <[email protected]>
# Copyright (c) 2021-2024 Jaedeok Kim <[email protected]>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
Expand Down
6 changes: 3 additions & 3 deletions examples/src/query.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ static void DeinitExample(void);
static void DrawCursorBounds(void);
static frAABB GetCursorBounds(void);

static bool OnHashQuery(frIndexedData arg);
static bool OnHashQuery(frContextNode ctx);

/* Public Functions ======================================================== */

Expand Down Expand Up @@ -210,8 +210,8 @@ static frAABB GetCursorBounds(void) {
};
}

static bool OnHashQuery(frIndexedData arg) {
frSetBodyUserData(bodies[arg.idx], (void *) &secondaryColor);
static bool OnHashQuery(frContextNode ctx) {
frSetBodyUserData(bodies[ctx.id], (void *) &secondaryColor);

return true;
}
25 changes: 13 additions & 12 deletions include/ferox.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ extern "C" {
#define FR_WORLD_DEFAULT_GRAVITY ((frVector2) { .y = 9.8f })

/* Defines the iteration count for the constraint solver. */
#define FR_WORLD_ITERATION_COUNT 10
#define FR_WORLD_ITERATION_COUNT 12

/* Defines the maximum number of objects in a world. */
#define FR_WORLD_MAX_OBJECT_COUNT 2048
Expand All @@ -93,12 +93,6 @@ typedef struct frAABB_ {
float x, y, width, height;
} frAABB;

/* A structure that represents arbitrary data with an index. */
typedef struct frIndexedData_ {
int idx;
void *data;
} frIndexedData;

/*
A structure that represents a collision shape,
which can be attached to a rigid body.
Expand All @@ -111,13 +105,19 @@ typedef struct frBody_ frBody;
/* A structure that represents a simulation container. */
typedef struct frWorld_ frWorld;

/* A structure that represents arbitrary data with an identifier. */
typedef struct frContextNode_ {
int id;
void *data;
} frContextNode;

/* (From 'broad-phase.c') ================================================== */

/* A structure that represents a spatial hash. */
typedef struct frSpatialHash_ frSpatialHash;

/* A callback function type for `frQuerySpatialHash()`. */
typedef bool (*frHashQueryFunc)(frIndexedData arg);
typedef bool (*frHashQueryFunc)(frContextNode ctx);

/* (From 'collision.c') ==================================================== */

Expand All @@ -127,6 +127,7 @@ typedef struct frCollision_ {
struct {
int id;
float depth;
float timestamp;
frVector2 point;
struct {
float normalMass, normalScalar;
Expand Down Expand Up @@ -503,11 +504,11 @@ frRingBuffer *frCreateRingBuffer(size_t length);
/* Releases the memory allocated for `rbf`. */
void frReleaseRingBuffer(frRingBuffer *rbf);

/* Adds a `value` to `rbf`. */
bool frAddValueToRingBuffer(frRingBuffer *rbf, frIndexedData value);
/* Adds a `node` to `rbf`. */
bool frAddNodeToRingBuffer(frRingBuffer *rbf, frContextNode node);

/* Removes a value from `rbf` and stores it to `value`. */
bool frRemoveValueFromRingBuffer(frRingBuffer *rbf, frIndexedData *value);
/* Removes a node from `rbf` and stores it to `node`. */
bool frRemoveNodeFromRingBuffer(frRingBuffer *rbf, frContextNode *node);

/* (From 'world.c') ======================================================== */

Expand Down
2 changes: 1 addition & 1 deletion src/broad-phase.c
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ void frQuerySpatialHash(frSpatialHash *sh,
will be called with the user data pointer `ctx`.
*/
for (int i = 0; i < arrlen(sh->queryResult); i++)
func((frIndexedData) { .idx = sh->queryResult[i], .data = ctx});
func((frContextNode) { .id = sh->queryResult[i], .data = ctx });
}

/* Private Functions ======================================================= */
Expand Down
14 changes: 7 additions & 7 deletions src/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

/* A structure that represents a ring buffer for storing indexed data. */
struct frRingBuffer_ {
frIndexedData *buffer;
frContextNode *buffer;
int head, tail, length;
};

Expand Down Expand Up @@ -61,23 +61,23 @@ void frReleaseRingBuffer(frRingBuffer *rbf) {
if (rbf != NULL) free(rbf->buffer), free(rbf);
}

/* Adds a `value` to `rbf`. */
bool frAddValueToRingBuffer(frRingBuffer *rbf, frIndexedData value) {
/* Adds a `node` to `rbf`. */
bool frAddNodeToRingBuffer(frRingBuffer *rbf, frContextNode node) {
if (rbf == NULL || ((rbf->head + 1) & (rbf->length - 1)) == rbf->tail)
return false;

rbf->buffer[rbf->head] = value;
rbf->buffer[rbf->head] = node;

rbf->head = (rbf->head + 1) & (rbf->length - 1);

return true;
}

/* Removes a value from `rbf` and stores it to `value`. */
bool frRemoveValueFromRingBuffer(frRingBuffer *rbf, frIndexedData *value) {
/* Removes a node from `rbf` and stores it to `node`. */
bool frRemoveNodeFromRingBuffer(frRingBuffer *rbf, frContextNode *node) {
if (rbf == NULL || (rbf->head == rbf->tail)) return false;

if (value != NULL) *value = rbf->buffer[rbf->tail];
if (node != NULL) *node = rbf->buffer[rbf->tail];

rbf->tail = (rbf->tail + 1) & (rbf->length - 1);

Expand Down
70 changes: 44 additions & 26 deletions src/world.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ typedef struct frContactCacheEntry_ {

/* A structure that represents a simulation container. */
struct frWorld_ {
frVector2 gravity;
frBody **bodies;
frRingBuffer *rbf;
frSpatialHash *hash;
frContactCacheEntry *cache;
frCollisionHandler handler;
float accumulator, timestamp;
frCollisionHandler handler;
frVector2 gravity;
};

/*
Expand Down Expand Up @@ -78,13 +78,13 @@ typedef struct frRaycastHashQueryCtx_ {
A callback function for `frQuerySpatialHash()`
that will be called during `frPreStepWorld()`.
*/
static bool frPreStepHashQueryCallback(frIndexedData arg);
static bool frPreStepHashQueryCallback(frContextNode ctx);

/*
A callback function for `frQuerySpatialHash()`
that will be called during `frComputeRaycastForWorld()`.
*/
static bool frRaycastHashQueryCallback(frIndexedData arg);
static bool frRaycastHashQueryCallback(frContextNode ctx);

/* Finds all pairs of bodies in `w` that are colliding. */
static void frPreStepWorld(frWorld *w);
Expand Down Expand Up @@ -143,17 +143,17 @@ bool frAddBodyToWorld(frWorld *w, frBody *b) {
|| arrlen(w->bodies) >= FR_WORLD_MAX_OBJECT_COUNT)
return false;

return frAddValueToRingBuffer(w->rbf,
(frIndexedData) { .idx = FR_OPT_ADD_BODY,
return frAddNodeToRingBuffer(w->rbf,
(frContextNode) { .id = FR_OPT_ADD_BODY,
.data = b });
}

/* Removes a rigid `b`ody from `w`. */
bool frRemoveBodyFromWorld(frWorld *w, frBody *b) {
if (w == NULL || b == NULL) return false;

return frAddValueToRingBuffer(w->rbf,
(frIndexedData) { .idx = FR_OPT_REMOVE_BODY,
return frAddNodeToRingBuffer(w->rbf,
(frContextNode) { .id = FR_OPT_REMOVE_BODY,
.data = b });
}

Expand Down Expand Up @@ -213,6 +213,21 @@ void frStepWorld(frWorld *w, float dt) {
frIntegrateForBodyVelocity(w->bodies[i], dt);
}

int entryCount = hmlen(w->cache);

for (int j = 0; j < entryCount; j++) {
frBodyPair key = w->cache[j].key;

const frCollision *value = &w->cache[j].value;

for (int k = 0; k < value->count; k++)
if (value->contacts[k].timestamp < w->timestamp) {
hmdel(w->cache, key);

break;
}
}

for (int j = 0; j < hmlen(w->cache); j++)
frApplyAccumulatedImpulses(w->cache[j].key.first,
w->cache[j].key.second,
Expand Down Expand Up @@ -290,15 +305,17 @@ void frComputeRaycastForWorld(frWorld *w, frRay ray, frRaycastQueryFunc func) {
A callback function for `frQuerySpatialHash()`
that will be called during `frPreStepWorld()`.
*/
static bool frPreStepHashQueryCallback(frIndexedData arg) {
frPreStepHashQueryCtx *queryCtx = arg.data;
static bool frPreStepHashQueryCallback(frContextNode ctx) {
frPreStepHashQueryCtx *queryCtx = ctx.data;

int firstIndex = queryCtx->bodyIndex, secondIndex = arg.idx;
int firstIndex = queryCtx->bodyIndex, secondIndex = ctx.id;

if (firstIndex >= secondIndex) return false;

frBody *b1 = queryCtx->world->bodies[firstIndex];
frBody *b2 = queryCtx->world->bodies[secondIndex];
frWorld *world = queryCtx->world;

frBody *b1 = world->bodies[firstIndex];
frBody *b2 = world->bodies[secondIndex];

if (frGetBodyInverseMass(b1) + frGetBodyInverseMass(b2) <= 0.0f)
return false;
Expand All @@ -310,18 +327,21 @@ static bool frPreStepHashQueryCallback(frIndexedData arg) {
if (!frComputeCollision(b1, b2, &collision)) {
/*
NOTE: `hmdel()` returns `0` if `key` is not
in `queryCtx->world->cache`!
in `world->cache`!
*/

hmdel(queryCtx->world->cache, key);
hmdel(world->cache, key);

return false;
}

frContactCacheEntry *entry = hmgetp_null(queryCtx->world->cache, key);
frContactCacheEntry *entry = hmgetp_null(world->cache, key);

const frShape *s1 = frGetBodyShape(b1), *s2 = frGetBodyShape(b2);

for (int i = 0; i < collision.count; i++)
collision.contacts[i].timestamp = world->timestamp;

if (entry != NULL) {
collision.friction = entry->value.friction;
collision.restitution = entry->value.restitution;
Expand All @@ -348,12 +368,12 @@ static bool frPreStepHashQueryCallback(frIndexedData arg) {
A callback function for `frQuerySpatialHash()`
that will be called during `frComputeRaycastForWorld()`.
*/
static bool frRaycastHashQueryCallback(frIndexedData arg) {
frRaycastHashQueryCtx *queryCtx = arg.data;
static bool frRaycastHashQueryCallback(frContextNode ctx) {
frRaycastHashQueryCtx *queryCtx = ctx.data;

frRaycastHit raycastHit = { .distance = 0.0f };

if (!frComputeRaycast(queryCtx->world->bodies[arg.idx],
if (!frComputeRaycast(queryCtx->world->bodies[ctx.id],
queryCtx->ray,
&raycastHit))
return false;
Expand All @@ -365,8 +385,6 @@ static bool frRaycastHashQueryCallback(frIndexedData arg) {

/* Finds all pairs of bodies in `w` that are colliding. */
static void frPreStepWorld(frWorld *w) {
/* TODO: ... */

for (int i = 0; i < arrlen(w->bodies); i++)
frInsertIntoSpatialHash(w->hash, frGetBodyAABB(w->bodies[i]), i);

Expand All @@ -383,18 +401,18 @@ static void frPreStepWorld(frWorld *w) {
then clears the spatial hash of `w`.
*/
static void frPostStepWorld(frWorld *w) {
frIndexedData value = { .idx = FR_OPT_UNKNOWN };
frContextNode node = { .id = FR_OPT_UNKNOWN };

while (frRemoveValueFromRingBuffer(w->rbf, &value)) {
switch (value.idx) {
while (frRemoveNodeFromRingBuffer(w->rbf, &node)) {
switch (node.id) {
case FR_OPT_ADD_BODY:
arrput(w->bodies, value.data);
arrput(w->bodies, node.data);

break;

case FR_OPT_REMOVE_BODY:
for (int i = 0; i < arrlen(w->bodies); i++)
if (w->bodies[i] == value.data) {
if (w->bodies[i] == node.data) {
arrdelswap(w->bodies, i);

break;
Expand Down
2 changes: 1 addition & 1 deletion tests/Makefile.mingw
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ CC = x86_64-w64-mingw32-gcc
# =============================================================================

all clean rebuild:
@${MAKE} TARGETS=${TARGETS} CC=${CC} $@
@${MAKE} TARGET_SUFFIX=${TARGET_SUFFIX} CC=${CC} $@

# =============================================================================
14 changes: 7 additions & 7 deletions tests/src/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,27 +45,27 @@ TEST utRingBufferOps(void) {
frRingBuffer *rbf = frCreateRingBuffer(RING_BUFFER_LENGTH);

{
frIndexedData value = { .idx = 0 };
frContextNode node = { .id = 0 };

for (int i = 0; i < RING_BUFFER_LENGTH; i++) {
value.idx = i;
node.id = i;

bool result = (i < (RING_BUFFER_LENGTH - 1));

ASSERT_EQ(result, frAddValueToRingBuffer(rbf, value));
ASSERT_EQ(result, frAddNodeToRingBuffer(rbf, node));
}

ASSERT_EQ(false, frAddValueToRingBuffer(rbf, value));
ASSERT_EQ(false, frAddNodeToRingBuffer(rbf, node));

for (int i = 0; i < RING_BUFFER_LENGTH; i++) {
bool result = (i < (RING_BUFFER_LENGTH - 1));

ASSERT_EQ(result, frRemoveValueFromRingBuffer(rbf, &value));
ASSERT_EQ(result, frRemoveNodeFromRingBuffer(rbf, &node));

if (result == true) ASSERT_EQ(i, value.idx);
if (result == true) ASSERT_EQ(i, node.id);
}

ASSERT_EQ(false, frRemoveValueFromRingBuffer(rbf, NULL));
ASSERT_EQ(false, frRemoveNodeFromRingBuffer(rbf, NULL));
}

frReleaseRingBuffer(rbf);
Expand Down

0 comments on commit f178650

Please sign in to comment.