diff --git a/assets/levels/levels.ldtk b/assets/levels/levels.ldtk index efcb32c..46ff888 100644 --- a/assets/levels/levels.ldtk +++ b/assets/levels/levels.ldtk @@ -1187,8 +1187,8 @@ "padding": 0, "tags": ["World"], "tagsSourceEnumUid": 477, - "enumTags": [ { "enumValueId": "none", "tileIds": [] }, { "enumValueId": "fullrect", "tileIds": [4,6,7,10,11,12,13,14,15,16,17,21,24,25,26,27,31,34,35,36,37,44,45] }, { "enumValueId": "pointshape", "tileIds": [0,2,3,20,22,23,30,32] } ], - "customData": [], + "enumTags": [ { "enumValueId": "fullrect", "tileIds": [1,4,6,7,10,11,12,13,14,15,16,17,21,24,25,26,27,31,34,35,36,37,44,45] }, { "enumValueId": "pointshape", "tileIds": [0,2,3,20,22,23,30,32] } ], + "customData": [ { "tileId": 0, "data": "\"collisionShape\": [\n [0.0, 0.0], [1.0, 1.0],\n [1.0, 0.0]\n]" }, { "tileId": 2, "data": "\"collisionsPoints\": [\n [0.0, 0.0], [0.0, 1.0], [1.0, 0.0]\n]" } ], "savedSelections": [], "cachedPixelData": { "opaqueTiles": "0000000100010011000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -1236,11 +1236,7 @@ "savedSelections": [], "cachedPixelData": { "opaqueTiles": "000", "averageColors": "187928790bbd" } } - ], "enums": [{ "identifier": "tilecollisiontype", "uid": 477, "values": [ - { "id": "none", "tileRect": { "tilesetUid": 3, "x": 384, "y": 64, "w": 16, "h": 16 }, "color": 16777215 }, - { "id": "fullrect", "tileRect": { "tilesetUid": 3, "x": 272, "y": 272, "w": 16, "h": 16 }, "color": 16723968 }, - { "id": "pointshape", "tileRect": { "tilesetUid": 3, "x": 288, "y": 48, "w": 16, "h": 16 }, "color": 8698879 } - ], "iconTilesetUid": 3, "externalRelPath": null, "externalFileChecksum": null, "tags": ["Tiles"] }], "externalEnums": [], "levelFields": [] }, + ], "enums": [{ "identifier": "tilecollisiontype", "uid": 477, "values": [ { "id": "fullrect", "tileRect": { "tilesetUid": 3, "x": 272, "y": 272, "w": 16, "h": 16 }, "color": 16723968 }, { "id": "pointshape", "tileRect": { "tilesetUid": 3, "x": 288, "y": 48, "w": 16, "h": 16 }, "color": 8698879 } ], "iconTilesetUid": 3, "externalRelPath": null, "externalFileChecksum": null, "tags": ["Tiles"] }], "externalEnums": [], "levelFields": [] }, "levels": [ { "identifier": "level_0", diff --git a/compile_commands.json b/compile_commands.json index bf63341..3ac525a 100644 --- a/compile_commands.json +++ b/compile_commands.json @@ -1,21 +1,4 @@ [ - { - "arguments": [ - "/usr/lib64/ccache/cc", - "-c", - "-Wall", - "-g3", - "-DVMATH_SDL=1", - "-Iinclude", - "-Isrc", - "-o", - "bin/fencer", - "src/fencer.c" - ], - "directory": "/home/sara/Documents/c-projects/fencer", - "file": "/home/sara/Documents/c-projects/fencer/src/fencer.c", - "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" - }, { "arguments": [ "/usr/lib64/ccache/cc", @@ -50,23 +33,6 @@ "file": "/home/sara/Documents/c-projects/fencer/src/render.c", "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" }, - { - "arguments": [ - "/usr/lib64/ccache/cc", - "-c", - "-Wall", - "-g3", - "-DVMATH_SDL=1", - "-Iinclude", - "-Isrc", - "-o", - "bin/fencer", - "src/tilemap.c" - ], - "directory": "/home/sara/Documents/c-projects/fencer", - "file": "/home/sara/Documents/c-projects/fencer/src/tilemap.c", - "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" - }, { "arguments": [ "/usr/lib64/ccache/cc", @@ -134,5 +100,243 @@ "directory": "/home/sara/Documents/c-projects/fencer", "file": "/home/sara/Documents/c-projects/fencer/src/assets.c", "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/level.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/level.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/tileset.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/tileset.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/debug.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/debug.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/input.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/input.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/shape.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/shape.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/fencer.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/fencer.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/rigidbody.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/rigidbody.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/collision.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/collision.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/player.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/player.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/physics_world.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/physics_world.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/tilemap.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/tilemap.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/sprite_entity.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/sprite_entity.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/transformable.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/transformable.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" + }, + { + "arguments": [ + "/usr/lib64/ccache/cc", + "-c", + "-Wall", + "-g3", + "-DVMATH_SDL=1", + "-Iinclude", + "-Isrc", + "-o", + "bin/fencer", + "src/list.c" + ], + "directory": "/home/sara/Documents/c-projects/fencer", + "file": "/home/sara/Documents/c-projects/fencer/src/list.c", + "output": "/home/sara/Documents/c-projects/fencer/bin/fencer" } ] diff --git a/levels.ldtk b/levels.ldtk index efcb32c..46ff888 100644 --- a/levels.ldtk +++ b/levels.ldtk @@ -1187,8 +1187,8 @@ "padding": 0, "tags": ["World"], "tagsSourceEnumUid": 477, - "enumTags": [ { "enumValueId": "none", "tileIds": [] }, { "enumValueId": "fullrect", "tileIds": [4,6,7,10,11,12,13,14,15,16,17,21,24,25,26,27,31,34,35,36,37,44,45] }, { "enumValueId": "pointshape", "tileIds": [0,2,3,20,22,23,30,32] } ], - "customData": [], + "enumTags": [ { "enumValueId": "fullrect", "tileIds": [1,4,6,7,10,11,12,13,14,15,16,17,21,24,25,26,27,31,34,35,36,37,44,45] }, { "enumValueId": "pointshape", "tileIds": [0,2,3,20,22,23,30,32] } ], + "customData": [ { "tileId": 0, "data": "\"collisionShape\": [\n [0.0, 0.0], [1.0, 1.0],\n [1.0, 0.0]\n]" }, { "tileId": 2, "data": "\"collisionsPoints\": [\n [0.0, 0.0], [0.0, 1.0], [1.0, 0.0]\n]" } ], "savedSelections": [], "cachedPixelData": { "opaqueTiles": "0000000100010011000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -1236,11 +1236,7 @@ "savedSelections": [], "cachedPixelData": { "opaqueTiles": "000", "averageColors": "187928790bbd" } } - ], "enums": [{ "identifier": "tilecollisiontype", "uid": 477, "values": [ - { "id": "none", "tileRect": { "tilesetUid": 3, "x": 384, "y": 64, "w": 16, "h": 16 }, "color": 16777215 }, - { "id": "fullrect", "tileRect": { "tilesetUid": 3, "x": 272, "y": 272, "w": 16, "h": 16 }, "color": 16723968 }, - { "id": "pointshape", "tileRect": { "tilesetUid": 3, "x": 288, "y": 48, "w": 16, "h": 16 }, "color": 8698879 } - ], "iconTilesetUid": 3, "externalRelPath": null, "externalFileChecksum": null, "tags": ["Tiles"] }], "externalEnums": [], "levelFields": [] }, + ], "enums": [{ "identifier": "tilecollisiontype", "uid": 477, "values": [ { "id": "fullrect", "tileRect": { "tilesetUid": 3, "x": 272, "y": 272, "w": 16, "h": 16 }, "color": 16723968 }, { "id": "pointshape", "tileRect": { "tilesetUid": 3, "x": 288, "y": 48, "w": 16, "h": 16 }, "color": 8698879 } ], "iconTilesetUid": 3, "externalRelPath": null, "externalFileChecksum": null, "tags": ["Tiles"] }], "externalEnums": [], "levelFields": [] }, "levels": [ { "identifier": "level_0", diff --git a/sprites/player.ase b/sprites/player.ase index b6927e0..d1e0b29 100644 Binary files a/sprites/player.ase and b/sprites/player.ase differ diff --git a/src/collision.c b/src/collision.c index e302e9c..e1ee83c 100644 --- a/src/collision.c +++ b/src/collision.c @@ -1,25 +1,32 @@ #include "collision.h" +#include "debug.h" #include "player.h" // ===================================================== // Shape overlap test using the separating axis theorem // ===================================================== -typedef struct Range {float min; float max; } Range; +typedef struct Range {float min; Vector minpoint; float max; Vector maxpoint; } Range; static Range _internal_collision_get_range_on_axis(PhysicsEntity self, Vector axis) { - Vector point = shape_get_point_transformed(self.tc->get_shape(self.data), 0, *self.transformable->get_transform(self.data)); + Transform* transform = self.transformable->get_transform(self.data); + Shape* shape = self.tc->get_shape(self.data); + Vector point = shape_get_point_transformed(shape, 0, *transform); float dot = vdotf(axis, point); - Range range = {dot, dot}; + Range range = {dot, point, dot, point}; - for(size_t point_index = 1; point_index < shape_get_points_count(self.tc->get_shape(self.data)); ++point_index) { - point = shape_get_point_transformed(self.tc->get_shape(self.data), point_index, *self.transformable->get_transform(self.data)); + for(size_t point_index = 1; point_index < shape_get_points_count(shape); ++point_index) { + point = shape_get_point_transformed(shape, point_index, *transform); dot = vdotf(axis, point); - if(dot < range.min) - range.min = fminf(dot, range.min); - if(dot > range.max) - range.max = fmaxf(dot, range.max); + if(dot < range.min) { + range.min = dot; + range.minpoint = point; + } + if(dot > range.max) { + range.max = dot; + range.maxpoint = point; + } } return range; @@ -30,10 +37,11 @@ Vector _internal_collision_overlap_on_axis(PhysicsEntity self, PhysicsEntity oth Range a_range = _internal_collision_get_range_on_axis(self, axis); Range b_range = _internal_collision_get_range_on_axis(other, axis); - if(a_range.min <= b_range.max && b_range.min <= b_range.max) - return vmulff(axis, fminf(b_range.max - b_range.min, b_range.min - a_range.max)); - else + if(a_range.min <= b_range.max && a_range.min <= b_range.max) { + return vmulff(axis, fminf(a_range.max - b_range.min, b_range.min - a_range.max)); + } else { return ZeroVector; + } } static @@ -58,20 +66,26 @@ int _internal_collision_get_overlap(PhysicsEntity self, PhysicsEntity other, Col Vector a = shape_get_point_transformed(self.tc->get_shape(self.data), point_index, *self_transform); Vector b = shape_get_point_transformed(self.tc->get_shape(self.data), next_index, *self_transform); // the direction of the line - Vector diff = vsubf(b, a); + Vector normal = vperpendicularf(vsubf(b, a)); // the smallest escape vector on this axis - Vector escape = _internal_collision_overlap_on_axis(self, other, vnormalizedf(diff)); + Vector escape = _internal_collision_overlap_on_axis(self, other, vnormalizedf(normal)); float sqr_mag = vsqrmagnitudef(escape); if(sqr_mag < shortest_sqrmag) { shortest_sqrmag = sqr_mag; shortest_escape = escape; shortest_escape_edge = point_index; } + + if(sqr_mag == 0) { + return 0; + } } RigidBody* rba = self.tc->get_rigidbody(self.data); - RigidBody* rbb = self.tc->get_rigidbody(self.data); + RigidBody* rbb = other.tc->get_rigidbody(other.data); + Vector velocity = rigidbody_get_velocity(rba); + velocity = vsubf(velocity, rigidbody_get_velocity(rbb)); *out = (Collision) { .other = other, @@ -79,7 +93,7 @@ int _internal_collision_get_overlap(PhysicsEntity self, PhysicsEntity other, Col .point = InfinityVector, .normal = vnormalizedf(shortest_escape), - .velocity = vsubf(rigidbody_get_velocity(rba), rigidbody_get_velocity(rbb)), + .velocity = velocity, .separation_force = shortest_escape, .edge_left = shape_get_point_transformed(self_shape, shortest_escape_edge, *self_transform), diff --git a/src/fencer.c b/src/fencer.c index ff97a81..c720e1d 100644 --- a/src/fencer.c +++ b/src/fencer.c @@ -11,59 +11,31 @@ #include "player.h" #include -static Spritesheet* spr_player_standing = NULL; static Player* player = NULL; - static Level* level = NULL; -static Vector cam_speed = ZeroVector; - -static -void cam_move_h(int val) { - cam_speed.x = val * 0.1f; -} -static -void cam_move_v(int val) { - cam_speed.y = -val * 0.1f; -} - static void play() { - spr_player_standing = spritesheet_load("assets/sprites/player.png", (IVector){128, 128}); g_camera.fov = 40; player = malloc(sizeof(Player)); - player->transform.scale = (Vector){4.f, 4.f}; - store_asset(player, free); - player->sprite = sprite_from_spritesheet(spr_player_standing, 0); - player->rigidbody = rigidbody_make(Player_as_Transformable(player)); - sprite_set_origin(player->sprite, (Vector){0.25f, 1.f}); - player->shape = shape_new((Vector[]){ - {-.1f, -.75f}, - {0.1f, -.75f}, - {0.1f, 0.00f}, - {-.1f, 0.00f} - }, 4); + player_spawn(player, ZeroVector); level = level_load("level_0"); - - input_add_axis_action(SDL_SCANCODE_A, SDL_SCANCODE_D, &cam_move_h); - input_add_axis_action(SDL_SCANCODE_S, SDL_SCANCODE_W, &cam_move_v); - - physics_world_add_entity(Player_as_PhysicsEntity(player)); } static void tick() { - g_camera.transform.position = vaddf(g_camera.transform.position, cam_speed); - player->transform.position = g_camera.transform.position; + Player_as_BehaviourEntity(player).tc->update(player, delta_time()); + g_camera.transform.position = player->transform.position; } static void draw() { level_draw(level); sprite_entity_draw(Player_as_SpriteEntity(player)); + shape_draw(player->shape, player->transform); } int main(int argc, char* argv[]) { diff --git a/src/level.c b/src/level.c index f94e373..db8d5ec 100644 --- a/src/level.c +++ b/src/level.c @@ -1,6 +1,7 @@ #include "level.h" #include "assets.h" #include "debug.h" +#include "physics_world.h" #include "tilemap.h" #include #include @@ -23,6 +24,11 @@ static void _deallocate_level(void* self_void) { Level* self = self_void; + for(size_t map_index = 0; map_index < self->tilemaps_len; ++map_index) { + physics_world_remove_map(self->tilemaps[map_index]); + tilemap_destroy(self->tilemaps[map_index]); + } + free(self); } @@ -84,6 +90,7 @@ Level* level_from_json(cJSON* json) { // load a tilemap as an autolayer LOG_INFO("loading autolayer"); *next_tilemap = tilemap_from_autolayer(layer); + physics_world_add_map(*next_tilemap); ++next_tilemap; } else if(strcmp(type->valuestring, "Entities")) { // load entities @@ -123,3 +130,12 @@ void level_draw(Level* self) { tilemap_draw(*map, self->transform); } } + +Tilemap* level_get_tilemap_layer(Level* self, size_t layer) { + ASSERT_RETURN(layer < self->tilemaps_len, NULL, "Layer index %zu out of range", layer); + return self->tilemaps[layer]; +} + +size_t level_get_tilemap_count(Level* self) { + return self->tilemaps_len; +} diff --git a/src/level.h b/src/level.h index c5ccab5..6832517 100644 --- a/src/level.h +++ b/src/level.h @@ -5,6 +5,8 @@ #include #include +typedef struct Tilemap Tilemap; + typedef struct Level Level; extern void world_init(); @@ -16,4 +18,7 @@ extern void level_destroy(Level* self); extern void level_draw(Level* self); +extern Tilemap* level_get_tilemap_layer(Level* self, size_t layer); +extern size_t level_get_tilemap_count(Level* self); + #endif // !_fencer_level_h diff --git a/src/physics_world.c b/src/physics_world.c index 66a6d4f..13ff1ef 100644 --- a/src/physics_world.c +++ b/src/physics_world.c @@ -125,10 +125,12 @@ void _internal_physics_narrow_collision() { for(PhysicsEntity* left = _world_bodies; left < end; ++left) { for(PhysicsEntity* right = _world_bodies; right < half_end; ++right) { - collision_check(*left, *right, &collision_left, &collision_right); + if(left == right) continue; - left->tc->on_collision(left->data, collision_left); - right->tc->on_collision(right->data, collision_right); + if(collision_check(*left, *right, &collision_left, &collision_right)) { + left->tc->on_collision(left->data, collision_left); + right->tc->on_collision(right->data, collision_right); + } } } } @@ -138,6 +140,7 @@ void _internal_tilemap_entity_collision_check(Tilemap* map, PhysicsEntity entity Collision collision_a; Collision collision_b; PhysicsEntity tileentity; + RigidBody* entity_body = entity.tc->get_rigidbody(entity.data); size_t len = tilemap_get_tile_count(map); for(size_t i = 0; i < len; ++i) { @@ -145,6 +148,7 @@ void _internal_tilemap_entity_collision_check(Tilemap* map, PhysicsEntity entity if(collision_check(entity, tileentity, &collision_a, &collision_b)) { entity.tc->on_collision(entity.data, collision_a); + rigidbody_add_contact(entity_body, collision_a); } } } @@ -161,12 +165,13 @@ void _internal_physics_tilemap_collision() { } static -void _internal_physics_move() { +void _internal_physics_apply() { PhysicsEntity* end = _world_bodies + _world_bodies_len; RigidBody* body = NULL; for(PhysicsEntity* entity = _world_bodies; entity < end; ++entity) { body = entity->tc->get_rigidbody(entity->data); + rigidbody_solve_contacts(body); rigidbody_apply_physics(body); } } @@ -175,5 +180,5 @@ void physics_world_tick() { // _internal_physics_broad_collision(); _internal_physics_narrow_collision(); _internal_physics_tilemap_collision(); - _internal_physics_move(); + _internal_physics_apply(); } diff --git a/src/player.c b/src/player.c index a1d6874..8181670 100644 --- a/src/player.c +++ b/src/player.c @@ -1,37 +1,60 @@ #include "player.h" +#include "assets.h" #include "debug.h" #include "program.h" #include "rigidbody.h" #include "input.h" +#include "physics_world.h" static Vector directional = ZeroVector; +static Spritesheet* spr_player_standing = NULL; static void player_input_h(int val) { - directional.x = val * 0.1f; + directional.x = val * 10.f; } static void player_input_v(int val) { - directional.y = -val * 0.1f; + directional.y = -val * 10.f; } void player_spawn(Player* self, Vector at) { + player_start(self); self->transform.position = at; - input_add_axis_action(SDL_SCANCODE_A, SDL_SCANCODE_D, &player_input_h); - input_add_axis_action(SDL_SCANCODE_S, SDL_SCANCODE_W, &player_input_v); - } void player_start(Player* self) { + input_add_axis_action(SDL_SCANCODE_A, SDL_SCANCODE_D, &player_input_h); + input_add_axis_action(SDL_SCANCODE_S, SDL_SCANCODE_W, &player_input_v); + self->transform = IdentityTransform; + self->transform.scale = (Vector){4.f, 4.f}; + + spr_player_standing = spritesheet_load("assets/sprites/player.png", (IVector){128, 128}); + store_asset(self, free); + + self->sprite = sprite_from_spritesheet(spr_player_standing, 0); + sprite_set_origin(self->sprite, (Vector){0.25f, 1.f}); + + self->rigidbody = rigidbody_make(Player_as_Transformable(self)); + + float ex_w = 0.1f; + float h = .75f; + float r = 0.05f; + self->shape = shape_new((Vector[]){ + {r-ex_w, 0.f}, {-ex_w, -r}, + {-ex_w, r-h}, {r-ex_w, -h}, + {ex_w-r, -h}, {ex_w, r-h}, + {ex_w, -r}, {ex_w-r, 0.f}, + }, 8); + + physics_world_add_entity(Player_as_PhysicsEntity(self)); } void player_update(Player* self, float dt) { Vector velocity = rigidbody_get_velocity(self->rigidbody); - ASSERT_RETURN(!visnanf(velocity),, "Velocity is NaN (2)"); - velocity = vmovetowardsf(velocity, vmulff(directional, 100.f), 10000.f * dt); - ASSERT_RETURN(!visnanf(velocity),, "Velocity is NaN (3)"); - rigidbody_set_velocity(self->rigidbody, velocity); + Vector velocity_target = {directional.x, directional.y}; + rigidbody_accelerate(self->rigidbody, vmulff(vsubf(velocity_target, velocity), 200.f)); } void player_collision(Player* self, Collision hit) { diff --git a/src/program.c b/src/program.c index c6dd788..7cdc187 100644 --- a/src/program.c +++ b/src/program.c @@ -11,6 +11,7 @@ #include SDL_Window* g_window; +static double _target_delta_time = 0.0; static double _delta_time; static double _frame_start; static double _game_start_time; @@ -35,9 +36,10 @@ double get_time_s() { int program_run(const struct ProgramSettings* settings) { LOG_INFO("Starting program..."); - float target_dt = 1.0f/settings->target_fps; if(settings->target_fps <= 0) { - target_dt = 0; + _target_delta_time = 0; + } else { + _target_delta_time = 1.0f/settings->target_fps; } _game_start_time = _frame_start = get_time_s(); SDL_Init(INITFLAGS); @@ -62,19 +64,20 @@ int program_run(const struct ProgramSettings* settings) { LOG_INFO("Starting program loop"); for(;;) { render_present(); - double current; - do { - current = get_time_s(); - _delta_time = current - _frame_start; - SDL_Delay(1); - program_handle_events(); - } while(_delta_time <= target_dt); - _frame_start = current; + double current_time = get_time_s(); + _delta_time += current_time - _frame_start; + _frame_start = current_time; - settings->on_tick(); + while(_delta_time > _target_delta_time) { + _delta_time -= _target_delta_time; + settings->on_tick(); + physics_world_tick(); + } + + program_handle_events(); settings->on_draw(); - physics_world_tick(); + SDL_Delay(1); } LOG_ERROR("Failed to exit"); @@ -122,5 +125,5 @@ void program_handle_windowevent(SDL_WindowEvent* event) { inline float delta_time() { - return _delta_time; + return _target_delta_time == 0 ? _delta_time : _target_delta_time; } diff --git a/src/rigidbody.c b/src/rigidbody.c index 4f2faad..daa8fed 100644 --- a/src/rigidbody.c +++ b/src/rigidbody.c @@ -1,37 +1,156 @@ #include "rigidbody.h" +#include "camera.h" +#include "debug.h" #include "program.h" +#include "collision.h" + +typedef struct { + Collision hit; + float warming; + int expiry; +} Contact; struct RigidBody { Transformable transformable; float mass; - Vector linear_velocity; Vector linear_force; + Vector linear_velocity; int is_static; + + List contacts; }; RigidBody* rigidbody_make(Transformable transform) { RigidBody* self = malloc(sizeof(RigidBody)); + ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for rigidbody"); *self = (RigidBody){ .transformable = transform, - .mass = 0.0f, + .mass = 1.0f, .linear_velocity = ZeroVector, - .linear_force = ZeroVector, - .is_static = 0 + .is_static = 0, + .contacts = list_from_type(Contact), }; return self; } void rigidbody_destroy(RigidBody* self) { + list_empty(&self->contacts); free(self); } +void rigidbody_add_contact(RigidBody* self, Collision hit) { + LOG_INFO("contact between rigidbody %p and %p detected", self->transformable.data, hit.other.data); + // update an existing contact + list_foreach(Contact, contact, &self->contacts) { + if(contact->hit.other.data == hit.other.data) { + ++contact->expiry; + contact->hit = hit; + contact->warming += delta_time(); + return; + } + } + + // create a new contact + list_add(&self->contacts, + &(Contact) { + .expiry = 1, + .hit = hit, + .warming = delta_time() + }); + + Contact* c = list_at(&self->contacts, self->contacts.len - 1); + LOG_INFO("added contact %p with exp %d", c->hit.other.data, c->expiry); +} + +static inline +void _internal_rigidbody_collect_contacts(RigidBody* self) { + for(size_t i = 0; i < self->contacts.len; ++i) { + Contact* contact = list_at(&self->contacts, i); + --(contact->expiry); + if(contact->expiry <= 0) { + list_erase(&self->contacts, i); + i--; + } + } +} + +static inline +void _internal_debug_draw_collision_edge(Vector left, Vector right, Vector normal) { +#if !NDEBUG + Vector a = camera_world_to_pixel_point(&g_camera, left); + Vector b = camera_world_to_pixel_point(&g_camera, right); + Vector n = transform_direction(&g_camera.transform, normal); + SDL_SetRenderDrawColor(g_renderer, 255, 255, 255, 255); + SDL_RenderDrawLine(g_renderer, a.x, a.y, b.x, b.y); + a = vlerpf(a, b, 0.5f); + b = vaddf(a, vmulff(n, 100.f)); + SDL_SetRenderDrawColor(g_renderer, 255, 0, 0, 255); + SDL_RenderDrawLine(g_renderer, a.x, a.y, b.x, b.y); +#endif +} + +static inline +Vector _internal_calculate_contact_force(Contact* contact) { + Collision hit = contact->hit; + const float warming = fminf(1.f, contact->warming); + const float d = 1.f; + const float k = 50.0 * warming; + const float b = 1.f; + + const Vector damping = vmulff(hit.normal, k * d); + const Vector bounce = vprojectf(vmulff(hit.normal, -b), hit.velocity); + + return vsubf(damping, bounce); +} + +static inline +void _internal_rigidbody_solve_contact(RigidBody* self, Contact* contact, Vector* solve_forces) { + Collision hit = contact->hit; + if (vsqrmagnitudef(contact->hit.separation_force) < 0.001f*0.001f) + return; + Vector force = _internal_calculate_contact_force(contact); + float dot = vdotf(vnormalizedf(*solve_forces), force); + if (veqf(*solve_forces, ZeroVector) || dot * dot > vsqrmagnitudef(*solve_forces) || dot <= 0.0f) { + #if !NDEBUG + LOG_INFO("warming: %f", contact->warming); + LOG_INFO("force: %f %f", force.x, force.y); + LOG_INFO("dot: %f", dot); + LOG_INFO("mag: %f", vmagnitudef(self->linear_force)); + #endif + *solve_forces = vaddf(*solve_forces, force); + rigidbody_add_impulse(self, force); + } + + _internal_debug_draw_collision_edge(hit.edge_left, hit.edge_right, hit.normal); + + ASSERT_RETURN(!visnanf(force), , "Force contains NaN (1)"); +} + +void rigidbody_solve_contacts(RigidBody* self) { + ASSERT_RETURN(!visnanf(self->linear_velocity),, "Velocity is NaN (0)"); + + Vector solve_forces = ZeroVector; + list_foreach(Contact, contact, &self->contacts) { + _internal_rigidbody_solve_contact(self, contact, &solve_forces); + } + + _internal_rigidbody_collect_contacts(self); + + ASSERT_RETURN(!visnanf(self->linear_velocity),, "Velocity is NaN (1)"); +} + void rigidbody_apply_physics(RigidBody* self) { + Vector position = transformable_get_position(self->transformable); Vector velocity = vmulff(self->linear_velocity, delta_time()); - Vector* position = (self->transformable.tc->get_position(self->transformable.data)); - *position = vaddf(*position, velocity); + + if(vsqrmagnitudef(velocity) > powf(0.00001f, 2)) { + transformable_set_position(self->transformable, vaddf(position, velocity)); + } + + self->linear_force = ZeroVector; } float rigidbody_get_mass(const RigidBody* self) { @@ -44,11 +163,11 @@ void rigidbody_set_mass(RigidBody* self, float mass) { void rigidbody_add_impulse(RigidBody* self, Vector force) { self->linear_force = vaddf(self->linear_force, force); + self->linear_velocity = vaddf(self->linear_velocity, force); } void rigidbody_accelerate(RigidBody* self, Vector force) { - force = vmulff(force, delta_time()); - self->linear_force = vaddf(self->linear_force, force); + rigidbody_add_impulse(self, vmulff(force, delta_time())); } int rigidbody_is_static(const RigidBody* self) { @@ -64,5 +183,6 @@ Vector rigidbody_get_velocity(const RigidBody* self) { } void rigidbody_set_velocity(RigidBody* self, Vector velocity) { + self->linear_force = vaddf(self->linear_force, vsubf(velocity, self->linear_force)); self->linear_velocity = velocity; } diff --git a/src/rigidbody.h b/src/rigidbody.h index 749d55e..a4f29e2 100644 --- a/src/rigidbody.h +++ b/src/rigidbody.h @@ -3,6 +3,9 @@ #include "shape.h" #include "transformable.h" +#include "list.h" + +struct Collision; typedef struct RigidBody RigidBody; @@ -10,6 +13,9 @@ typedef struct RigidBody RigidBody; RigidBody* rigidbody_make(Transformable transform); void rigidbody_destroy(RigidBody* self); +void rigidbody_add_contact(RigidBody* self, struct Collision hit); +void rigidbody_solve_contacts(RigidBody* self); + void rigidbody_apply_physics(RigidBody* self); float rigidbody_get_mass(const RigidBody* self); diff --git a/src/shape.c b/src/shape.c index 9dd1c4b..3b94c60 100644 --- a/src/shape.c +++ b/src/shape.c @@ -1,5 +1,8 @@ #include "shape.h" +#include "camera.h" #include "debug.h" +#include "render.h" +#include #include struct Shape { @@ -86,9 +89,9 @@ Shape* shape_new(const Vector* points, size_t points_len) { Shape* shape_new_square(Vector size) { return shape_new((Vector[4]){ ZeroVector, - (Vector){0.f, size.y}, + (Vector){size.x, 0.f}, size, - (Vector){size.x, 0.f} + (Vector){0.f, size.y}, }, 4); } @@ -182,3 +185,23 @@ Vector shape_get_median_point(Shape* self) { int shape_is_convex(Shape* self) { return self->is_convex; } + +void shape_draw(Shape* self, Transform transform) { + Vector lhs, rhs, normal; + for(size_t i = 0; i < self->points_len; ++i) { + lhs = shape_get_point_transformed(self, i, transform); + rhs = shape_get_point_transformed(self, (i + 1) % self->points_len, transform); + normal = vnormalizedf(vperpendicularf(vsubf(rhs, lhs))); + lhs = camera_world_to_pixel_point(&g_camera, lhs); + rhs = camera_world_to_pixel_point(&g_camera, rhs); + normal = transform_direction(&g_camera.transform, normal); + + SDL_SetRenderDrawColor(g_renderer, 255, 255, 255, 255); + SDL_RenderDrawLineF(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y); + + lhs = vlerpf(lhs, rhs, 0.5f); + rhs = vaddf(lhs, vmulff(normal, 10.f)); + SDL_SetRenderDrawColor(g_renderer, 0, 0, 255, 255); + SDL_RenderDrawLineF(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y); + } +} diff --git a/src/shape.h b/src/shape.h index 62be26e..bf91607 100644 --- a/src/shape.h +++ b/src/shape.h @@ -25,5 +25,6 @@ extern Vector shape_remove_point(Shape* self, size_t at); extern Vector shape_get_median_point(Shape* self); extern int shape_is_convex(Shape* self); +extern void shape_draw(Shape* self, Transform transform); #endif // !_fencer_shape_h diff --git a/src/sprite.c b/src/sprite.c index 7d7420a..21d1d26 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -25,7 +25,7 @@ Sprite* sprite_from_spritesheet(Spritesheet* sheet, size_t initial_frame) { ASSERT_RETURN(self != NULL, NULL, "Failed to allocate memory for new sprite."); self->spritesheet = sheet; - self->origin = (Vector){0.5f, 1.0f}; + self->origin = (Vector){0.5f, 0.5f}; self->current_frame = initial_frame; // TODO: replace with a getter for the current game time. self->current_frame_time = 0; diff --git a/src/sprite_entity.c b/src/sprite_entity.c index 4b3a0a9..c7bc120 100644 --- a/src/sprite_entity.c +++ b/src/sprite_entity.c @@ -1,5 +1,5 @@ #include "sprite_entity.h" -#include "sprite.h" +#include "sprite.h" void sprite_entity_draw(SpriteEntity self) { Sprite* sprite = self.tc->get_sprite(self.data); diff --git a/src/tilemap.c b/src/tilemap.c index 03b29d8..61cd60c 100644 --- a/src/tilemap.c +++ b/src/tilemap.c @@ -1,5 +1,6 @@ #include "tilemap.h" #include "debug.h" +#include "rigidbody.h" #include "tileset.h" #include "assets.h" #include "camera.h" @@ -63,6 +64,7 @@ Tilemap* tilemap_from_autolayer(cJSON* json) { free(self); RETURN_ERROR(NULL, "Failed to allocate map memory"); } + self->rigidbody = rigidbody_make(Tilemap_as_Transformable(self)); const double px_to_ws = 1.0 / grid->valuedouble; @@ -80,17 +82,30 @@ Tilemap* tilemap_from_autolayer(cJSON* json) { return self; } +void tilemap_destroy(Tilemap* self) { + free(self->map); + rigidbody_destroy(self->rigidbody); + free(self); +} + void tilemap_draw(Tilemap* self, Transform transform) { Transform tiletrans = transform_apply(transform, self->transform); TileInstance* tile; + for(int i = 0; i < self->map_num; ++i) { + tile = self->map + i; + tiletrans = transform_apply(self->transform, tile->transform); + shape_draw(tiledef_get_shape(tile->tiledef), tiletrans); + } + for(int i = 0; i < self->map_num; ++i) { tile = self->map + i; tiletrans = transform_apply(self->transform, tile->transform); Sprite* sprite = tiledef_get_sprite(tile->tiledef); - if(sprite != NULL) + if(sprite != NULL) { sprite_draw(sprite, tiletrans); + } } } @@ -102,6 +117,10 @@ TileInstance* tilemap_get_tile(Tilemap* self, size_t at) { return &self->map[at]; } +Transform* tilemap_get_transform(Tilemap* self ) { + return &self->transform; +} + RigidBody* tile_instance_get_rigidbody(TileInstance* self) { return self->parent_map->rigidbody; } diff --git a/src/tilemap.h b/src/tilemap.h index 6cb8fa1..78bb952 100644 --- a/src/tilemap.h +++ b/src/tilemap.h @@ -22,20 +22,20 @@ extern void tilemap_draw(Tilemap* self, Transform transform); extern size_t tilemap_get_tile_count(Tilemap* self); extern TileInstance* tilemap_get_tile(Tilemap* self, size_t at); +extern Transform* tilemap_get_transform(Tilemap* self); + extern RigidBody* tile_instance_get_rigidbody(TileInstance* self); extern Transform* tile_instance_get_transform(TileInstance* self); extern Shape* tile_instance_get_shape(TileInstance* self); extern void tile_instance_on_collision(TileInstance* self, Collision collision); -extern Vector* tile_instance_get_position(TileInstance* self); -extern Vector* tile_instance_get_scale(TileInstance* self); -extern float* tile_instance_get_rotation(TileInstance* self); + +impl_Transformable_for(Tilemap, + tilemap_get_transform +) impl_Transformable_for(TileInstance, - tile_instance_get_transform, - tile_instance_get_position, - tile_instance_get_scale, - tile_instance_get_rotation + tile_instance_get_transform ) impl_PhysicsEntity_for(TileInstance, tile_instance_get_rigidbody, diff --git a/src/tileset.c b/src/tileset.c index c8357c0..445d73f 100644 --- a/src/tileset.c +++ b/src/tileset.c @@ -122,6 +122,7 @@ Tileset* tileset_from_json(cJSON* json) { .sprite = sprite_from_spritesheet(self->atlas, tid), .collision = shape_new_square(OneVector) }; + sprite_set_origin(self->tiledefs[tid].sprite, ZeroVector); // TODO: generate/read collision information } diff --git a/src/transform.h b/src/transform.h index ae0cc5d..fa88cf5 100644 --- a/src/transform.h +++ b/src/transform.h @@ -51,26 +51,8 @@ Transform* transform_get_transform(Transform* self) { return self; } -static -Vector* transform_get_position(Transform* self) { - return &self->position; -} - -static -Vector* transform_get_scale(Transform* self) { - return &self->scale; -} - -static -float* transform_get_rotation(Transform* self) { - return &self->rotation; -} - impl_Transformable_for(Transform, - transform_get_transform, - transform_get_position, - transform_get_scale, - transform_get_rotation + transform_get_transform ); #endif // !_fencer_transform_h diff --git a/src/transformable.c b/src/transformable.c new file mode 100644 index 0000000..11a2559 --- /dev/null +++ b/src/transformable.c @@ -0,0 +1,41 @@ +#include "transformable.h" +#include "transform.h" + +Vector transformable_get_position(Transformable self) { + return self.tc->get_transform(self.data)->position; +} + +void transformable_set_position(Transformable self, Vector position) { + self.tc->get_transform(self.data)->position = position; +} + +void transformable_move(Transformable self, Vector delta) { + Vector* position = &self.tc->get_transform(self.data)->position; + *position = vaddf(*position, delta); +} + +Vector transformable_get_scale(Transformable self) { + return self.tc->get_transform(self.data)->scale; +} + +void transformable_set_scale(Transformable self, Vector scale) { + self.tc->get_transform(self.data)->scale = scale; +} + +void transformable_scale(Transformable self, Vector factor) { + Vector* scale = &self.tc->get_transform(self.data)->scale; + *scale = vmulf(*scale, factor); +} + +float transformable_get_rotation(Transformable self) { + return self.tc->get_transform(self.data)->rotation; +} + +void transformable_set_rotation(Transformable self, float rotation) { + self.tc->get_transform(self.data)->rotation = rotation; +} + +void transformable_rotate(Transformable self, float delta) { + float* rotation = &self.tc->get_transform(self.data)->rotation; + *rotation = *rotation + delta; +} diff --git a/src/transformable.h b/src/transformable.h index 5e9c79c..0ac7531 100644 --- a/src/transformable.h +++ b/src/transformable.h @@ -6,9 +6,6 @@ typedef struct { struct Transform* (*const get_transform)(void* self); - Vector* (*const get_position)(void* self); - Vector* (*const get_scale)(void* self); - float* (*const get_rotation)(void* self); } ITransformable; typedef struct { @@ -16,16 +13,23 @@ typedef struct { ITransformable const* tc; } Transformable; -#define impl_Transformable_for(T, get_transform_f, get_position_f, get_scale_f, get_rotation_f)\ +extern Vector transformable_get_position(Transformable self); +extern void transformable_set_position(Transformable self, Vector position); +extern void transformable_move(Transformable self, Vector delta); + +extern Vector transformable_get_scale(Transformable self); +extern void transformable_set_scale(Transformable self, Vector scale); +extern void transformable_scale(Transformable self, Vector factor); + +extern float transformable_get_rotation(Transformable self); +extern void transformable_set_rotation(Transformable self, float rotation); +extern void transformable_rotate(Transformable self, float delta); + +#define impl_Transformable_for(T, get_transform_f)\ static inline Transformable T##_as_Transformable(T* x) {\ TC_FN_TYPECHECK(Transform*, get_transform_f, T*);\ - TC_FN_TYPECHECK(Vector*, get_position_f, T*);\ - TC_FN_TYPECHECK(float*, get_rotation_f, T*);\ static ITransformable const tc = {\ - .get_transform = (Transform*(*const)(void*)) get_transform_f,\ - .get_position = (Vector*(*const)(void*)) get_position_f,\ - .get_scale = (Vector*(*const)(void*)) get_scale_f,\ - .get_rotation = (float*(*const)(void*)) get_rotation_f\ + .get_transform = (Transform*(*const)(void*)) get_transform_f\ };\ return (Transformable){.tc = &tc, .data = x};\ } diff --git a/src/vmath.h b/src/vmath.h index 87749c3..3697372 100644 --- a/src/vmath.h +++ b/src/vmath.h @@ -46,6 +46,15 @@ typedef struct IVector { // Floating point vector maths functions. /// static inline +int veqf(Vector a, Vector b) { + const float e = 0.0001f; + return fabsf(a.x - b.x) + fabsf(a.y - b.y) < e; +} +static inline +int visnanf(Vector a) { + return isnanf(a.x) || isnanf(a.y); +} +static inline Vector vaddf(Vector a, Vector b) { return (Vector){a.x + b.x, a.y + b.y}; } @@ -71,14 +80,20 @@ Vector vperpendicularf(Vector a) { } static inline float vmagnitudef(Vector a) { + if(veqf(a, ZeroVector)) return 0.f; + a.x = fabsf(a.x); + a.y = fabsf(a.y); return sqrtf(a.x*a.x + a.y*a.y); } static inline float vsqrmagnitudef(Vector a) { - return fabsf(a.x*a.x) + fabsf(a.y*a.y); + a.x = fabsf(a.x); + a.y = fabsf(a.y); + return a.x*a.x + a.y*a.y; } static inline Vector vnormalizedf(Vector a) { + if(veqf(a, ZeroVector)) return ZeroVector; return vmulff(a, 1.0/vmagnitudef(a)); } static inline @@ -104,6 +119,22 @@ Vector vrotatef(Vector a, float t) { sinf(t) * a.x + cosf(t) * a.y }; } +static inline +Vector vprojectf(Vector onto, Vector from) { + float dot = vdotf(onto, from); + return vmulff(onto, dot); +} +static inline +Vector vlerpf(Vector start, Vector end, float t) { + if(veqf(start, end)) + return end; + t = fminf(fmaxf(0.0f, t), 1.0f); + return vaddf(start, vmulff(vsubf(end, start), t)); +} +static inline +Vector vmovetowardsf(Vector start, Vector end, float delta) { + return vlerpf(start, end, delta / vdistancef(end, start)); +} static inline IVector vaddi(IVector a, IVector b) {