From 314ce5e10d0b2d2eb55e5a53267a4cba07a5b7eb Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Tue, 26 May 2026 10:36:31 -0400 Subject: [PATCH] Physics simulation engine implemented, basic cases (null and 2d SideScroller) works --- include/akgl/SDL_GameControllerDB.h | 2 +- include/akgl/actor.h | 8 +- include/akgl/character.h | 20 +-- include/akgl/physics.h | 40 ++++++ src/actor.c | 13 +- src/character.c | 10 +- src/physics.c | 186 ++++++++++++++++++++++++++++ src/registry.c | 18 +-- 8 files changed, 271 insertions(+), 26 deletions(-) create mode 100644 include/akgl/physics.h create mode 100644 src/physics.c diff --git a/include/akgl/SDL_GameControllerDB.h b/include/akgl/SDL_GameControllerDB.h index 57b4eaf..41ea51d 100644 --- a/include/akgl/SDL_GameControllerDB.h +++ b/include/akgl/SDL_GameControllerDB.h @@ -1,7 +1,7 @@ #ifndef _SDL_GAMECONTROLLERDB_H_ #define _SDL_GAMECONTROLLERDB_H_ -// Taken from https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt on Mon May 25 09:28:01 PM EDT 2026 +// Taken from https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt on Tue May 26 10:27:58 AM EDT 2026 #define AKGL_SDL_GAMECONTROLLER_DB_LEN 2228 diff --git a/include/akgl/actor.h b/include/akgl/actor.h index c7dcd07..cfa4fda 100644 --- a/include/akgl/actor.h +++ b/include/akgl/actor.h @@ -18,8 +18,8 @@ #define AKGL_ACTOR_STATE_MOVING_RIGHT 1 << 8 // 256 0000 0001 0000 0000 #define AKGL_ACTOR_STATE_MOVING_UP 1 << 9 // 512 0000 0010 0000 0000 #define AKGL_ACTOR_STATE_MOVING_DOWN 1 << 10 // 1024 0000 0100 0000 0000 -#define AKGL_ACTOR_STATE_UNDEFINED_11 1 << 11 // 2048 0000 1000 0000 0000 -#define AKGL_ACTOR_STATE_UNDEFINED_12 1 << 12 // 4096 0001 0000 0000 0000 +#define AKGL_ACTOR_STATE_MOVING_IN 1 << 11 // 2048 0000 1000 0000 0000 +#define AKGL_ACTOR_STATE_MOVING_OUT 1 << 12 // 4096 0001 0000 0000 0000 #define AKGL_ACTOR_STATE_UNDEFINED_13 1 << 13 // 8192 0010 0000 0000 0000 #define AKGL_ACTOR_STATE_UNDEFINED_14 1 << 14 // 16384 0100 0000 0000 0000 #define AKGL_ACTOR_STATE_UNDEFINED_15 1 << 15 // 32768 1000 0000 0000 0000 @@ -71,7 +71,9 @@ typedef struct akgl_Actor { void *actorData; bool visible; SDL_Time movetimer; - float32_t mass; + float32_t vx; + float32_t vy; + float32_t vz; float32_t x; float32_t y; float32_t z; diff --git a/include/akgl/character.h b/include/akgl/character.h index b80dc6e..cb0c3f7 100644 --- a/include/akgl/character.h +++ b/include/akgl/character.h @@ -9,14 +9,18 @@ #define AKGL_MAX_HEAP_CHARACTER 256 typedef struct akgl_Character { - uint8_t refcount; - char name[AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH]; - SDL_PropertiesID state_sprites; - uint64_t movementspeed; - float32_t vx; - float32_t vy; - akerr_ErrorContext AKERR_NOIGNORE *(*sprite_add)(struct akgl_Character *, akgl_Sprite *, int); - akerr_ErrorContext AKERR_NOIGNORE *(*sprite_get)(struct akgl_Character *, int, akgl_Sprite **); + uint8_t refcount; + char name[AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH]; + SDL_PropertiesID state_sprites; + uint64_t speedtime; + float32_t ax; + float32_t ay; + float32_t az; + float32_t sx; + float32_t sy; + float32_t sz; + akerr_ErrorContext AKERR_NOIGNORE *(*sprite_add)(struct akgl_Character *, akgl_Sprite *, int); + akerr_ErrorContext AKERR_NOIGNORE *(*sprite_get)(struct akgl_Character *, int, akgl_Sprite **); } akgl_Character; diff --git a/include/akgl/physics.h b/include/akgl/physics.h new file mode 100644 index 0000000..ccaf33b --- /dev/null +++ b/include/akgl/physics.h @@ -0,0 +1,40 @@ +#ifndef _PHYSICS_H_ +#define _PHYSICS_H_ + +#include +#include +#include +#include + +typedef struct akgl_PhysicsBackend { + akerr_ErrorContext AKERR_NOIGNORE *(*simulate)(struct akgl_PhysicsBackend *self, akgl_Iterator *opflags); + akerr_ErrorContext AKERR_NOIGNORE *(*gravity)(struct akgl_PhysicsBackend *self, akgl_Actor *actor, SDL_Time curtime); + akerr_ErrorContext AKERR_NOIGNORE *(*collide)(struct akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2); + akerr_ErrorContext AKERR_NOIGNORE *(*move)(struct akgl_PhysicsBackend *self, akgl_Actor *actor, SDL_Time curtime); + + double drag_x; + double drag_y; + double drag_z; + double gravity_x; + double gravity_y; + double gravity_z; + SDL_Time gravity_time; + SDL_Time timer_gravity; + +} akgl_PhysicsBackend; + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_null_gravity(akgl_PhysicsBackend *self, akgl_Actor *actor, SDL_Time curtime); +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_null_collide(akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2); +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_null_move(akgl_PhysicsBackend *self, akgl_Actor *actor, SDL_Time curtime); +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_null(akgl_PhysicsBackend *self); + + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_ss_gravity(akgl_PhysicsBackend *self, akgl_Actor *actor, SDL_Time curtime); +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_ss_collide(akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2); +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_ss_move(akgl_PhysicsBackend *self, akgl_Actor *actor, SDL_Time curtime); +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_sidescroller(akgl_PhysicsBackend *self); + + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *self, akgl_Iterator *opflags); + +#endif // _PHYSICS_H_ diff --git a/src/actor.c b/src/actor.c index 5177e34..2c587c1 100644 --- a/src/actor.c +++ b/src/actor.c @@ -117,7 +117,14 @@ akerr_ErrorContext *akgl_actor_logic_changeframe(akgl_Actor *obj, akgl_Sprite *c akerr_ErrorContext *akgl_actor_logic_movement(akgl_Actor *obj, SDL_Time curtime) { PREPARE_ERROR(errctx); - FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "Null actor reference"); + FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "obj"); + FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "obj->basechar"); + if ( obj->vx > obj->basechar->sx ) { + obj->vx = obj->basechar->sx; + } + if ( obj->vy > obj->basechar->sy ) { + obj->vy = obj->basechar->sy; + } // Effectively a NOOP, this is handled by the physics engine now // These functions are still present in case the library user wants per-actor behavior SUCCEED_RETURN(errctx); @@ -276,6 +283,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_off(akgl_Actor *obj, SDL FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor"); FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "NULL event"); //SDL_Log("event %d (button %d / key %d) stops moving actor left", event->type, event->gbutton.which, event->key.key); + obj->vx = 0; AKGL_BITMASK_DEL(obj->state, AKGL_ACTOR_STATE_MOVING_LEFT); //SDL_Log("new target actor state: %b", obj->state); SUCCEED_RETURN(errctx); @@ -299,6 +307,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_off(akgl_Actor *obj, SD FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor"); FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "NULL event"); //SDL_Log("event %d (button %d / key %d) stops moving actor right", event->type, event->gbutton.which, event->key.key); + obj->vx = 0; AKGL_BITMASK_DEL(obj->state, AKGL_ACTOR_STATE_MOVING_RIGHT); //SDL_Log("new target actor state: %b", obj->state); SUCCEED_RETURN(errctx); @@ -322,6 +331,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_off(akgl_Actor *obj, SDL_E FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor"); FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "NULL event"); //SDL_Log("event %d (button %d / key %d) stops moving actor up", event->type, event->gbutton.which, event->key.key); + obj->vy = 0; AKGL_BITMASK_DEL(obj->state, AKGL_ACTOR_STATE_MOVING_UP); //SDL_Log("new target actor state: %b", obj->state); SUCCEED_RETURN(errctx); @@ -345,6 +355,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_down_off(akgl_Actor *obj, SDL FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor"); FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "NULL event"); //SDL_Log("event %d (button %d / key %d) stops moving actor down", event->type, event->gbutton.which, event->key.key); + obj->vy = 0; AKGL_BITMASK_DEL(obj->state, AKGL_ACTOR_STATE_MOVING_DOWN); //SDL_Log("new target actor state: %b", obj->state); SUCCEED_RETURN(errctx); diff --git a/src/character.c b/src/character.c index 016ddf1..df68432 100644 --- a/src/character.c +++ b/src/character.c @@ -185,10 +185,12 @@ akerr_ErrorContext *akgl_character_load_json(char *filename) "Error while loading character from %s on line %d: %s", filename, error.line, error.text ); CATCH(errctx, akgl_character_load_json_inner(json, obj)); - CATCH(errctx, akgl_get_json_integer_value(json, "movementspeed", (int *)&obj->movementspeed)); - obj->movementspeed = obj->movementspeed * AKGL_TIME_ONESEC_MS; - CATCH(errctx, akgl_get_json_number_value(json, "velocity_x", &obj->vx)); - CATCH(errctx, akgl_get_json_number_value(json, "velocity_y", &obj->vy)); + CATCH(errctx, akgl_get_json_integer_value(json, "speedtime", (int *)&obj->speedtime)); + obj->speedtime = obj->speedtime * AKGL_TIME_ONESEC_MS; + CATCH(errctx, akgl_get_json_number_value(json, "speed_x", &obj->sx)); + CATCH(errctx, akgl_get_json_number_value(json, "speed_y", &obj->sy)); + CATCH(errctx, akgl_get_json_number_value(json, "acceleration_x", &obj->ax)); + CATCH(errctx, akgl_get_json_number_value(json, "acceleration_y", &obj->ay)); } CLEANUP { //IGNORE(akgl_heap_release_string(tmpstr)); if ( errctx != NULL ) { diff --git a/src/physics.c b/src/physics.c new file mode 100644 index 0000000..61a45ec --- /dev/null +++ b/src/physics.c @@ -0,0 +1,186 @@ +#include +#include +#include +#include +#include +#include +#include + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_null_gravity(akgl_PhysicsBackend *self, akgl_Actor *actor, SDL_Time curtime) +{ + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); + SUCCEED_RETURN(e); +} + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_null_collide(akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2) +{ + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); + SUCCEED_RETURN(e); +} + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_null_move(struct akgl_PhysicsBackend *self, akgl_Actor *actor, SDL_Time curtime) +{ + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); + SUCCEED_RETURN(e); +} + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_null(akgl_PhysicsBackend *self) +{ + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); + + self->gravity = akgl_physics_null_gravity; + self->collide = akgl_physics_null_collide; + self->move = akgl_physics_null_move; + self->simulate = akgl_physics_simulate; + + SUCCEED_RETURN(e); +} + + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_ss_gravity(akgl_PhysicsBackend *self, akgl_Actor *actor, SDL_Time curtime) +{ + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); + // Do nothing + // Calculate the drop of all relevant actors which is a function of their mass and the + // world's gravity + + //Gravity is applied in units per second. So we apply + // (unit / milliseconds per second) * (milliseconds since last update) + actor->vx += ((self->gravity_x / AKGL_TIME_ONESEC_NS) * (curtime - self->gravity_time)); + actor->vy += ((self->gravity_y / AKGL_TIME_ONESEC_NS) * (curtime - self->gravity_time)); + actor->vz += ((self->gravity_z / AKGL_TIME_ONESEC_NS) * (curtime - self->gravity_time)); + + // Apply atmospheric drag + actor->vx -= actor->vx * self->drag_x * (curtime - self->gravity_time); + actor->vy -= actor->vy * self->drag_x * (curtime - self->gravity_time); + actor->vz -= actor->vz * self->drag_x * (curtime - self->gravity_time); + // Need a euler function + SUCCEED_RETURN(e); +} + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_ss_collide(akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2) +{ + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); + FAIL_RETURN(e, AKERR_API, "Not implemented"); + SUCCEED_RETURN(e); +} + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_ss_move(struct akgl_PhysicsBackend *self, akgl_Actor *actor, SDL_Time curtime) +{ + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); + FAIL_ZERO_RETURN(e, actor, AKERR_NULLPOINTER, "actor"); + + if ( actor->parent != NULL ) { + // Children don't move independently of their parents, they just have an offset + SUCCEED_RETURN(e); + } else if ( actor->basechar == NULL ) { + SUCCEED_RETURN(e); + } else { + if ( (curtime - actor->movetimer) >= actor->basechar->speedtime ) { + actor->movetimer = curtime; + ATTEMPT { + CATCH(e, actor->movementlogicfunc(actor,curtime)); + } CLEANUP { + } PROCESS(e) { + } HANDLE(e, AKGL_ERR_LOGICINTERRUPT) { + // The actor told us NOT to process them, they handled their own update + SUCCEED_RETURN(e); + } FINISH(e, true); + if ( AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_LEFT) ) { + actor->x += -actor->vx; + } + if ( AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_RIGHT) ) { + actor->x += actor->vx; + } + if ( AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_UP) ) { + actor->y += -actor->vy; + } + if ( AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_DOWN) ) { + actor->y += actor->vy; + } + } + } + SUCCEED_RETURN(e); +} + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_sidescroller(akgl_PhysicsBackend *self) +{ + akgl_String *tmp; + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); + PASS(e, akgl_heap_next_string(&tmp)); + + self->gravity = akgl_physics_ss_gravity; + self->collide = akgl_physics_ss_collide; + self->move = akgl_physics_ss_move; + self->simulate = akgl_physics_simulate; + + ATTEMPT { + CATCH(e, akgl_get_property("physics.gravity.x", &tmp, "0.0")); + CATCH(e, aksl_atof(tmp->data, &self->gravity_x)); + CATCH(e, akgl_get_property("physics.gravity.y", &tmp, "0.0")); + CATCH(e, aksl_atof(tmp->data, &self->gravity_y)); + CATCH(e, akgl_get_property("physics.gravity.z", &tmp, "0.0")); + CATCH(e, aksl_atof(tmp->data, &self->gravity_z)); + CATCH(e, akgl_get_property("physics.drag.x", &tmp, "0.0")); + CATCH(e, aksl_atof(tmp->data, &self->drag_x)); + CATCH(e, akgl_get_property("physics.drag.y", &tmp, "0.0")); + CATCH(e, aksl_atof(tmp->data, &self->drag_y)); + CATCH(e, akgl_get_property("physics.drag.z", &tmp, "0.0")); + CATCH(e, aksl_atof(tmp->data, &self->drag_z)); + } CLEANUP { + IGNORE(akgl_heap_release_string(tmp)); + } PROCESS(e) { + } FINISH(e, true); + + SUCCEED_RETURN(e); +} + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *self, akgl_Iterator *opflags) +{ + PREPARE_ERROR(e); + akgl_Iterator defflags = { + .flags = 0, + .layerid = 0 + }; + SDL_Time curtime = SDL_GetTicksNS(); + akgl_Actor *actor = NULL; + + FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); + FAIL_ZERO_RETURN(e, self->move, AKERR_NULLPOINTER, "self->move"); + + if ( opflags == NULL ) { + opflags = &defflags; + } + + for ( int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) { + actor = &HEAP_ACTOR[i]; + if ( actor->refcount == 0 ) { + continue; + } + if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_LAYERMASK) ) { + if ( actor->layer != opflags->layerid ) { + continue; + } + } + if ( AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_LEFT) || + AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_RIGHT) ) { + actor->vx += actor->basechar->ax; + } + if ( AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_UP) || + AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_DOWN) ) { + actor->vy += actor->basechar->ay; + } + PASS(e, self->gravity(self, actor, curtime)); + PASS(e, self->move(self, actor, curtime)); + self->gravity_time = curtime; + } + SUCCEED_RETURN(e); +} diff --git a/src/registry.c b/src/registry.c index ae7c8cc..c39730d 100644 --- a/src/registry.c +++ b/src/registry.c @@ -10,14 +10,14 @@ #include #include -SDL_PropertiesID AKGL_REGISTRY_ACTOR; -SDL_PropertiesID AKGL_REGISTRY_ACTOR_STATE_STRINGS; -SDL_PropertiesID AKGL_REGISTRY_SPRITE; -SDL_PropertiesID AKGL_REGISTRY_SPRITESHEET; -SDL_PropertiesID AKGL_REGISTRY_CHARACTER; -SDL_PropertiesID AKGL_REGISTRY_MUSIC; -SDL_PropertiesID AKGL_REGISTRY_FONT; -SDL_PropertiesID AKGL_REGISTRY_PROPERTIES; +SDL_PropertiesID AKGL_REGISTRY_ACTOR = 0; +SDL_PropertiesID AKGL_REGISTRY_ACTOR_STATE_STRINGS = 0; +SDL_PropertiesID AKGL_REGISTRY_SPRITE = 0; +SDL_PropertiesID AKGL_REGISTRY_SPRITESHEET = 0; +SDL_PropertiesID AKGL_REGISTRY_CHARACTER = 0; +SDL_PropertiesID AKGL_REGISTRY_MUSIC = 0; +SDL_PropertiesID AKGL_REGISTRY_FONT = 0; +SDL_PropertiesID AKGL_REGISTRY_PROPERTIES = 0; akerr_ErrorContext *akgl_registry_init() { @@ -39,7 +39,7 @@ akerr_ErrorContext *akgl_registry_init() akerr_ErrorContext *akgl_registry_init_actor() { PREPARE_ERROR(errctx); - if ( AKGL_REGISTRY_ACTOR != NULL ) { + if ( AKGL_REGISTRY_ACTOR != 0 ) { SDL_DestroyProperties(AKGL_REGISTRY_ACTOR); } AKGL_REGISTRY_ACTOR = SDL_CreateProperties();