#include "engine/camera.h" #include #include void camera_init(Camera *c, float vp_w, float vp_h) { c->pos = vec2_zero(); c->viewport = vec2(vp_w, vp_h); c->bounds_min = vec2_zero(); c->bounds_max = vec2(vp_w, vp_h); /* default: one screen */ c->smoothing = 5.0f; c->deadzone = vec2(30.0f, 20.0f); c->look_ahead = vec2(40.0f, 0.0f); } void camera_set_bounds(Camera *c, float world_w, float world_h) { c->bounds_min = vec2_zero(); c->bounds_max = vec2(world_w, world_h); } void camera_follow(Camera *c, Vec2 target, Vec2 velocity, float dt) { /* Target is the center point we want to follow */ Vec2 desired; desired.x = target.x - c->viewport.x * 0.5f; desired.y = target.y - c->viewport.y * 0.5f; /* Look-ahead: shift camera in the direction of movement */ if (velocity.x > 10.0f) desired.x += c->look_ahead.x; else if (velocity.x < -10.0f) desired.x -= c->look_ahead.x; /* Smooth follow using exponential decay */ float t = 1.0f - expf(-c->smoothing * dt); c->pos = vec2_lerp(c->pos, desired, t); /* Clamp to world bounds */ c->pos.x = clampf(c->pos.x, c->bounds_min.x, c->bounds_max.x - c->viewport.x); c->pos.y = clampf(c->pos.y, c->bounds_min.y, c->bounds_max.y - c->viewport.y); } Vec2 camera_world_to_screen(const Camera *c, Vec2 world_pos) { Vec2 screen = vec2_sub(world_pos, c->pos); /* Apply shake offset */ screen.x += c->shake_offset.x; screen.y += c->shake_offset.y; return screen; } Vec2 camera_screen_to_world(const Camera *c, Vec2 screen_pos) { return vec2_add(screen_pos, c->pos); } /* ── Screen shake ────────────────────────────────── */ static float randf_sym(void) { /* Random float in [-1, 1] */ return ((float)rand() / (float)RAND_MAX) * 2.0f - 1.0f; } void camera_shake(Camera *c, float intensity, float duration) { /* Take the stronger shake if one is already active */ if (intensity > c->shake_intensity || c->shake_timer <= 0) { c->shake_intensity = intensity; c->shake_timer = duration; } } void camera_update_shake(Camera *c, float dt) { if (c->shake_timer <= 0) { c->shake_offset = vec2_zero(); return; } c->shake_timer -= dt; /* Decay intensity as shake expires */ float t = c->shake_timer > 0 ? c->shake_timer / 0.3f : 0; /* ~0.3s reference */ if (t > 1.0f) t = 1.0f; float current = c->shake_intensity * t; /* Random offset, rounded to whole pixels for crisp pixel art */ c->shake_offset.x = roundf(randf_sym() * current); c->shake_offset.y = roundf(randf_sym() * current); if (c->shake_timer <= 0) { c->shake_timer = 0; c->shake_intensity = 0; c->shake_offset = vec2_zero(); } }