Stage BSP partitioning written. Changes to actors to keep track of their BSP node. Actors traverse the BSP as they move. Miscellanious error code def/usage updates. WIP

This commit is contained in:
2026-06-22 08:18:15 -04:00
parent f4728bf19d
commit 71de95822c
12 changed files with 713 additions and 61 deletions

View File

@@ -277,7 +277,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
controlmap->jsid = jsid;
controlmap->target = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, actorname, NULL);
FAIL_ZERO_BREAK(errctx, controlmap->target, AKERR_REGISTRY, "Actor %s not found in registry", actorname);
FAIL_ZERO_BREAK(errctx, controlmap->target, AKGL_ERR_REGISTRY, "Actor %s not found in registry", actorname);
// ---- KEYBOARD CONTROLS ----

View File

@@ -50,6 +50,12 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
int i = 0;
PREPARE_ERROR(e);
akerr_name_for_status(AKGL_ERR_SDL, "SDL Error");
akerr_name_for_status(AKGL_ERR_LOGICINTERRUPT, "AKGL Logic Interrupt");
akerr_name_for_status(AKGL_ERR_REGISTRY, "AKGL Registry Error");
akerr_name_for_status(AKGL_ERR_OOHEAP, "AKGL Heap Capacity");
strncpy((char *)&game.libversion, AKGL_VERSION, 32);
game.gameStartTime = SDL_GetTicksNS();
game.lastIterTime = game.gameStartTime;
@@ -445,7 +451,8 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags)
PASS(e, actor->updatefunc(actor));
}
}
PASS(e, physics->simulate(physics, NULL));
PASS(e, physics->simulate(physics, NULL));
PASS(e, renderer->draw_world(renderer, NULL));
PASS(e, akgl_game_state_unlock());
SUCCEED_RETURN(e);

View File

@@ -1,3 +1,5 @@
/** @file */
#include <stdlib.h>
#include <akerror.h>
@@ -15,8 +17,10 @@ akgl_SpriteSheet HEAP_SPRITESHEET[AKGL_MAX_HEAP_SPRITESHEET];
akgl_Character HEAP_CHARACTER[AKGL_MAX_HEAP_CHARACTER];
akgl_String HEAP_STRING[AKGL_MAX_HEAP_STRING];
aksl_ListNode HEAP_LIST[AKGL_MAX_HEAP_LIST];
aksl_TreeNode HEAP_TREE[AKGL_MAX_HEAP_TREE];
aksl_ListNode HEAP_TREE_LEAVES[AKGL_MAX_HEAP_TREE];
int AKGL_LIST_SENTINEL = 65535;
void *AKGL_LIST_SENTINEL = (void *)1; /** Sentinel value used for aksl_ListNode objects to determine if they are available */
akerr_ErrorContext *akgl_heap_init()
{
@@ -44,7 +48,17 @@ akerr_ErrorContext *akgl_heap_init_list(void)
PREPARE_ERROR(e);
for ( int i = 0; i < AKGL_MAX_HEAP_LIST; i++) {
memset(&HEAP_LIST[i], 0x00, sizeof(aksl_ListNode));
HEAP_LIST[i].data = (void *)&AKGL_LIST_SENTINEL;
HEAP_LIST[i].data = AKGL_LIST_SENTINEL;
}
SUCCEED_RETURN(e);
}
akerr_ErrorContext *akgl_heap_init_tree(void)
{
PREPARE_ERROR(e);
for ( int i = 0; i < AKGL_MAX_HEAP_TREE; i++) {
memset(&HEAP_TREE[i], 0x00, sizeof(aksl_TreeNode));
HEAP_TREE[i].leaf = AKGL_LIST_SENTINEL;
}
SUCCEED_RETURN(e);
}
@@ -58,20 +72,60 @@ akerr_ErrorContext *akgl_heap_init_actor(void)
SUCCEED_RETURN(e);
}
/**
* @brief Return the next aksl_ListNode from the heap
*
* @param[out] dest The pointer that will hold the procured aksl_ListNode
*
* @throws AKGL_ERR_OOHEAP There are no available nodes in HEAP_LIST
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_next_list(aksl_ListNode **dest)
{
PREPARE_ERROR(errctx);
for (int i = 0; i < AKGL_MAX_HEAP_LIST; i++ ) {
if ( HEAP_LIST[i].data != &AKGL_LIST_SENTINEL ) {
if ( HEAP_LIST[i].data != AKGL_LIST_SENTINEL ) {
continue;
}
*dest = &HEAP_LIST[i];
HEAP_LIST[i].data = NULL;
SUCCEED_RETURN(errctx);
}
FAIL_RETURN(errctx, AKERR_HEAP, "Unable to find unused list on the heap");
FAIL_RETURN(errctx, AKGL_ERR_OOHEAP, "Unable to find unused list on the heap");
}
/**
* @brief Return the next aksl_TreeNode from the heap
*
* @param[out] dest The pointer that will hold the procured tree node
*
* @throws AKGL_ERR_OOHEAP There are no available nodes in HEAP_TREE
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_next_tree(aksl_TreeNode **dest)
{
PREPARE_ERROR(errctx);
for (int i = 0; i < AKGL_MAX_HEAP_TREE; i++ ) {
if ( HEAP_TREE[i].leaf != &AKGL_LIST_SENTINEL ) {
continue;
}
*dest = &HEAP_TREE[i];
HEAP_TREE[i].leaf = &HEAP_TREE_LEAVES[i];
SUCCEED_RETURN(errctx);
}
FAIL_RETURN(errctx, AKGL_ERR_OOHEAP, "Unable to find unused tree on the heap");
}
/**
* @brief Release an aksl_ListNode that came from HEAP_TREE to make it available again
*
* @param[in] tree The aksl_ListNode to release.
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_release_list(aksl_ListNode *list)
{
PREPARE_ERROR(e);
@@ -82,6 +136,76 @@ akerr_ErrorContext *akgl_heap_release_list(aksl_ListNode *list)
SUCCEED_RETURN(e);
}
/**
* @brief Release an aksl_TreeNode that came from HEAP_TREE to make it available again
*
* @param[in] tree The aksl_TreeNode to release. Ensure the leaf on this node has been released and the leaf is set to NULL.
*
* @throws AKERR_VALUE The aksl_TreeNode contains a leaf that has not been released.
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_release_tree(aksl_TreeNode *tree)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, tree, AKERR_NULLPOINTER, "tree");
tree->left = NULL;
tree->right = NULL;
if ( tree->leaf != NULL && tree->leaf->actors != NULL ) {
FAIL_RETURN(e, AKERR_VALUE, "Can't release tree node %p until its leaf actors %p is released and leaf->actors == NULL", tree, tree->leaf->actors);
} else if ( tree->leaf != NULL ) {
memset((void *)tree->leaf, 0x00, sizeof(akgl_BSPLeaf));
}
tree->leaf = AKGL_LIST_SENTINEL;
SUCCEED_RETURN(e);
}
/**
* @brief Iterator callback which releases a given list item back to the heap
*
* @param[in] ptr The aksl_ListNode to free
* @param[in] data Context data that was passed to aksl_list_iterate
*
* @throws AKERR_VALUE The aksl_TreeNode contains a leaf that has not been released.
* @return akerr_ErrorContext*
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_iter_list_release(aksl_ListNode *ptr, void *data)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, ptr, AKERR_NULLPOINTER, "ptr)");
PASS(e, akgl_heap_release_list(ptr));
SUCCEED_RETURN(e);
}
/**
* @brief Iterator callback which releases a given tree node back to the heap. Calls akgl_heap_release_list on the tree leaf as well.
*
* @param[in] ptr The aksl_TreeNode to free
* @param[in] data Context data that was passed to aksl_tree_iterate
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_iter_tree_release(aksl_TreeNode *ptr, void *data)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, ptr, AKERR_NULLPOINTER, "ptr");
if ( ptr->leaf != AKGL_LIST_SENTINEL && ptr->leaf != NULL ) {
PASS(e, akgl_heap_release_list(ptr->leaf));
ptr->leaf = NULL;
}
PASS(e, akgl_heap_release_tree(ptr));
SUCCEED_RETURN(e);
}
/**
* @brief Return the next actor from the heap
*
* @param[out] dest The pointer that will hold the procured actor
*
* @throws AKGL_ERR_OOHEAP There are no available actors in HEAP_ACTOR
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_next_actor(akgl_Actor **dest)
{
PREPARE_ERROR(errctx);
@@ -92,9 +216,18 @@ akerr_ErrorContext *akgl_heap_next_actor(akgl_Actor **dest)
*dest = &HEAP_ACTOR[i];
SUCCEED_RETURN(errctx);
}
FAIL_RETURN(errctx, AKERR_HEAP, "Unable to find unused actor on the heap");
FAIL_RETURN(errctx, AKGL_ERR_OOHEAP, "Unable to find unused actor on the heap");
}
/**
* @brief Return the next akgl_Sprite from the heap
*
* @param[out] dest The pointer that will hold the procured sprite
*
* @throws AKGL_ERR_OOHEAP There are no available actors in HEAP_SPRITE
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_next_sprite(akgl_Sprite **dest)
{
PREPARE_ERROR(errctx);
@@ -105,9 +238,17 @@ akerr_ErrorContext *akgl_heap_next_sprite(akgl_Sprite **dest)
*dest = &HEAP_SPRITE[i];
SUCCEED_RETURN(errctx);
}
FAIL_RETURN(errctx, AKERR_HEAP, "Unable to find unused sprite on the heap");
FAIL_RETURN(errctx, AKGL_ERR_OOHEAP, "Unable to find unused sprite on the heap");
}
/**
* @brief Return the next akgl_SpriteSheet from the heap
*
* @param[out] dest The pointer that will hold the procured spritesheet
*
* @throws AKGL_ERR_OOHEAP There are no available elements in HEAP_SPRITESHEET
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_next_spritesheet(akgl_SpriteSheet **dest)
{
PREPARE_ERROR(errctx);
@@ -118,9 +259,18 @@ akerr_ErrorContext *akgl_heap_next_spritesheet(akgl_SpriteSheet **dest)
*dest = &HEAP_SPRITESHEET[i];
SUCCEED_RETURN(errctx);
}
FAIL_RETURN(errctx, AKERR_HEAP, "Unable to find unused spritesheet on the heap");
FAIL_RETURN(errctx, AKGL_ERR_OOHEAP, "Unable to find unused spritesheet on the heap");
}
/**
* @brief Return the next akgl_Character from the heap
*
* @param[out] dest The pointer that will hold the procured character
*
* @throws AKGL_ERR_OOHEAP There are no available elements in HEAP_CHARACTER
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_next_character(akgl_Character **dest)
{
PREPARE_ERROR(errctx);
@@ -131,9 +281,17 @@ akerr_ErrorContext *akgl_heap_next_character(akgl_Character **dest)
*dest = &HEAP_CHARACTER[i];
SUCCEED_RETURN(errctx);
}
FAIL_RETURN(errctx, AKERR_HEAP, "Unable to find unused character on the heap");
FAIL_RETURN(errctx, AKGL_ERR_OOHEAP, "Unable to find unused character on the heap");
}
/**
* @brief Return the next akgl_String from the heap
*
* @param[out] dest The pointer that will hold the procured string
*
* @throws AKGL_ERR_OOHEAP There are no available elements in HEAP_STRING
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_next_string(akgl_String **dest)
{
PREPARE_ERROR(errctx);
@@ -145,9 +303,16 @@ akerr_ErrorContext *akgl_heap_next_string(akgl_String **dest)
HEAP_STRING[i].refcount += 1;
SUCCEED_RETURN(errctx);
}
FAIL_RETURN(errctx, AKERR_HEAP, "Unable to find unused string on the heap");
FAIL_RETURN(errctx, AKGL_ERR_OOHEAP, "Unable to find unused string on the heap");
}
/**
* @brief Release an akgl_Actor from the heap to be used again
*
* @param[in] ptr The object that will be released
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_release_actor(akgl_Actor *ptr)
{
int i = 0;
@@ -168,23 +333,37 @@ akerr_ErrorContext *akgl_heap_release_actor(akgl_Actor *ptr)
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akgl_heap_release_character(akgl_Character *basechar)
/**
* @brief Release an akgl_Character from the heap to be used again
*
* @param[in] ptr The object that will be released
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_release_character(akgl_Character *ptr)
{
PREPARE_ERROR(errctx);
akgl_Iterator opflags;
FAIL_ZERO_RETURN(errctx, basechar, AKERR_NULLPOINTER, "NULL character reference");
FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "NULL character reference");
AKGL_BITMASK_CLEAR(opflags.flags);
if ( basechar->refcount > 0 ) {
basechar->refcount -= 1;
if ( ptr->refcount > 0 ) {
ptr->refcount -= 1;
}
if ( basechar->refcount == 0 ) {
SDL_ClearProperty(AKGL_REGISTRY_CHARACTER, (char *)&basechar->name);
memset(basechar, 0x00, sizeof(akgl_Character));
if ( ptr->refcount == 0 ) {
SDL_ClearProperty(AKGL_REGISTRY_CHARACTER, (char *)&ptr->name);
memset(ptr, 0x00, sizeof(akgl_Character));
}
SUCCEED_RETURN(errctx);
}
/**
* @brief Release an akgl_Sprite from the heap to be used again
*
* @param[in] ptr The object that will be released
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_release_sprite(akgl_Sprite *ptr)
{
PREPARE_ERROR(errctx);
@@ -199,6 +378,13 @@ akerr_ErrorContext *akgl_heap_release_sprite(akgl_Sprite *ptr)
SUCCEED_RETURN(errctx);
}
/**
* @brief Release an akgl_SpriteSheet from the heap to be used again
*
* @param[in] ptr The object that will be released
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_release_spritesheet(akgl_SpriteSheet *ptr)
{
PREPARE_ERROR(errctx);
@@ -217,6 +403,13 @@ akerr_ErrorContext *akgl_heap_release_spritesheet(akgl_SpriteSheet *ptr)
SUCCEED_RETURN(errctx);
}
/**
* @brief Release an akgl_String from the heap to be used again
*
* @param[in] ptr The object that will be released
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext *akgl_heap_release_string(akgl_String *ptr)
{
PREPARE_ERROR(errctx);

View File

@@ -64,10 +64,24 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_gravity(akgl_PhysicsBacke
SUCCEED_RETURN(e);
}
/**
* @brief Test two actors for collision
*
* @param[in] self The physics simulation to use
* @param[in] a1 actor 1
* @param[in] a2 actor 2
*
* @throws AKERR_NULLPOINTER on null pointer input
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_collide(akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
FAIL_ZERO_RETURN(e, a1, AKERR_NULLPOINTER, "a1");
FAIL_ZERO_RETURN(e, a2, AKERR_NULLPOINTER, "a2");
FAIL_RETURN(e, AKERR_API, "Not implemented");
SUCCEED_RETURN(e);
}
@@ -79,7 +93,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_move(struct akgl_PhysicsB
FAIL_ZERO_RETURN(e, actor, AKERR_NULLPOINTER, "actor");
actor->x += actor->vx * dt;
actor->y += actor->vy * dt;
actor->z += actor->vz * dt;
actor->z += actor->vz * dt;
SUCCEED_RETURN(e);
}
@@ -116,6 +130,17 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_arcade(akgl_PhysicsBackend
SUCCEED_RETURN(e);
}
/**
* @brief Perform arcade physics simulation on all alive actors
*
* @param[in] self Physics simulator object
* @param[in] opflags Iteration operation flags (or NULL)
*
* @throws AKERR_NULLPOINTER on null pointer input
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *self, akgl_Iterator *opflags)
{
PREPARE_ERROR(e);
@@ -189,34 +214,47 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *se
}
ATTEMPT {
CATCH(e, actor->movementlogicfunc(actor, dt));
PASS(e, self->gravity(self, actor, dt));
// Counteract velocity with atmospheric drag
if ( self->drag_x != 0 ) {
actor->ex -= actor->ex * self->drag_x * dt;
}
if ( self->drag_y != 0 ) {
actor->ey -= actor->ey * self->drag_y * dt;
}
if ( self->drag_z != 0 ) {
actor->ez -= actor->ez * self->drag_z * dt;
}
actor->vx = actor->ex + actor->tx;
actor->vy = actor->ey + actor->ty;
actor->vz = actor->ez + actor->tz;
PASS(e, self->move(self, actor, dt));
} CLEANUP {
} PROCESS(e) {
} HANDLE(e, AKGL_ERR_LOGICINTERRUPT) {
// noop
} FINISH(e, true);
PASS(e, self->gravity(self, actor, dt));
// Counteract velocity with atmospheric drag
if ( self->drag_x != 0 ) {
actor->ex -= actor->ex * self->drag_x * dt;
}
if ( self->drag_y != 0 ) {
actor->ey -= actor->ey * self->drag_y * dt;
}
if ( self->drag_z != 0 ) {
actor->ez -= actor->ez * self->drag_z * dt;
}
actor->vx = actor->ex + actor->tx;
actor->vy = actor->ey + actor->ty;
actor->vz = actor->ez + actor->tz;
PASS(e, self->move(self, actor, dt));
PASS(e, stage->partition_actor(stage, actor));
}
self->gravity_time = curtime;
SUCCEED_RETURN(e);
}
/**
* @brief Initialize a physics backend according to a string type
*
* @param[out] self The physics backend to initialize
* @param[in] type The type to initialize
*
* @throws AKERR_NULLPOINTER on null pointer input
* @throws AKERR_KEY on invalid type
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_factory(akgl_PhysicsBackend *self, akgl_String *type)
{
uint32_t hashval;

349
src/stage.c Normal file
View File

@@ -0,0 +1,349 @@
#include <SDL3/SDL.h>
#include <akgl/stage.h>
#include <akgl/actor.h>
#include <akgl/heap.h>
#include <akgl/game.h>
/**
* @brief Initialize a 2D stage
*
* @param[in] self akgl_Stage * to initialize
*
* @throw AKERR_NULLPOINTER on null input pointers
* @return akerr_ErrorContext
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_stage_2d(akgl_Stage *self)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
memset((void *)self, 0x00, sizeof(akgl_Stage));
self->partition = akgl_stage_2d_partition;
self->partition_actor = akgl_stage_2d_bsp_move;
SUCCEED_RETURN(e);
}
/**
* @brief Flip the current partitioning direction
*
* @param[in] One of AKGL_STAGE_PARTITION_HORIZONTAL or AKGL_STAGE_PARTITION_VERTICAL
* @param[out] dest Location to place the modified direction
*
* @throws AKERR_NULLPOINTER on null pointer inputs
* @throws AKERR_VALUE on invalid direction
* @return akerr_ErrorContext*
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_stage_2d_partition_switchdirection(uint8_t direction, uint8_t *dest)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dest");
if ( direction == AKGL_STAGE_PARTITION_HORIZONTAL ) {
*dest = AKGL_STAGE_PARTITION_VERTICAL;
} else if ( direction == AKGL_STAGE_PARTITION_VERTICAL ) {
*dest = AKGL_STAGE_PARTITION_HORIZONTAL;
} else {
FAIL_RETURN(e, AKERR_VALUE, "Unknown partition direction %d", direction);
}
}
/**
* @brief Iterator function that sorts an actor into one of three lists based on colliding rectangles
*
* This function tests which of two rectangles ("left" and "right") in the context collide with the actor.
* The actor is sorted into the appropriate linked list for each given rectangle.
* If the actor collides with neither rectangle, it is pushed onto the parent list.
* Note that the existing node is not pushed - a new node is taken from the heap and pushed.
* Otherwise we would have to pop the actor out of whatever list it was in, which would
* modify the list during iteration.
*
* @param[in] node A list node containing an akgl_Actor in ->data
* @param[in] data A pointer to akgl_BSPContext containing left, right, and parent SDL_FRect structures
*
* @throws AKERR_NULLPOINTER on null pointer inputs
* @return akerr_ErrorContext*
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_stage_2d_bsp_actoriter(aksl_ListNode *node, void *data)
{
bool e1intersect;
bool e2intersect;
akgl_BSPContext *context = NULL;
akgl_Actor *actor = NULL;
aksl_ListNode *dest = NULL;
aksl_ListNode *newnode = NULL;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
FAIL_ZERO_RETURN(e, node->data, AKERR_NULLPOINTER, "node->data");
FAIL_ZERO_RETURN(e, context->left, AKERR_NULLPOINTER, "left");
FAIL_ZERO_RETURN(e, context->right, AKERR_NULLPOINTER, "right");
FAIL_ZERO_RETURN(e, context->parent, AKERR_NULLPOINTER, "parent");
FAIL_ZERO_RETURN(e, context->left->leaf.actors, AKERR_NULLPOINTER, "left->leaf");
FAIL_ZERO_RETURN(e, context->right->leaf.actors, AKERR_NULLPOINTER, "right->leaf");
FAIL_ZERO_RETURN(e, context->parent->leaf.actors, AKERR_NULLPOINTER, "parent->leaf");
context = (akgl_BSPContext *)data;
actor = (akgl_Actor *)node->data;
e1intersect = SDL_HasRectIntersectionFloat(&context->left.extent, &actor->bbox);
e2intersect = SDL_HasRectIntersectionFloat(&context->right.extent, &actor->bbox);
if ( e1intersect && e2intersect ) {
// Actors who cross left/right boundaries get left in the context of their parent, so both
// child spaces test them for collision
dest = context->parent;
} else if ( e1intersect ) {
dest = context->left;
} else if ( e2intersect ) {
dest = context->right;
}
if ( dest != NULL ) {
PASS(e, akgl_heap_next_list(&newnode));
newnode->data = node->data;
PASS(e, aksl_list_push(dest->leaf.actors, newnode));
actor->bsphome = dest;
actor->bsplistnode = newnode;
}
SUCCEED_RETURN(e);
}
/**
* @brief Recursive function used to divide the stage into a BSP tree of actor near neighbors
*
* This is a recursive function that builds a balanced tree until the tree is maxdepth levels deep.
* It creates a list of all actors which fit within the extents of the root node,
* then successively divides that actor list down into the extents of the child nodes of the balanced tree,
* until the maximum recursion depth has been achieved.
*
* @param[in] root The root node of the current level of the tree
* @param[in] actors The list of all actors that are within the root node's extents
* @param[in] extents The {x, y, w, h} extents of this level of the tree
* @param[in] direction The AKL_STAGE_PARTITION_* direction in which the extent is being divided
* @param[in] depth The depth of the current root node. Callers should provide 1 here.
* @param[in] maxdepth The maximum depth to which we should recurse
*
* @throws AKERR_VALUE Invalid direction provided
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_stage_2d_bsp(aksl_TreeNode *root, aksl_ListNode *actors, SDL_FRect extents, uint8_t direction, uint8_t depth, uint8_t maxdepth)
{
akgl_BSPContext context;
// Until we have reached maxdepth levels of depth
PREPARE_ERROR(e);
if ( depth > maxdepth ) {
SUCCEED_RETURN(e);
}
// - Get a pointer to the left and right nodes of the tree representing the halves
// - Divide the current working space into two equal halves
// - Construct a SDL_FRect representing the two equal halves
// (the top half of a vertical division is "left", bottom half is "right")
if ( root->left == NULL ) {
PASS(e, akgl_heap_next_tree((aksl_TreeNode **)&root->left));
PASS(e, akgl_heap_next_list((aksl_ListNode **)&root->left->leaf->actors));
context.left = root->left;
root->left->parent = root;
if ( direction == AKGL_STAGE_PARTITION_HORIZONTAL ) {
context.left.extent.x = extents.x;
context.left.extent.y = extents.y;
context.left.extent.w = extents.w;
context.left.extent.h = (extents.h / 2);
} else if ( direction == AKGL_STAGE_PARTITION_VERTICAL ) {
context.left.extent.x = extents.x;
context.left.extent.y = extents.y;
context.left.extent.w = (extents.w / 2);
context.left.extent.h = extents.h;
} else {
FAIL_RETURN(e, AKERR_VALUE, "Invalid partition direction %d", direction);
}
}
if ( root->right == NULL ) {
PASS(e, akgl_heap_next_tree((aksl_TreeNode **)&root->right));
PASS(e, akgl_heap_next_list((aksl_ListNode **)&root->right->leaf->actors));
context.right = root->right;
root->right->parent = root;
if ( direction == AKGL_STAGE_PARTITION_HORIZONTAL ) {
context.right.extent.x = extents.x;
context.right.extent.y = extents.y + (extents.h / 2);
context.right.extent.w = extents.w;
context.right.extent.h = (extents.h / 2);
} else if ( direction == AKGL_STAGE_PARTITION_VERTICAL ) {
context.right.extent.x = extents.x + (extents.w / 2);
context.right.extent.y = extents.y;
context.right.extent.w = (extents.w / 2);
context.right.extent.h = extents.h;
} else {
FAIL_RETURN(e, AKERR_VALUE, "Invalid partition direction %d", direction);
}
}
if ( root->leaf->actors == NULL ) {
PASS(e, akgl_heap_next_list((aksl_ListNode **)&root->leaf->actors));
context.parent = root;
}
// - Test (SDL_HasIntersection) all actor objects in the current working space
// against the SDL_Frects for both halves. If they intersect, push the actors
// down into the leaf linked lists for each half
PASS(e, aksl_list_iterate(actors, &akgl_stage_2d_bsp_actoriter, &context));
PASS(e, akgl_stage_2d_partition_switchdirection(direction, &direction));
// - Recurse down into the left BSP node, changing our partitioning
// (vertical->horizontal, horizontal->vertical etc)
PASS(e, akgl_stage_2d_bsp(root->left, root->left->leaf->actors, context.left.extent, direction, depth + 1, maxdepth));
// - Recurse down into the right BSP node
// (vertical->horizontal, horizontal->vertical etc)
PASS(e, akgl_stage_2d_bsp(root->right, root->right->leaf->actors, context.right.extent, direction, depth + 1, maxdepth));
SUCCEED_RETURN(e);
}
/**
* @brief Release the stage's curent BSP tree
*
* This recursive function traverses down all nodes of the stage's BSP tree and release all the tree and list nodes
*
* @param[in] self The stage whose BSP tree should be freed
*
* @throws AKERR_NULLPOINTER on NULL pointer inputs
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_stage_2d_bspfree(akgl_Stage *self)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
PASS(e, aksl_tree_iterate(&self->bsp, &akgl_heap_iter_tree_release, NULL, NULL, AKSL_TREE_SEARCH_DFS, NULL, NULL));
SUCCEED_RETURN(e);
}
/**
* @brief Partition the stage into a BSP tree, grouping near neighbor actors together
*
* @param[in] self The stage to partition
*
* @return akerr_ErrorContext*
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_stage_2d_partition(akgl_Stage *self)
{
aksl_ListNode *actors = NULL;
aksl_ListNode *curnode = NULL;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
PASS(e, akgl_stage_2d_bspfree(self));
// Build a linked list of all actors currently initialized
PASS(e, akgl_heap_next_list(&actors));
curnode = actors;
for ( int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
if ( HEAP_ACTOR[i].refcount != 0 ) {
curnode->data = (void *)&HEAP_ACTOR[i];
PASS(e, akgl_heap_next_list(&curnode->next));
curnode = curnode->next;
}
}
/* This could be better.
* Right now we divide the extents into 16 equal rectangles and sort actors
* into them by their bbox coordinates. This doesn't help when lots of actors are
* stacked up unequally on one area of the screen. A better solution might be to
* continue subdividing a given extent until it is small enough that only
* N number of actors fit inside of it. All of this is overkill for something like
* a simple Mario style sidescroller or even a JRPG, but for something like
* a bullet hell, it will be essential.
*/
ATTEMPT {
CATCH(e, akgl_stage_2d_bsp(
&self->bsp,
actors,
// Define the current working space as the boundaries of the entire stage
// (from the origin to the extents of the largest and/or furthest object)
// .... let's see what we get if we just cheat and use the camera extents.
// (Do we *really* want off-screen objects in the collision detection?)
(SDL_FRect){0.0, 0.0, camera->w, camera->h},
// Define if we are partitioning horizontally or vertically
AKGL_STAGE_PARTITION_VERTICAL,
// Begin depth counter at 1
1,
// Maximum recursion depth
AKGL_STAGE_PARTITION_MAXDEPTH)
);
} CLEANUP {
PREPARE_ERROR(e2);
PASS(e2, aksl_list_iterate(actors, &akgl_heap_iter_list_release, NULL));
} PROCESS(e) {
} FINISH(e, true);
SUCCEED_RETURN(e);
}
/**
* @brief Move this actor to the correct part of the BSP tree
*
* @param[in] self akgl_Stage *
* @param[in] actor akgl_Actor *
*
* @throws AKERR_NULLPOINTER on null pointer inputs
* @return akerr_ErrorContext
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_stage_2d_bsp_move(akgl_Stage *self, akgl_Actor *actor)
{
aksl_ListNode *tmp = NULL;
aksl_TreeNode *curnode = NULL;
akgl_BSPContext context;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
FAIL_ZERO_RETURN(e, actor, AKERR_NULLPOINTER, "actor");
if ( actor->bsphome == NULL ) {
// New actor that is not currently part of the BSP tree. Push it down.
PASS(e, akgl_heap_next_list(&tmp));
tmp->data = actor;
ATTEMPT {
PASS(e, akgl_stage_2d_bsp(
&self->bsp,
tmp,
(SDL_FRect){0.0, 0.0, camera->w, camera->h},
AKGL_STAGE_PARTITION_VERTICAL,
1,
AKGL_STAGE_PARTITION_MAXDEPTH)
);
} CLEANUP {
PREPARE_ERROR(e2);
PASS(e2, akgl_heap_release_list(tmp));
} PROCESS(e) {
} FINISH(e, true);
} else {
// Actor already exists somewhere in the BSP tree
// 1. Is it still in the extents for its bsphome? If so, do nothing.
if ( SDL_HasRectIntersectionFloat(&actor->bsphome->leaf->extent, &actor->bbox) ) {
SUCCEED_RETURN(e);
}
// 2. If not, pop the actor out of its current BSP actor list, and start walking up.
PASS(e, aksl_list_pop(actor->bsplistnode));
curnode = actor->bsphome->parent;
tmp = actor->bsplistnode;
while ( curnode != NULL ) {
// 3. Is it within the current BSP node's extents? If not, walk up and repeat.
if ( SDL_HasRectIntersectionFloat(&curnode->leaf->extent, &actor->bbox) ) {
// 4. Find which extent it matches.
// FIXME : akgl_BSPContext should be replaced with aksl_TreeNode at this point, it's fully redundant
context.left = curnode->left;
context.right = curnode->right;
context.parent = curnode->parent;
PASS(e, akgl_stage_2d_bsp_actoriter(tmp, actor->bsplistnode, &context));
}
curnode = curnode->parent;
}
// Release the old actor node, it will have been moved to a new one by now
if ( tmp == actor->bsplistnode ) {
// Odd ...
SDL_Log("Expected actor %d to be moved to a different BSP node after moving, but it still has the same list node...\n", actor);
} else {
PASS(e, akgl_heap_release_list(tmp));
}
}
SUCCEED_RETURN(e);
}