power_play/src/game.c
2024-10-08 07:35:02 -05:00

1705 lines
62 KiB
C

#include "game.h"
#include "sys.h"
#include "util.h"
#include "world.h"
#include "sprite.h"
#include "sound.h"
#include "mixer.h"
#include "math.h"
#include "scratch.h"
#include "atomic.h"
#include "app.h"
#include "log.h"
#include "gjk.h"
GLOBAL struct {
struct atomic_i32 game_thread_shutdown;
struct sys_thread game_thread;
b32 paused;
struct sprite_scope *sprite_frame_scope;
/* TODO: Remove this (testing) */
b32 first_spawn;
/* Game thread input */
struct sys_mutex game_cmds_mutex;
struct arena game_cmds_arena;
struct entity *root;
/* Ticks */
struct sys_mutex prev_tick_mutex;
struct atomic_u64 prev_tick_id;
struct world prev_tick;
struct world tick;
} G = ZI, DEBUG_ALIAS(G, G_game);
/* ========================== *
* Startup
* ========================== */
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(game_shutdown);
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(game_thread_entry_point, arg);
struct game_startup_receipt game_startup(struct mixer_startup_receipt *mixer_sr,
struct sprite_startup_receipt *sheet_sr,
struct sound_startup_receipt *sound_sr)
{
(UNUSED)mixer_sr;
(UNUSED)sheet_sr;
(UNUSED)sound_sr;
/* Initialize game cmd storage */
G.game_cmds_mutex = sys_mutex_alloc();
G.game_cmds_arena = arena_alloc(GIGABYTE(64));
/* Initialize ticks */
world_alloc(&G.tick);
world_alloc(&G.prev_tick);
/* FIXME: Make the world struct itself readonly as well */
arena_set_readonly(&G.prev_tick.entity_store->arena);
G.prev_tick_mutex = sys_mutex_alloc();
G.tick.timescale = GAME_TIMESCALE;
G.game_thread = sys_thread_alloc(&game_thread_entry_point, NULL, STR("[P2] Game thread"));
app_register_exit_callback(&game_shutdown);
return (struct game_startup_receipt) { 0 };
}
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(game_shutdown)
{
__prof;
atomic_i32_eval_exchange(&G.game_thread_shutdown, true);
sys_thread_wait_release(&G.game_thread);
}
/* ========================== *
* Game cmd
* ========================== */
INTERNAL void push_cmds(struct game_cmd_array cmd_array)
{
struct sys_lock lock = sys_mutex_lock_e(&G.game_cmds_mutex);
struct game_cmd *cmds = arena_push_array(&G.game_cmds_arena, struct game_cmd, cmd_array.count);
MEMCPY(cmds, cmd_array.cmds, cmd_array.count * sizeof(*cmds));
sys_mutex_unlock(&lock);
}
INTERNAL struct game_cmd_array pop_cmds(struct arena *arena)
{
struct game_cmd_array array = ZI;
if (G.game_cmds_arena.pos > 0) {
struct sys_lock lock = sys_mutex_lock_e(&G.game_cmds_mutex);
struct buffer game_cmds_buff = arena_to_buffer(&G.game_cmds_arena);
arena_align(arena, alignof(struct game_cmd));
array.cmds = (struct game_cmd *)arena_push_array(arena, u8, game_cmds_buff.size);
array.count = game_cmds_buff.size / sizeof(struct game_cmd);
MEMCPY(array.cmds, game_cmds_buff.data, game_cmds_buff.size);
arena_reset(&G.game_cmds_arena);
sys_mutex_unlock(&lock);
}
return array;
}
/* ========================== *
* Activate
* ========================== */
INTERNAL void activate_now(struct entity *ent)
{
entity_enable_prop(ent, ENTITY_PROP_ACTIVE);
ent->activation_tick = G.tick.tick_id;
++ent->continuity_gen;
}
/* ========================== *
* Test
* ========================== */
/* TODO: Remove this */
INTERNAL void spawn_test_entities(f32 offset)
{
struct entity *root = entity_from_handle(G.tick.entity_store, G.tick.entity_store->root);
//const f32 offset_all = -20000;
//const f32 offset_all = -5000;
const f32 offset_all = 0;
/* Player */
struct entity *player_ent;
{
//struct v2 pos = V2(0.25, -10);
struct v2 pos = V2(0.25, -7);
//struct v2 pos = V2(0.25, -5.27);
//struct v2 pos = V2(0.25, -2);
//struct v2 pos = V2(1.1230469346046448864129274625156, -1); /* Touching right side of box */
//struct v2 pos = V2(1.1230469346046448864129274625156 - 0.0001, -1); /* Touching right side of box */
//struct v2 pos = V2(0.374142020941, -0.246118023992); /* Touching glitch spot */
pos = v2_add(pos, V2(0, offset));
pos = v2_add(pos, V2(0, offset_all));
//struct v2 size = V2(1, 1);
struct v2 size = V2(0.5, 0.5);
//f32 r = PI;
f32 r = PI / 4;
//f32 r = PI / 3;
//f32 r = 0.05;
//f32 r = PI / 2;
//f32 r = 0;
f32 skew = 0;
struct entity *e = entity_alloc(root);
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
xf = xform_skewed_to(xf, skew);
entity_set_xform(e, xf);
//e->sprite = sprite_tag_from_path(STR("res/graphics/tim.ase"));
e->sprite = sprite_tag_from_path(STR("res/graphics/box.ase"));
//e->sprite_span_name = STR("idle.unarmed");
//e->sprite_span_name = STR("idle.one_handed");
e->sprite_span_name = STR("idle.two_handed");
entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED);
//e->control_force = 4500;
//e->control_force = 1200;
e->control_force = 250;
e->control_torque = 10;
e->control.focus = V2(0, -1);
entity_enable_prop(e, ENTITY_PROP_PHYSICAL);
e->mass_unscaled = 100;
//e->inertia_unscaled = F32_INFINITY;
e->inertia_unscaled = 25;
e->linear_ground_friction = 1000;
e->angular_ground_friction = 100;
//entity_enable_prop(e, ENTITY_PROP_TEST);
player_ent = e;
}
/* Weapon */
{
#if 0
struct v2 pos = V2(1, 0);
struct v2 size = V2(1, 1);
f32 r = PI / 4;
struct entity *e = entity_alloc(root);
#else
struct entity *e = entity_alloc(player_ent);
#endif
e->sprite = sprite_tag_from_path(STR("res/graphics/gun.ase"));
entity_enable_prop(e, ENTITY_PROP_ATTACHED);
e->attach_slice = STR("attach.wep");
entity_enable_prop(e, ENTITY_PROP_WEAPON);
e->trigger_delay = 1.0f / 10.0f;
player_ent->equipped = e->handle;
}
/* Box */
if (!G.first_spawn) {
//struct v2 pos = V2(0.5, -1);
struct v2 pos = V2(0.5, 20);
//struct v2 pos = V2(1, -1);
//struct v2 size = V2(1, 1);
struct v2 size = V2(500, 50);
//f32 rot = PI / 4;
f32 rot = 0;
struct entity *e = entity_alloc(root);
pos = v2_add(pos, V2(0, offset_all));
e->sprite = sprite_tag_from_path(STR("res/graphics/box.ase"));
entity_enable_prop(e, ENTITY_PROP_PHYSICAL);
#if 0
e->mass_unscaled = 500;
e->inertia_unscaled = 500;
#else
e->mass_unscaled = F32_INFINITY;
e->inertia_unscaled = F32_INFINITY;
#endif
e->linear_ground_friction = 10000;
e->angular_ground_friction = 10000;
entity_set_xform(e, XFORM_TRS(.t = pos, .s = size, .r = rot));
}
/* Camera */
if (!G.first_spawn) {
struct entity *e = entity_alloc(root);
entity_set_xform(e, XFORM_IDENT);
entity_enable_prop(e, ENTITY_PROP_CAMERA);
entity_enable_prop(e, ENTITY_PROP_CAMERA_ACTIVE);
e->camera_follow = player_ent->handle;
f32 width = (f32)DEFAULT_CAMERA_WIDTH;
f32 height = (f32)DEFAULT_CAMERA_HEIGHT;
e->camera_quad_xform = XFORM_TRS(.s = V2(width, height));
}
G.first_spawn = true;
}
/* ========================== *
* TESTING
* ========================== */
INTERNAL void create_contact_manifolds(void)
{
/* TODO: Remove this */
static u64 manifold_iteration = 0;
++manifold_iteration;
/* FIXME: I think it's technically possible for manifold entities to swap between iterations */
struct entity_store *store = G.tick.entity_store;
struct sprite_scope *sprite_frame_scope = G.sprite_frame_scope;
struct entity *root = G.root;
for (u64 e0_index = 0; e0_index < store->reserved; ++e0_index) {
struct entity *e0 = &store->entities[e0_index];
if (!entity_is_valid_and_active(e0)) continue;
if (!entity_has_prop(e0, ENTITY_PROP_PHYSICAL)) continue;
/* Calculate entity 0 shape */
struct xform e0_xf = entity_get_xform(e0);
struct quad e0_quad;
struct v2_array e0_poly;
{
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, e0->sprite);
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, STR("shape"), e0->animation_frame);
e0_quad = xform_mul_quad(e0->sprite_local_xform, quad_from_rect(slice.rect));
e0_quad = xform_mul_quad(e0_xf, e0_quad);
e0_poly = (struct v2_array) {
.count = ARRAY_COUNT(e0_quad.e),
.points = e0_quad.e
};
}
for (u64 e1_index = 0; e1_index < store->reserved; ++e1_index) {
struct entity *e1 = &store->entities[e1_index];
if (e1 == e0) continue;
if (!entity_is_valid_and_active(e1)) continue;
if (!entity_has_prop(e1, ENTITY_PROP_PHYSICAL)) continue;
/* TODO: Remove this (temporary stop to prevent double-manifold creation) */
if (e0_index >= e1_index) {
continue;
}
/* TODO: Remove this */
static struct arena dict_arena = ZI;
static struct fixed_dict dict = ZI;
if (dict.buckets_count == 0) {
dict_arena = arena_alloc(GIGABYTE(64));
dict = fixed_dict_init(&dict_arena, 4096);
}
/* Retrieve manifold */
u64 manifold_hash;
struct string manifold_key;
{
struct entity_handle h0 = e0->handle;
struct entity_handle h1 = e1->handle;
manifold_hash = hash_fnv64(HASH_FNV64_BASIS, BUFFER_FROM_STRUCT(&h0));
manifold_hash = hash_fnv64(manifold_hash, BUFFER_FROM_STRUCT(&h1));
manifold_key = STRING_FROM_BUFFER(BUFFER_FROM_STRUCT(&manifold_hash));
}
struct entity *manifold = NULL;
struct entity_handle *entry = fixed_dict_get(&dict, manifold_key);
if (entry) {
struct entity *t = entity_from_handle(store, *entry);
if (entity_is_valid_and_active(t)) {
manifold = t;
}
}
/* Ensure manifold hasn't already been computed this iteration */
if (manifold) {
if (manifold->last_manifold_iteration == manifold_iteration) {
/* Already iterated this manifold from The other entity's perspective, skip */
continue;
}
manifold->last_manifold_iteration = manifold_iteration;
}
/* Calculate entity 1 shape */
struct xform e1_xf = entity_get_xform(e1);
struct quad e1_quad;
struct v2_array e1_poly;
{
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, e1->sprite);
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, STR("shape"), e1->animation_frame);
e1_quad = xform_mul_quad(e1->sprite_local_xform, quad_from_rect(slice.rect));
e1_quad = xform_mul_quad(e1_xf, e1_quad);
e1_poly = (struct v2_array) {
.count = ARRAY_COUNT(e1_quad.e),
.points = e1_quad.e
};
}
struct gjk_contact_points_result res = gjk_contact_points(e0_poly, e1_poly);
/* Parts of algorithm are hard-coded to support 2 contact points */
CT_ASSERT(ARRAY_COUNT(manifold->contacts) == 2);
CT_ASSERT(ARRAY_COUNT(res.points) == 2);
/* TODO: Remove this (debugging) */
if (manifold) {
manifold->prototype = res.prototype;
manifold->simplex = res.simplex;
manifold->solved = res.solved;
manifold->path = res.path;
}
if (res.num_points > 0) {
if (!manifold) {
manifold = entity_alloc(root);
manifold->manifold_e0 = e0->handle;
manifold->manifold_e1 = e1->handle;
/* TODO: Should we recalculate normal as more contact points are added? */
entity_enable_prop(manifold, ENTITY_PROP_MANIFOLD);
activate_now(manifold);
if (entry) {
*entry = manifold->handle;
} else {
entry = arena_push(&dict_arena, struct entity_handle);
*entry = manifold->handle;
fixed_dict_set(&dict_arena, &dict, manifold_key, entry);
}
/* TODO: Remove this (debugging) */
{
manifold->prototype = res.prototype;
manifold->simplex = res.simplex;
manifold->solved = res.solved;
}
}
struct v2 normal = res.normal;
struct v2 tangent = v2_perp(normal);
manifold->manifold_normal = normal;
/* Delete old contacts that are no longer present */
for (u32 i = 0; i < manifold->num_contacts; ++i) {
struct contact *old = &manifold->contacts[i];
u32 id = old->id;
b32 found = false;
for (u32 j = 0; j < res.num_points; ++j) {
if (res.points[j].id == id) {
found = true;
break;
}
}
if (!found) {
/* Delete contact by replacing with last in array */
*old = manifold->contacts[--manifold->num_contacts];
--i;
}
}
/* Update / insert returned contacts */
for (u32 i = 0; i < res.num_points; ++i) {
struct gjk_contact_point *res_point = &res.points[i];
struct v2 point = res_point->point;
f32 sep = res_point->separation;
u32 id = res_point->id;
struct contact *contact = NULL;
/* Match */
for (u32 j = 0; j < manifold->num_contacts; ++j) {
struct contact *t = &manifold->contacts[j];
if (t->id == id) {
contact = t;
break;
}
}
if (contact) {
/* Update existing */
#if !GAME_PHYSICS_ENABLE_WARM_STARTING
contact->normal_impulse = 0;
contact->tangent_impulse = 0;
#endif
} else {
/* Insert new */
contact = &manifold->contacts[manifold->num_contacts++];
MEMZERO_STRUCT(contact);
contact->id = id;
}
contact->point_local_e0 = xform_invert_mul_v2(e0_xf, point);
contact->point_local_e1 = xform_invert_mul_v2(e1_xf, point);
contact->starting_separation = sep;
{
f32 scale0 = math_fabs(xform_get_determinant(e0_xf));
f32 scale1 = math_fabs(xform_get_determinant(e1_xf));
f32 m0 = e0->mass_unscaled * scale0;
f32 m1 = e1->mass_unscaled * scale1;
f32 i0 = e0->inertia_unscaled * scale0;
f32 i1 = e1->inertia_unscaled * scale1;
f32 inv_m0 = 1.f / m0;
f32 inv_m1 = 1.f / m1;
f32 inv_i0 = 1.f / i0;
f32 inv_i1 = 1.f / i1;
struct v2 vcp0 = v2_sub(point, e0_xf.og);
struct v2 vcp1 = v2_sub(point, e1_xf.og);
/* Normal mass */
{
f32 vcp0_wedge = v2_wedge(vcp0, normal);
f32 vcp1_wedge = v2_wedge(vcp1, normal);
f32 k = (inv_m0 + inv_m1) + (inv_i0 * vcp0_wedge * vcp0_wedge) + (inv_i1 * vcp1_wedge * vcp1_wedge);
//f32 k = ((inv_m0 + inv_m1) + (inv_i0 * vcp0_wedge * vcp0_wedge) + (inv_i1 * vcp1_wedge * vcp1_wedge)) * manifold->num_contacts;
contact->inv_normal_mass = k > 0.0f ? 1.0f / k : 0.0f;
}
/* Tangent mass */
{
f32 vcp0_wedge = v2_wedge(vcp0, tangent);
f32 vcp1_wedge = v2_wedge(vcp1, tangent);
f32 k = (inv_m0 + inv_m1) + (inv_i0 * vcp0_wedge * vcp0_wedge) + (inv_i1 * vcp1_wedge * vcp1_wedge);
//f32 k = ((inv_m0 + inv_m1) + (inv_i0 * vcp0_wedge * vcp0_wedge) + (inv_i1 * vcp1_wedge * vcp1_wedge)) * manifold->num_contacts;
contact->inv_tangent_mass = k > 0.0f ? 1.0f / k : 0.0f;
}
contact->inv_m0 = inv_m0;
contact->inv_m1 = inv_m1;
contact->inv_i0 = inv_i0;
contact->inv_i1 = inv_i1;
}
}
} else if (manifold) {
/* No longer colliding, delete manifold */
#if 0
manifold->num_contacts = 0;
entity_enable_prop(manifold, ENTITY_PROP_RELEASE);
#else
if (res.solved) {
manifold->prototype.len = 0;
manifold->simplex.len = 0;
}
manifold->num_contacts = 0;
#endif
}
}
}
}
INTERNAL void warm_start_contacts(void)
{
struct entity_store *store = G.tick.entity_store;
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *manifold = &store->entities[entity_index];
if (!entity_is_valid_and_active(manifold)) continue;
if (!entity_has_prop(manifold, ENTITY_PROP_MANIFOLD)) continue;
u32 num_contacts = manifold->num_contacts;
struct entity *e0 = entity_from_handle(store, manifold->manifold_e0);
struct entity *e1 = entity_from_handle(store, manifold->manifold_e1);
if (num_contacts > 0 && entity_is_valid_and_active(e0) && entity_is_valid_and_active(e1)) {
struct xform e0_xf = entity_get_xform(e0);
struct xform e1_xf = entity_get_xform(e1);
struct v2 v0 = e0->linear_velocity;
struct v2 v1 = e1->linear_velocity;
f32 w0 = e0->angular_velocity;
f32 w1 = e1->angular_velocity;
/* Warm start */
struct v2 normal = manifold->manifold_normal;
struct v2 tangent = v2_perp(normal);
for (u32 i = 0; i < num_contacts; ++i) {
struct contact *contact = &manifold->contacts[i];
struct v2 p0 = xform_mul_v2(e0_xf, contact->point_local_e0);
struct v2 p1 = xform_mul_v2(e1_xf, contact->point_local_e1);
struct v2 vcp0 = v2_sub(p0, e0_xf.og);
struct v2 vcp1 = v2_sub(p1, e1_xf.og);
struct v2 impulse = v2_add(v2_mul(normal, contact->normal_impulse), v2_mul(tangent, contact->tangent_impulse));
v0 = v2_sub(v0, v2_mul(impulse, contact->inv_m0));
v1 = v2_add(v1, v2_mul(impulse, contact->inv_m1));
w0 -= v2_wedge(vcp0, impulse) * contact->inv_i0;
w1 += v2_wedge(vcp1, impulse) * contact->inv_i1;
}
e0->linear_velocity = v0;
e0->angular_velocity = w0;
e1->linear_velocity = v1;
e1->angular_velocity = w1;
}
}
}
struct soft_result {
f32 bias_rate;
f32 mass_scale;
f32 impulse_scale;
};
INTERNAL struct soft_result make_soft(f32 hertz, f32 zeta, f32 h)
{
if (hertz == 0.0f) {
return (struct soft_result) { .mass_scale = 1.0f };
} else {
f32 omega = 2.0f * PI * hertz;
f32 a1 = 2.0f * zeta + h * omega;
f32 a2 = h * omega * a1;
f32 a3 = 1.0f / (1.0f + a2);
return (struct soft_result) {
.bias_rate = omega / a1,
.mass_scale = a2 * a3,
.impulse_scale = 1.0f / (1.0f + a2)
};
}
}
INTERNAL void solve_collisions(f32 dt, b32 apply_bias)
{
#if 1
struct entity_store *store = G.tick.entity_store;
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *manifold = &store->entities[entity_index];
if (!entity_is_valid_and_active(manifold)) continue;
if (!entity_has_prop(manifold, ENTITY_PROP_MANIFOLD)) continue;
struct entity *e0 = entity_from_handle(store, manifold->manifold_e0);
struct entity *e1 = entity_from_handle(store, manifold->manifold_e1);
struct v2 v0 = e0->linear_velocity;
struct v2 v1 = e1->linear_velocity;
f32 w0 = e0->angular_velocity;
f32 w1 = e1->angular_velocity;
u32 num_contacts = manifold->num_contacts;
if (num_contacts > 0 && entity_is_valid_and_active(e0) && entity_is_valid_and_active(e1)) {
struct xform e0_xf = entity_get_xform(e0);
struct xform e1_xf = entity_get_xform(e1);
/* Normal impulse */
struct v2 normal = manifold->manifold_normal;
for (u32 contact_index = 0; contact_index < num_contacts; ++contact_index) {
struct contact *contact = &manifold->contacts[contact_index];
struct v2 p0 = xform_mul_v2(e0_xf, contact->point_local_e0);
struct v2 p1 = xform_mul_v2(e1_xf, contact->point_local_e1);
struct v2 vcp0 = v2_sub(p0, e0_xf.og);
struct v2 vcp1 = v2_sub(p1, e1_xf.og);
f32 separation = v2_dot(v2_sub(p1, p0), normal) + contact->starting_separation;
f32 velocity_bias = 0.0f;
f32 mass_scale = 1.0f;
f32 impulse_scale = 0.0f;
if (separation > 0.0f) {
velocity_bias = separation / dt;
} else if (apply_bias) {
/* Soft constraint */
f32 contact_damping_ratio = 10.0f;
f32 contact_hertz = (GAME_FPS * GAME_PHYSICS_SUBSTEPS) / 8.f;
struct soft_result softness = make_soft(contact_hertz, contact_damping_ratio, dt);
f32 contact_pushout_velocity = 3.0f;
velocity_bias = max_f32(softness.bias_rate * separation, -contact_pushout_velocity);
mass_scale = softness.mass_scale;
impulse_scale = softness.impulse_scale;
}
struct v2 vel0 = v2_add(v0, v2_perp_mul(vcp0, w0));
struct v2 vel1 = v2_add(v1, v2_perp_mul(vcp1, w1));
struct v2 vrel = v2_sub(vel0, vel1);
f32 k = contact->inv_normal_mass;
/* (to be applied along n) */
f32 vn = v2_dot(vrel, normal);
f32 j = ((k * mass_scale) * (vn - velocity_bias)) - (contact->normal_impulse * impulse_scale);
f32 old_impulse = contact->normal_impulse;
f32 new_impulse = max_f32(contact->normal_impulse + j, 0);
f32 delta = new_impulse - old_impulse;
contact->normal_impulse = new_impulse;
struct v2 impulse = v2_mul(normal, delta);
v0 = v2_sub(v0, v2_mul(impulse, contact->inv_m0));
v1 = v2_add(v1, v2_mul(impulse, contact->inv_m1));
w0 -= v2_wedge(vcp0, impulse) * contact->inv_i0;
w1 += v2_wedge(vcp1, impulse) * contact->inv_i1;
}
/* Tangent impulse */
struct v2 tangent = v2_perp(normal);
for (u32 contact_index = 0; contact_index < num_contacts; ++contact_index) {
struct contact *contact = &manifold->contacts[contact_index];
struct v2 p0 = xform_mul_v2(e0_xf, contact->point_local_e0);
struct v2 p1 = xform_mul_v2(e1_xf, contact->point_local_e1);
struct v2 vcp0 = v2_sub(p0, e0_xf.og);
struct v2 vcp1 = v2_sub(p1, e1_xf.og);
struct v2 vel0 = v2_add(v0, v2_perp_mul(vcp0, w0));
struct v2 vel1 = v2_add(v1, v2_perp_mul(vcp1, w1));
struct v2 vrel = v2_sub(vel0, vel1);
f32 k = contact->inv_tangent_mass;
/* (to be applied along t) */
f32 vt = v2_dot(vrel, tangent);
f32 j = vt * k;
f32 friction = 0.6f;
//f32 friction = 1.0f;
//f32 friction = F32_INFINITY;
f32 max_friction = friction * contact->normal_impulse;
f32 old_impulse = contact->tangent_impulse;
f32 new_impulse = clamp_f32(old_impulse + j, -max_friction, max_friction);
f32 delta = new_impulse - old_impulse;
contact->tangent_impulse = new_impulse;
struct v2 impulse = v2_mul(tangent, delta);
v0 = v2_sub(v0, v2_mul(impulse, contact->inv_m0));
v1 = v2_add(v1, v2_mul(impulse, contact->inv_m1));
w0 -= v2_wedge(vcp0, impulse) * contact->inv_i0;
w1 += v2_wedge(vcp1, impulse) * contact->inv_i1;
}
e0->linear_velocity = v0;
e0->angular_velocity = w0;
e1->linear_velocity = v1;
e1->angular_velocity = w1;
}
}
#else
(UNUSED)make_soft;
(UNUSED)dt;
(UNUSED)apply_bias;
#endif
}
INTERNAL void integrate_velocities_from_forces(f32 dt)
{
struct entity_store *store = G.tick.entity_store;
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (!entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) continue;
struct xform xf = entity_get_xform(ent);
f32 det_abs = math_fabs(xform_get_determinant(xf));
f32 mass = ent->mass_unscaled * det_abs;
f32 inertia = ent->inertia_unscaled * det_abs;
/* Determine force & torque acceleration */
struct v2 force_accel = v2_mul(v2_div(ent->force, mass), dt);
f32 torque_accel = (ent->torque / inertia) * dt;
/* Integrate */
ent->linear_velocity = v2_add(ent->linear_velocity, force_accel);
ent->angular_velocity += torque_accel;
/* Clamp velocities */
ent->linear_velocity = v2_clamp_len(ent->linear_velocity, GAME_MAX_LINEAR_VELOCITY);
ent->angular_velocity = clamp_f32(ent->angular_velocity, -GAME_MAX_ANGULAR_VELOCITY, GAME_MAX_ANGULAR_VELOCITY);
/* Reset forces */
ent->force = V2(0, 0);
ent->torque = 0;
}
}
INTERNAL void integrate_positions_from_velocities(f32 dt)
{
struct entity_store *store = G.tick.entity_store;
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (!entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) continue;
/* Clamp velocities */
ent->linear_velocity = v2_clamp_len(ent->linear_velocity, GAME_MAX_LINEAR_VELOCITY);
ent->angular_velocity = clamp_f32(ent->angular_velocity, -GAME_MAX_ANGULAR_VELOCITY, GAME_MAX_ANGULAR_VELOCITY);
struct xform xf = entity_get_xform(ent);
struct v2 tick_linear_velocity = v2_mul(ent->linear_velocity, dt);
f32 tick_angular_velocity = ent->angular_velocity * dt;
xf.og = v2_add(xf.og, tick_linear_velocity);
xf = xform_rotated(xf, tick_angular_velocity);
entity_set_xform(ent, xf);
}
}
/* ========================== *
* Update
* ========================== */
INTERNAL void publish_game_tick(void)
{
__prof;
struct sys_lock lock = sys_mutex_lock_e(&G.prev_tick_mutex);
arena_set_readwrite(&G.prev_tick.entity_store->arena);
{
world_copy_replace(&G.prev_tick, &G.tick);
}
arena_set_readonly(&G.prev_tick.entity_store->arena);
atomic_u64_eval_exchange(&G.prev_tick_id, G.prev_tick.tick_id);
sys_mutex_unlock(&lock);
}
INTERNAL void game_update(struct game_cmd_array game_cmds)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
/* ========================== *
* Begin frame
* ========================== */
++G.tick.tick_id;
G.tick.tick_ts = sys_timestamp();
G.tick.dt = max_f64(0.0, (1.0 / GAME_FPS) * G.tick.timescale);
G.tick.time += G.tick.dt;
f64 dt = G.tick.dt;
f64 time = G.tick.time;
G.sprite_frame_scope = sprite_scope_begin();
G.root = entity_from_handle(G.tick.entity_store, G.tick.entity_store->root);
struct entity *root = G.root;
struct entity_store *store = G.tick.entity_store;
struct sprite_scope *sprite_frame_scope = G.sprite_frame_scope;
(UNUSED)dt;
(UNUSED)time;
/* ========================== *
* Spawn test entities
* ========================== */
/* TODO: remove this (testing) */
/* Initialize entities */
{
static b32 run = 0;
if (!run) {
run = 1;
spawn_test_entities(0);
}
}
/* ========================== *
* Process global game cmds
* ========================== */
for (u64 cmd_index = 0; cmd_index < game_cmds.count; ++cmd_index) {
struct game_cmd cmd = game_cmds.cmds[cmd_index];
switch (cmd.kind) {
/* Clear level */
case GAME_CMD_KIND_CLEAR_ALL:
{
logf_info("Clearing level");
entity_store_release_all_entities(store);
G.first_spawn = false;
} break;
/* Spawn test */
case GAME_CMD_KIND_SPAWN_TEST:
{
logf_info("Spawning (test)");
#if 1
for (u32 i = 0; i < 50; ++i) {
spawn_test_entities(-(f32)i);
}
#else
spawn_test_entities(0);
#endif
} break;
default: break;
};
}
/* ========================== *
* Activate entities
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!ent->valid) continue;
if (!entity_has_prop(ent, ENTITY_PROP_ACTIVE)) {
u64 atick = ent->activation_tick;
if (atick != 0 || G.tick.tick_id >= atick) {
activate_now(ent);
}
}
}
/* ========================== *
* Reset triggered entities
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (entity_has_prop(ent, ENTITY_PROP_TRIGGER_NEXT_TICK)) {
entity_disable_prop(ent, ENTITY_PROP_TRIGGER_NEXT_TICK);
entity_enable_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK);
} else if (entity_has_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK)) {
entity_disable_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK);
}
}
/* ========================== *
* Update animations from sprite
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (sprite_tag_is_nil(ent->sprite)) continue;
/* Update animation */
{
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite);
struct sprite_sheet_span span = sprite_sheet_get_span(sheet, ent->sprite_span_name);
f64 time_in_frame = ent->animation_time_in_frame + G.tick.dt;
u64 frame_index = ent->animation_frame;
if (frame_index < span.start || frame_index > span.end) {
frame_index = span.start;
}
struct sprite_sheet_frame frame = sprite_sheet_get_frame(sheet, frame_index);
while (time_in_frame > frame.duration) {
time_in_frame -= frame.duration;
++frame_index;
if (frame_index > span.end) {
/* Loop animation */
frame_index = span.start;
}
frame = sprite_sheet_get_frame(sheet, frame_index);
}
ent->animation_time_in_frame = time_in_frame;
ent->animation_frame = frame_index;
}
/* Update sprite local xform */
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite);
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, STR("pivot"), ent->animation_frame);
struct v2 sprite_size = v2_div(sheet->frame_size, (f32)PIXELS_PER_UNIT);
struct v2 dir = v2_mul_v2(sprite_size, slice.dir);
f32 rot = v2_angle(dir) + PI / 2;
struct xform xf = XFORM_IDENT;
xf = xform_rotated(xf, -rot);
xf = xform_scaled(xf, sprite_size);
xf = xform_translated(xf, v2_neg(slice.center));
ent->sprite_local_xform = xf;
}
/* ========================== *
* Update attachments
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (!entity_has_prop(ent, ENTITY_PROP_ATTACHED)) continue;
struct entity *parent = entity_from_handle(store, ent->parent);
struct sprite_tag parent_sprite = parent->sprite;
struct sprite_sheet *parent_sheet = sprite_sheet_from_tag_await(sprite_frame_scope, parent_sprite);
struct xform parent_sprite_xf = parent->sprite_local_xform;
struct sprite_sheet_slice attach_slice = sprite_sheet_get_slice(parent_sheet, ent->attach_slice, parent->animation_frame);
struct v2 attach_pos = xform_mul_v2(parent_sprite_xf, attach_slice.center);
struct v2 attach_dir = xform_basis_mul_v2(parent_sprite_xf, attach_slice.dir);
struct xform xf = entity_get_local_xform(ent);
xf.og = attach_pos;
xf = xform_rotated_to(xf, v2_angle(attach_dir) + PI / 2);
entity_set_local_xform(ent, xf);
}
/* ========================== *
* Update control from player cmds
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
/* Process cmds */
struct v2 move = ent->control.move;
struct v2 focus = ent->control.focus;
b32 firing = entity_has_prop(ent, ENTITY_PROP_TRIGGERING_EQUIPPED);
for (u64 i = 0; i < game_cmds.count; ++i) {
struct game_cmd cmd = game_cmds.cmds[i];
b32 start = cmd.state == GAME_CMD_STATE_START;
b32 stop = cmd.state == GAME_CMD_STATE_STOP;
(UNUSED)start;
(UNUSED)stop;
/* TODO: Combine movement from multiple inputs? E.G. a sudden
* start and immediate stop cmd should still move the player a
* tad. */
switch (cmd.kind) {
case GAME_CMD_KIND_PLAYER_MOVE:
{
move = cmd.move_dir;
focus = v2_sub(cmd.aim_pos, entity_get_xform(ent).og);
} break;
case GAME_CMD_KIND_PLAYER_FIRE:
{
if (start) {
firing = true;
} else if (stop) {
firing = false;
}
} break;
default: break;
}
}
/* Movement */
if (v2_len_sq(move) > 1) {
/* Cap movement vector magnitude at 1 */
move = v2_norm(move);
}
ent->control.move = move;
ent->control.focus = focus;
/* Firing */
if (firing) {
entity_enable_prop(ent, ENTITY_PROP_TRIGGERING_EQUIPPED);
} else {
entity_disable_prop(ent, ENTITY_PROP_TRIGGERING_EQUIPPED);
}
}
}
/* ========================== *
* Test
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (!entity_has_prop(ent, ENTITY_PROP_TEST)) continue;
#if 0
if (!ent->test_initialized) {
ent->test_initialized = true;
ent->test_start_local_xform = entity_get_local_xform(ent);
ent->test_start_sprite_xform = ent->sprite_local_xform;
}
f32 t = (f32)time;
struct v2 og = v2_mul(V2(math_cos(t), math_sin(t)), 3);
f32 r = t * 3;
struct v2 s = V2(1 + (math_fabs(math_sin(t * 5)) * 3), 1);
f32 skew = t * PI / 10;
(UNUSED)og;
(UNUSED)r;
(UNUSED)s;
og = v2_add(og, ent->test_start_local_xform.og);
r += xform_get_rotation(ent->test_start_local_xform);
s = v2_add(s, xform_get_scale(ent->test_start_local_xform));
skew += xform_get_skew(ent->test_start_local_xform);
struct xform xf = entity_get_local_xform(ent);
xf.og = og;
xf = xform_rotated_to(xf, r);
xf = xform_scaled_to(xf, s);
xf = xform_skewed_to(xf, skew);
entity_set_local_xform(ent, xf);
#else
f32 t = (f32)time;
struct v2 og = v2_mul(V2(math_cos(t), math_sin(t)), 3);
f32 rot = t * PI / 3;
struct v2 scale = V2(1 + (math_fabs(math_sin(t * 5)) * 3), 1);
f32 skew = t * PI / 10;
(UNUSED)og;
(UNUSED)rot;
(UNUSED)scale;
(UNUSED)skew;
struct xform xf = entity_get_local_xform(ent);
//xf = xform_rotated_to(xf, rot);
//xf = xform_scaled_to(xf, scale);
xf = xform_skewed_to(xf, skew);
entity_set_local_xform(ent, xf);
#endif
}
/* ========================== *
* Trigger equipped
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (entity_has_prop(ent, ENTITY_PROP_TRIGGERING_EQUIPPED)) {
struct entity *eq = entity_from_handle(store, ent->equipped);
if (entity_is_valid_and_active(eq)) {
entity_enable_prop(eq, ENTITY_PROP_TRIGGERED_THIS_TICK);
}
}
}
/* ========================== *
* Process triggered entities
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (!entity_has_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK)) continue;
if ((time - ent->last_triggered < ent->trigger_delay) && ent->last_triggered != 0) continue;
ent->last_triggered = time;
/* Fire weapon */
if (entity_has_prop(ent, ENTITY_PROP_WEAPON)) {
struct sprite_tag sprite = ent->sprite;
u32 animation_frame = ent->animation_frame;
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, sprite);
struct xform sprite_local_xform = ent->sprite_local_xform;
struct sprite_sheet_slice out_slice = sprite_sheet_get_slice(sheet, STR("out"), animation_frame);
struct v2 rel_pos = xform_mul_v2(sprite_local_xform, out_slice.center);
struct v2 rel_dir = xform_basis_mul_v2(sprite_local_xform, out_slice.dir);
{
/* Spawn bullet */
struct entity *bullet = entity_alloc(root);
bullet->sprite = sprite_tag_from_path(STR("res/graphics/bullet.ase"));
bullet->bullet_src = ent->handle;
bullet->bullet_src_pos = rel_pos;
bullet->bullet_src_dir = rel_dir;
bullet->bullet_impulse = 0.25f;
//bullet->bullet_impulse = 1.f;
bullet->mass_unscaled = 0.04f;
bullet->inertia_unscaled = 0.00001f;
entity_enable_prop(bullet, ENTITY_PROP_BULLET);
}
}
}
/* ========================== *
* Create forces from control move
* ========================== */
#if 0
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
struct v2 move = ent->control.move;
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
struct entity *f = entity_alloc(ent);
entity_enable_prop(f, ENTITY_PROP_FORCE);
f->force = v2_mul(move, ent->control_force);
activate_now(f);
}
}
#else
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
struct v2 move = ent->control.move;
struct v2 force = v2_mul(move, ent->control_force);
entity_apply_force_to_center(ent, force);
}
}
#endif
/* ========================== *
* Create forces from control focus (aim)
* ========================== */
#if 0
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
struct xform xf = entity_get_xform(ent);
/* Solve for final angle using law of sines */
f32 final_xf_angle;
{
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite);
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, STR("attach.wep"), ent->animation_frame);
f32 forward_hold_angle_offset;
{
struct xform xf_unrotated = xform_rotated_to(xf, 0);
struct v2 hold_pos_unrotated = xform_mul_v2(xf_unrotated, xform_mul_v2(ent->sprite_local_xform, slice.center));
forward_hold_angle_offset = v2_angle_from_dirs(V2(0, -1), v2_sub(hold_pos_unrotated, xf_unrotated.og));
}
struct v2 ent_pos = xf.og;
struct v2 focus_pos = v2_add(ent_pos, ent->control.focus);
struct v2 hold_pos;
{
struct xform hold_pos_xf = xform_translated(ent->sprite_local_xform, slice.center);
hold_pos_xf = xform_mul(xf, hold_pos_xf);
struct v2 hold_pos_xf_dir = xform_basis_mul_v2(hold_pos_xf, slice.dir);
hold_pos_xf = xform_rotated_to(hold_pos_xf, v2_angle(hold_pos_xf_dir) + PI / 2);
if (v2_eq(hold_pos_xf.og, ent_pos)) {
/* If hold pos is same as origin (E.G if pivot is being used as hold pos), then move hold pos forward a tad to avoid issue */
hold_pos_xf = xform_translated(hold_pos_xf, V2(0, -1));
}
hold_pos = hold_pos_xf.og;
}
struct v2 hold_dir = xform_basis_mul_v2(xf, xform_basis_mul_v2(ent->sprite_local_xform, slice.dir));
struct v2 hold_ent_dir = v2_sub(ent_pos, hold_pos);
struct v2 focus_ent_dir = v2_sub(ent_pos, focus_pos);
f32 hold_ent_len = v2_len(hold_ent_dir);
f32 focus_ent_len = v2_len(focus_ent_dir);
f32 final_hold_angle_btw_ent_and_focus = v2_angle_from_dirs(hold_ent_dir, hold_dir);
f32 final_focus_angle_btw_ent_and_hold = math_asin((math_sin(final_hold_angle_btw_ent_and_focus) * hold_ent_len) / focus_ent_len);
f32 final_ent_angle_btw_focus_and_hold = PI - (final_focus_angle_btw_ent_and_hold + final_hold_angle_btw_ent_and_focus);
final_xf_angle = v2_angle_from_dirs(V2(0, -1), v2_sub(focus_pos, ent_pos)) + final_ent_angle_btw_focus_and_hold - forward_hold_angle_offset;
}
if (!F32_IS_NAN(final_xf_angle)) {
const f32 angle_error_allowed = 0.001;
if (math_fabs(final_xf_angle - v2_angle(xf.bx)) > angle_error_allowed) {
xf = xform_rotated_to(xf, final_xf_angle);
}
}
entity_set_xform(ent, xf);
}
}
#else
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
struct xform xf = entity_get_xform(ent);
/* Solve for final angle using law of sines */
f32 final_xf_angle;
{
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite);
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, STR("attach.wep"), ent->animation_frame);
f32 forward_hold_angle_offset;
{
struct xform xf_unrotated = xform_rotated_to(xf, 0);
struct v2 hold_pos_unrotated = xform_mul_v2(xf_unrotated, xform_mul_v2(ent->sprite_local_xform, slice.center));
forward_hold_angle_offset = v2_angle_from_dirs(V2(0, -1), v2_sub(hold_pos_unrotated, xf_unrotated.og));
}
struct v2 ent_pos = xf.og;
struct v2 focus_pos = v2_add(ent_pos, ent->control.focus);
struct v2 hold_pos;
{
struct xform hold_pos_xf = xform_translated(ent->sprite_local_xform, slice.center);
hold_pos_xf = xform_mul(xf, hold_pos_xf);
struct v2 hold_pos_xf_dir = xform_basis_mul_v2(hold_pos_xf, slice.dir);
hold_pos_xf = xform_rotated_to(hold_pos_xf, v2_angle(hold_pos_xf_dir) + PI / 2);
if (v2_eq(hold_pos_xf.og, ent_pos)) {
/* If hold pos is same as origin (E.G if pivot is being used as hold pos), then move hold pos forward a tad to avoid issue */
hold_pos_xf = xform_translated(hold_pos_xf, V2(0, -1));
}
hold_pos = hold_pos_xf.og;
}
struct v2 hold_dir = xform_basis_mul_v2(xf, xform_basis_mul_v2(ent->sprite_local_xform, slice.dir));
struct v2 hold_ent_dir = v2_sub(ent_pos, hold_pos);
struct v2 focus_ent_dir = v2_sub(ent_pos, focus_pos);
f32 hold_ent_len = v2_len(hold_ent_dir);
f32 focus_ent_len = v2_len(focus_ent_dir);
f32 final_hold_angle_btw_ent_and_focus = v2_angle_from_dirs(hold_ent_dir, hold_dir);
f32 final_focus_angle_btw_ent_and_hold = math_asin((math_sin(final_hold_angle_btw_ent_and_focus) * hold_ent_len) / focus_ent_len);
f32 final_ent_angle_btw_focus_and_hold = PI - (final_focus_angle_btw_ent_and_hold + final_hold_angle_btw_ent_and_focus);
final_xf_angle = v2_angle_from_dirs(V2(0, -1), v2_sub(focus_pos, ent_pos)) + final_ent_angle_btw_focus_and_hold - forward_hold_angle_offset;
}
if (!F32_IS_NAN(final_xf_angle)) {
const f32 angle_error_allowed = 0.001f;
f32 cur_angle = v2_angle(xf.bx);
f32 diff = final_xf_angle - cur_angle;
if (math_fabs(diff) > angle_error_allowed) {
#if 0
#if 0
/* Create force */
struct entity *f = entity_alloc(ent);
entity_enable_prop(f, ENTITY_PROP_TORQUE);
i32 dir = math_fsign(diff);
f32 damp = math_fabs(math_fmod(diff, 2 * PI)) / (2 * PI);
f32 torque = ent->control_torque * dir * damp;
f->torque = torque;
activate_now(f);
#else
/* TODO: Remove this (testing) */
/* Create force */
if (!ent->test_torque_applied) {
ent->test_torque_applied = true;
entity_apply_angular_impulse(ent, 10);
}
#endif
#endif
}
}
}
}
#endif
/* ========================== *
* Create ground friction force (gravity)
* ========================== */
#if 0
/* TODO: Do this globally rather than creating entities for constant forces */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (!entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) continue;
#if 1
if (ent->linear_ground_friction != 0) {
/* Linear velocity */
{
struct v2 linear_velocity = ent->linear_velocity;
if (!v2_is_zero(linear_velocity)) {
/* FIXME: Incorrect behavior at low FPS & low entity density */
const f32 clamp_epsilon = 0.01;
f32 linear_velocity_len = v2_len(linear_velocity);
if (linear_velocity_len >= clamp_epsilon) {
f32 force_len = -linear_velocity_len * ent->linear_ground_friction;
struct v2 force = v2_mul(v2_norm(linear_velocity), force_len);
entity_apply_force_to_center(ent, force);
} else {
/* If linear_velocity is below clamp_epsilon, stop entity movement. */
struct xform xf = entity_get_xform(ent);
f32 mass = ent->mass_unscaled * math_fabs(xform_get_determinant(xf));
struct v2 impulse = v2_mul(v2_neg(linear_velocity), mass);
entity_apply_linear_impulse_to_center(ent, impulse);
}
}
}
}
if (ent->angular_ground_friction != 0) {
/* Angular velocity */
{
f32 angular_velocity = ent->angular_velocity;
if (angular_velocity != 0) {
/* FIXME: Incorrect (just testing) */
const f32 clamp_epsilon = 0.001;
if (math_fabs(angular_velocity) >= clamp_epsilon) {
f32 torque = -angular_velocity * ent->angular_ground_friction;
entity_apply_torque(ent, torque);
} else {
/* If angular_velocity is below clamp_epsilon, stop entity movement. */
struct xform xf = entity_get_xform(ent);
f32 inertia = ent->inertia_unscaled * math_fabs(xform_get_determinant(xf));
f32 impulse = -angular_velocity * inertia;
entity_apply_angular_impulse(ent, impulse);
}
}
}
}
#else
/* TODO: Remove this (gravity test) */
struct xform xf = entity_get_xform(ent);
f32 mass = ent->mass_unscaled * math_fabs(xform_get_determinant(xf));
entity_apply_force_to_center(ent, V2(0, 9.81 * mass));
#endif
}
#endif
/* ========================== *
* Physics
* ========================== */
{
integrate_velocities_from_forces(dt);
create_contact_manifolds();
(UNUSED)create_contact_manifolds;
(UNUSED)solve_collisions;
(UNUSED)integrate_positions_from_velocities;
(UNUSED)warm_start_contacts;
f32 substep_dt = dt / GAME_PHYSICS_SUBSTEPS;
for (u32 i = 0; i < GAME_PHYSICS_SUBSTEPS; ++i) {
#if 1
#if GAME_PHYSICS_ENABLE_WARM_STARTING
warm_start_contacts();
#endif
solve_collisions(substep_dt, true);
integrate_positions_from_velocities(substep_dt);
solve_collisions(substep_dt, false); /* Relaxation */
#else
//solve_collisions(substep_dt, true);
solve_collisions(substep_dt, false);
integrate_positions_from_velocities(substep_dt);
#endif
}
}
/* ========================== *
* Initialize bullet kinematics from sources
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
/* FIXME: Apply src entity velocity to bullet velocity */
if (entity_has_prop(ent, ENTITY_PROP_BULLET) && ent->activation_tick == G.tick.tick_id) {
struct entity *src = entity_from_handle(store, ent->bullet_src);
struct xform src_xf = entity_get_xform(src);
struct v2 pos = xform_mul_v2(src_xf, ent->bullet_src_pos);
struct v2 impulse = xform_basis_mul_v2(src_xf, ent->bullet_src_dir);
impulse = v2_mul(v2_norm(impulse), ent->bullet_impulse);
struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(impulse) + PI / 2);
entity_set_xform(ent, xf);
entity_enable_prop(ent, ENTITY_PROP_PHYSICAL);
entity_apply_linear_impulse_to_center(ent, impulse);
}
}
/* ========================== *
* Update camera position
* ========================== */
#if 0
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
/* Camera follow */
if (entity_has_prop(ent, ENTITY_PROP_CAMERA)) {
struct entity *follow = entity_from_handle(store, ent->camera_follow);
struct xform xf = entity_get_xform(ent);
f32 aspect_ratio = 1.0;
{
struct xform quad_xf = xform_mul(entity_get_xform(ent), ent->camera_quad_xform);
struct v2 camera_size = xform_get_scale(quad_xf);
if (!v2_is_zero(camera_size)) {
aspect_ratio = camera_size.x / camera_size.y;
}
}
f32 ratio_y = 0.33f;
f32 ratio_x = ratio_y / aspect_ratio;
struct v2 camera_focus_dir = v2_mul_v2(follow->control.focus, V2(ratio_x, ratio_y));
struct v2 camera_focus_pos = v2_add(entity_get_xform(follow).og, camera_focus_dir);
ent->camera_xform_target = xf;
ent->camera_xform_target.og = camera_focus_pos;
/* Lerp camera */
if (ent->camera_applied_lerp_continuity_gen_plus_one == ent->camera_lerp_continuity_gen + 1) {
f32 t = 1 - math_pow(2.f, -20.f * (f32)G.tick.dt);
xf = xform_lerp(xf, ent->camera_xform_target, t);
} else {
/* Skip lerp */
xf = ent->camera_xform_target;
}
ent->camera_applied_lerp_continuity_gen_plus_one = ent->camera_lerp_continuity_gen + 1;
entity_set_xform(ent, xf);
}
#else
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
/* Camera follow */
if (entity_has_prop(ent, ENTITY_PROP_CAMERA)) {
struct entity *follow = entity_from_handle(store, ent->camera_follow);
struct xform xf = entity_get_xform(ent);
f32 aspect_ratio = 1.0;
{
struct xform quad_xf = xform_mul(entity_get_xform(ent), ent->camera_quad_xform);
struct v2 camera_size = xform_get_scale(quad_xf);
if (!v2_is_zero(camera_size)) {
aspect_ratio = camera_size.x / camera_size.y;
}
}
f32 ratio_y = 0.33f;
f32 ratio_x = ratio_y / aspect_ratio;
struct v2 camera_focus_dir = v2_mul_v2(follow->control.focus, V2(ratio_x, ratio_y));
struct v2 camera_focus_pos = v2_add(entity_get_xform(follow).og, camera_focus_dir);
ent->camera_xform_target = xf;
ent->camera_xform_target.og = camera_focus_pos;
/* Lerp camera */
if (ent->camera_applied_lerp_continuity_gen_plus_one == ent->camera_lerp_continuity_gen + 1) {
f32 t = 1 - math_pow(2.f, -20.f * (f32)G.tick.dt);
xf = xform_lerp(xf, ent->camera_xform_target, t);
} else {
/* Skip lerp */
xf = ent->camera_xform_target;
}
ent->camera_applied_lerp_continuity_gen_plus_one = ent->camera_lerp_continuity_gen + 1;
entity_set_xform(ent, xf);
}
#endif
}
/* ========================== *
* Update sound emitters
* ========================== */
/* TODO: Sound entities should be created by game thread, but played by the
* user thread. This is so sounds play at the correct time on the user
* thread regardless of interp delay. */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (entity_has_prop(ent, ENTITY_PROP_TEST_SOUND_EMITTER)) {
struct mixer_desc desc = ent->sound_desc;
desc.speed = G.tick.timescale;
desc.pos = entity_get_xform(ent).og;
struct sound *sound = sound_load_async(ent->sound_name, 0);
b32 played = ent->sound_handle.gen != 0;
if (sound) {
if (!played) {
ent->sound_handle = mixer_play_ex(sound, desc);
} else {
mixer_track_set(ent->sound_handle, desc);
}
}
}
}
/* ========================== *
* Release entities
* ========================== */
/* TODO: Breadth first iteration to only release parent entities (since
* child entities will be released along with parent anyway) */
{
struct temp_arena temp = arena_temp_begin(scratch.arena);
struct entity **ents_to_release = arena_dry_push(temp.arena, struct entity *);
u64 ents_to_release_count = 0;
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!ent->valid) continue;
if (entity_has_prop(ent, ENTITY_PROP_RELEASE)) {
*arena_push(temp.arena, struct entity *) = ent;
++ents_to_release_count;
}
}
for (u64 i = 0; i < ents_to_release_count; ++i) {
struct entity *ent = ents_to_release[i];
if (ent->valid) {
entity_release(store, ent);
}
}
arena_temp_end(temp);
}
/* ========================== *
* Publish tick
* ========================== */
/* Publish tick */
publish_game_tick();
__profframe("Game");
/* ========================== *
* End frame cache scopes
* ========================== */
sprite_scope_end(sprite_frame_scope);
scratch_end(scratch);
}
/* ========================== *
* Game thread
* ========================== */
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(game_thread_entry_point, arg)
{
struct temp_arena scratch = scratch_begin_no_conflict();
(UNUSED)arg;
sys_timestamp_t last_frame_ts = 0;
f64 target_dt = GAME_FPS > (0) ? (1.0 / GAME_FPS) : 0;
while (!atomic_i32_eval(&G.game_thread_shutdown)) {
__profscope(game_update_w_sleep);
struct temp_arena temp = arena_temp_begin(scratch.arena);
sleep_frame(last_frame_ts, target_dt);
last_frame_ts = sys_timestamp();
{
struct game_cmd_array game_cmds = pop_cmds(temp.arena);
if (!G.paused) {
game_update(game_cmds);
}
/* Check for pause / next frame cmds */
for (u64 i = 0; i < game_cmds.count; ++i) {
struct game_cmd cmd = game_cmds.cmds[i];
switch (cmd.kind) {
case GAME_CMD_KIND_PAUSE: {
G.paused = !G.paused;
} break;
case GAME_CMD_KIND_STEP: {
if (G.paused) {
G.paused = false;
game_update(game_cmds);
G.paused = true;
}
} break;
default: break;
}
}
}
arena_temp_end(temp);
}
scratch_end(scratch);
}
/* ========================== *
* Interface
* ========================== */
void game_get_latest_tick(struct world *dest)
{
__prof;
struct sys_lock lock = sys_mutex_lock_s(&G.prev_tick_mutex);
world_copy_replace(dest, &G.prev_tick);
sys_mutex_unlock(&lock);
}
u64 game_get_latest_tick_id(void)
{
return atomic_u64_eval(&G.prev_tick_id);
}
void game_push_cmds(struct game_cmd_array cmd_array)
{
push_cmds(cmd_array);
}