/** * @file physics.c * @brief Implements the physics subsystem. */ #include #include #include #include #include #include #include #include akerr_ErrorContext *akgl_physics_null_gravity(akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); SUCCEED_RETURN(e); } akerr_ErrorContext *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 *akgl_physics_null_move(struct akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); SUCCEED_RETURN(e); } akerr_ErrorContext *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 *akgl_physics_arcade_gravity(akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); FAIL_ZERO_RETURN(e, actor, AKERR_NULLPOINTER, "actor"); if ( self->gravity_x != 0 ) { // Assume the X origin is - (screen left) actor->ex -= (self->gravity_x * dt); } if ( self->gravity_y != 0 ) { // Assume Y origin is + (down screen) actor->ey += (self->gravity_y * dt); } if ( self->gravity_z != 0 ) { // Assume Z origin is - (behind the camera) actor->ez -= (self->gravity_z * dt); } SUCCEED_RETURN(e); } akerr_ErrorContext *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_RETURN(e, AKERR_API, "Not implemented"); SUCCEED_RETURN(e); } akerr_ErrorContext *akgl_physics_arcade_move(struct akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); FAIL_ZERO_RETURN(e, actor, AKERR_NULLPOINTER, "actor"); actor->x += actor->vx * dt; actor->y += actor->vy * dt; actor->z += actor->vz * dt; SUCCEED_RETURN(e); } akerr_ErrorContext *akgl_physics_init_arcade(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_arcade_gravity; self->collide = akgl_physics_arcade_collide; self->move = akgl_physics_arcade_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 *akgl_physics_simulate(akgl_PhysicsBackend *self, akgl_Iterator *opflags) { PREPARE_ERROR(e); akgl_Iterator defflags = { .flags = 0, .layerid = 0 }; SDL_Time curtime = 0; float32_t dt = 0; akgl_Actor *actor = NULL; FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); FAIL_ZERO_RETURN(e, self->move, AKERR_NULLPOINTER, "self->move"); // Reading the elapsed time requires self, so it cannot be hoisted above // the null check. curtime = SDL_GetTicksNS(); dt = (float32_t)(curtime - self->gravity_time) / (float32_t)AKGL_TIME_ONESEC_NS; if ( opflags == NULL ) { opflags = &defflags; } for ( int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) { actor = &akgl_heap_actors[i]; if ( actor->refcount == 0 ) { continue; } if ( actor->parent != NULL ) { // Children don't move independently of their parents, they just have an offset actor->x = actor->parent->x + actor->vx; actor->y = actor->parent->y + actor->vy; actor->z = actor->parent->z + actor->vz; continue; } else if ( actor->basechar == NULL ) { continue; } if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_LAYERMASK) ) { if ( actor->layer != opflags->layerid ) { continue; } } // thrust is a function of acceleration on a given axis if ( AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_LEFT) || AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_RIGHT) ) { actor->tx += actor->ax * dt; } if ( AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_UP) || AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_DOWN) ) { actor->ty += actor->ay * dt; } // velocity equals thrust unless thrust exceeds max speed if ( fabsf(actor->tx) > fabsf(actor->sx) ) { if ( actor->tx < 0 ) { actor->tx = -actor->sx; } else { actor->tx = actor->sx; } } if ( fabsf(actor->ty) > fabsf(actor->sy) ) { if ( actor->ty < 0 ) { actor->ty = -actor->sy; } else { actor->ty = actor->sy; } } if ( fabsf(actor->tz) > fabsf(actor->sz) ) { if ( actor->tz < 0 ) { actor->tz = -actor->sz; } else { actor->tz = actor->sz; } } 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); } self->gravity_time = curtime; SUCCEED_RETURN(e); } akerr_ErrorContext *akgl_physics_factory(akgl_PhysicsBackend *self, akgl_String *type) { uint32_t hashval; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); FAIL_ZERO_RETURN(e, type, AKERR_NULLPOINTER, "type"); if ( strncmp(type->data, "null", 4) == 0) { PASS(e, akgl_physics_init_null(self)); SUCCEED_RETURN(e); } if ( strncmp(type->data, "arcade", 6) == 0) { PASS(e, akgl_physics_init_arcade(self)); SUCCEED_RETURN(e); } FAIL_RETURN(e, AKERR_KEY, "Invalid physics engine %s", type->data); }