Initial commit
This commit is contained in:
145
src/engine/physics.c
Normal file
145
src/engine/physics.c
Normal file
@@ -0,0 +1,145 @@
|
||||
#include "engine/physics.h"
|
||||
#include "engine/tilemap.h"
|
||||
#include "config.h"
|
||||
#include <math.h>
|
||||
|
||||
static float s_gravity = DEFAULT_GRAVITY;
|
||||
|
||||
void physics_init(void) {
|
||||
s_gravity = DEFAULT_GRAVITY;
|
||||
}
|
||||
|
||||
void physics_set_gravity(float gravity) {
|
||||
s_gravity = gravity;
|
||||
}
|
||||
|
||||
float physics_get_gravity(void) {
|
||||
return s_gravity;
|
||||
}
|
||||
|
||||
static void resolve_tilemap_x(Body *body, const Tilemap *map) {
|
||||
if (!map) return;
|
||||
|
||||
/* Check tiles the body overlaps after X movement */
|
||||
int left = world_to_tile(body->pos.x);
|
||||
int right = world_to_tile(body->pos.x + body->size.x - 1);
|
||||
int top = world_to_tile(body->pos.y);
|
||||
int bottom = world_to_tile(body->pos.y + body->size.y - 1);
|
||||
|
||||
body->on_wall_left = false;
|
||||
body->on_wall_right = false;
|
||||
|
||||
for (int ty = top; ty <= bottom; ty++) {
|
||||
for (int tx = left; tx <= right; tx++) {
|
||||
if (!tilemap_is_solid(map, tx, ty)) continue;
|
||||
|
||||
float tile_left = tile_to_world(tx);
|
||||
float tile_right = tile_to_world(tx + 1);
|
||||
|
||||
if (body->vel.x > 0) {
|
||||
/* Moving right -> push left */
|
||||
body->pos.x = tile_left - body->size.x;
|
||||
body->vel.x = 0;
|
||||
body->on_wall_right = true;
|
||||
} else if (body->vel.x < 0) {
|
||||
/* Moving left -> push right */
|
||||
body->pos.x = tile_right;
|
||||
body->vel.x = 0;
|
||||
body->on_wall_left = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void resolve_tilemap_y(Body *body, const Tilemap *map) {
|
||||
if (!map) return;
|
||||
|
||||
int left = world_to_tile(body->pos.x);
|
||||
int right = world_to_tile(body->pos.x + body->size.x - 1);
|
||||
int top = world_to_tile(body->pos.y);
|
||||
int bottom = world_to_tile(body->pos.y + body->size.y - 1);
|
||||
|
||||
body->on_ground = false;
|
||||
body->on_ceiling = false;
|
||||
|
||||
for (int ty = top; ty <= bottom; ty++) {
|
||||
for (int tx = left; tx <= right; tx++) {
|
||||
uint32_t flags = tilemap_flags_at(map, tx, ty);
|
||||
if (!(flags & TILE_SOLID)) {
|
||||
/* Check one-way platforms: only block when falling down */
|
||||
if ((flags & TILE_PLATFORM) && body->vel.y > 0) {
|
||||
float tile_top = tile_to_world(ty);
|
||||
float body_bottom = body->pos.y + body->size.y;
|
||||
/* Only resolve if we just crossed into the tile */
|
||||
if (body_bottom - body->vel.y * DT <= tile_top + 2.0f) {
|
||||
body->pos.y = tile_top - body->size.y;
|
||||
body->vel.y = 0;
|
||||
body->on_ground = true;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
float tile_top = tile_to_world(ty);
|
||||
float tile_bottom = tile_to_world(ty + 1);
|
||||
|
||||
if (body->vel.y > 0) {
|
||||
/* Falling -> land on top of tile */
|
||||
body->pos.y = tile_top - body->size.y;
|
||||
body->vel.y = 0;
|
||||
body->on_ground = true;
|
||||
} else if (body->vel.y < 0) {
|
||||
/* Rising -> bonk on ceiling */
|
||||
body->pos.y = tile_bottom;
|
||||
body->vel.y = 0;
|
||||
body->on_ceiling = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void physics_update(Body *body, float dt, const Tilemap *map) {
|
||||
/* Apply gravity */
|
||||
body->vel.y += s_gravity * body->gravity_scale * dt;
|
||||
|
||||
/* Clamp fall speed */
|
||||
if (body->vel.y > MAX_FALL_SPEED) {
|
||||
body->vel.y = MAX_FALL_SPEED;
|
||||
}
|
||||
|
||||
/* Move X, resolve X */
|
||||
body->pos.x += body->vel.x * dt;
|
||||
resolve_tilemap_x(body, map);
|
||||
|
||||
/* Move Y, resolve Y */
|
||||
body->pos.y += body->vel.y * dt;
|
||||
resolve_tilemap_y(body, map);
|
||||
|
||||
/* Ground-stick probe: if not detected as on_ground but vel.y is
|
||||
* near zero (just landed or standing), check one pixel below.
|
||||
* Prevents flickering between grounded/airborne each frame due to
|
||||
* sub-pixel gravity accumulation. */
|
||||
if (!body->on_ground && body->vel.y >= 0 && body->vel.y < s_gravity * dt * 2.0f && map) {
|
||||
int left = world_to_tile(body->pos.x);
|
||||
int right = world_to_tile(body->pos.x + body->size.x - 1);
|
||||
int probe = world_to_tile(body->pos.y + body->size.y + 1.0f);
|
||||
for (int tx = left; tx <= right; tx++) {
|
||||
if (tilemap_is_solid(map, tx, probe)) {
|
||||
body->on_ground = true;
|
||||
body->vel.y = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool physics_overlap(const Body *a, const Body *b) {
|
||||
return physics_aabb_overlap(a->pos, a->size, b->pos, b->size);
|
||||
}
|
||||
|
||||
bool physics_aabb_overlap(Vec2 pos_a, Vec2 size_a, Vec2 pos_b, Vec2 size_b) {
|
||||
return pos_a.x < pos_b.x + size_b.x &&
|
||||
pos_a.x + size_a.x > pos_b.x &&
|
||||
pos_a.y < pos_b.y + size_b.y &&
|
||||
pos_a.y + size_a.y > pos_b.y;
|
||||
}
|
||||
Reference in New Issue
Block a user