power_play/src/sim_step.c
2025-05-14 09:07:30 -05:00

1379 lines
56 KiB
C

#include "sim_step.h"
#include "sim_ent.h"
#include "sim.h"
#include "sys.h"
#include "util.h"
#include "sprite.h"
#include "math.h"
#include "scratch.h"
#include "atomic.h"
#include "app.h"
#include "log.h"
#include "phys.h"
#include "collider.h"
#include "rand.h"
#include "space.h"
#include "bitbuff.h"
#include "host.h"
/* ========================== *
* Sim accel
* ========================== */
struct sim_accel sim_accel_alloc(void)
{
struct sim_accel accel = ZI;
accel.space = space_alloc(1, 256);
return accel;
}
void sim_accel_release(struct sim_accel *accel)
{
space_release(accel->space);
}
void sim_accel_reset(struct sim_snapshot *ss, struct sim_accel *accel)
{
space_reset(accel->space);
/* Reset ent space handles */
for (u64 sim_ent_index = 0; sim_ent_index < ss->num_ents_reserved; ++sim_ent_index) {
struct sim_ent *ent = &ss->ents[sim_ent_index];
if (ent->valid) {
MEMZERO_STRUCT(&ent->space_handle);
}
}
}
/* ========================== *
* Test
* ========================== */
/* TODO: Remove this */
INTERNAL struct sim_ent *spawn_test_employee(struct sim_step_ctx *ctx)
{
struct sim_snapshot *world = ctx->world;
struct sim_ent *root = sim_ent_from_id(world, SIM_ENT_ROOT_ID);
/* Player */
struct sim_ent *employee = sim_ent_nil();
//if (!ctx->extra_spawn) {
{
struct sim_ent *e = sim_ent_alloc_sync_src(root);
struct v2 pos = V2(1, -1);
//struct v2 size = V2(0.5, 0.5);
//struct v2 size = V2(0.5, 0.25);
struct v2 size = V2(1.0, 1.0);
//f32 r = PI / 4;
f32 r = 0;
{
sim_ent_enable_prop(e, SIM_ENT_PROP_TEST);
e->sprite = sprite_tag_from_path(LIT("res/graphics/tim.ase"));
e->mass_unscaled = 10;
e->inertia_unscaled = 5;
}
//e->sprite = sprite_tag_from_path(LIT("res/graphics/box_rounded.ase"));
//e->sprite_span_name = LIT("idle.unarmed");
//e->sprite_span_name = LIT("idle.one_handed");
e->sprite_span_name = LIT("idle.two_handed");
e->layer = SIM_LAYER_SHOULDERS;
e->local_collider.points[0] = V2(0, 0);
e->local_collider.count = 1;
e->local_collider.radius = 0.25f;
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
//xf.bx.y = -1.f;
sim_ent_set_xform(e, xf);
e->linear_ground_friction = 250;
e->angular_ground_friction = 200;
e->friction = 0;
//e->control_force = 500;
e->control_force = 1200;
e->control_force_max_speed = 7;
//e->control_torque = 5000;
e->control_torque = F32_INFINITY;
sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC);
employee = e;
}
/* Player weapon */
if (employee->valid) {
struct sim_ent *e = sim_ent_alloc_sync_src(employee);
e->sprite = sprite_tag_from_path(LIT("res/graphics/gun.ase"));
sim_ent_enable_prop(e, SIM_ENT_PROP_ATTACHED);
e->attach_slice = LIT("attach.wep");
e->layer = SIM_LAYER_RELATIVE_WEAPON;
sim_ent_enable_prop(e, SIM_ENT_PROP_WEAPON);
e->trigger_delay = 1.0f / 10.0f;
//e->trigger_delay = 1.0f / 100.0f;
employee->equipped = e->id;
}
return employee;
}
INTERNAL struct sim_ent *spawn_test_camera(struct sim_snapshot *world, struct sim_ent *follow)
{
struct sim_ent *root = sim_ent_from_id(world, SIM_ENT_ROOT_ID);
struct sim_ent *camera_ent = sim_ent_nil();
if (follow->valid) {
camera_ent = sim_ent_alloc_sync_src(root);
sim_ent_set_xform(camera_ent, XFORM_IDENT);
sim_ent_enable_prop(camera_ent, SIM_ENT_PROP_CAMERA);
sim_ent_enable_prop(camera_ent, SIM_ENT_PROP_CAMERA_ACTIVE);
camera_ent->camera_follow = follow->id;
f32 width = (f32)DEFAULT_CAMERA_WIDTH;
f32 height = (f32)DEFAULT_CAMERA_HEIGHT;
camera_ent->camera_quad_xform = XFORM_TRS(.s = V2(width, height));
}
return camera_ent;
}
INTERNAL void spawn_test_entities(struct sim_step_ctx *ctx, struct v2 offset)
{
struct sim_snapshot *world = ctx->world;
struct sim_ent *root = sim_ent_from_id(world, SIM_ENT_ROOT_ID);
(UNUSED)offset;
root->mass_unscaled = F32_INFINITY;
root->inertia_unscaled = F32_INFINITY;
#if 0
/* Enemy */
if (ctx->is_master) {
struct sim_ent *e = sim_ent_alloc_sync_src(root);
struct v2 pos = V2(1, -2);
pos = v2_add(pos, offset);
f32 r = 0;
struct v2 size = V2(1, 1);
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
sim_ent_set_xform(e, xf);
e->sprite = sprite_tag_from_path(LIT("res/graphics/tim.ase"));
e->sprite_collider_slice = LIT("shape");
e->layer = SIM_LAYER_SHOULDERS;
sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC);
e->mass_unscaled = 100;
e->inertia_unscaled = 10;
e->linear_ground_friction = 250;
e->angular_ground_friction = 200;
}
#endif
/* Enemy */
if (ctx->is_master) {
struct sim_ent *e = spawn_test_employee(ctx);
(UNUSED)e;
}
/* Big box */
#if 0
{
struct sim_ent *e = sim_ent_alloc_local(root);
struct v2 pos = V2(1, -0.5);
pos = v2_add(pos, offset);
f32 r = 0;
struct v2 size = V2(0.5, 0.25);
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
sim_ent_set_xform(e, xf);
e->sprite = sprite_tag_from_path(LIT("res/graphics/box.ase"));
e->sprite_collider_slice = LIT("shape");
e->layer = SIM_LAYER_SHOULDERS;
sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC);
e->mass_unscaled = 100;
e->inertia_unscaled = 50;
e->linear_ground_friction = 100;
e->angular_ground_friction = 50;
}
#endif
/* Tiny box */
#if 0
{
struct sim_ent *e = sim_ent_alloc_sync_src(root);
struct v2 pos = V2(1, -0.5);
pos = v2_add(pos, offset);
f32 r = PI / 4;
struct v2 size = V2(0.5, 0.25);
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
sim_ent_set_xform(e, xf);
e->sprite = sprite_tag_from_path(LIT("res/graphics/bullet.ase"));
e->sprite_collider_slice = LIT("shape");
e->layer = SIM_LAYER_SHOULDERS;
sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC);
e->mass_unscaled = 0.5;
e->inertia_unscaled = 1000;
e->linear_ground_friction = 0.001;
}
#endif
}
INTERNAL void test_clear_level(struct sim_step_ctx *ctx)
{
struct sim_snapshot *world = ctx->world;
for (u64 j = 0; j < world->num_ents_reserved; ++j) {
struct sim_ent *ent = &world->ents[j];
if (ent->valid) {
sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE);
}
}
}
/* ========================== *
* Respond to physics collisions
* ========================== */
INTERNAL PHYS_COLLISION_CALLBACK_FUNC_DEF(on_collision, collision_data_array, step_ctx)
{
struct sim_snapshot *world = step_ctx->world;
struct sim_ent *root = sim_ent_from_id(world, SIM_ENT_ROOT_ID);
for (u64 i = 0; i < collision_data_array.count; ++i) {
struct phys_collision_data *data = &collision_data_array.a[i];
struct phys_contact_constraint *constraint = data->constraint;
struct sim_ent *e0 = sim_ent_from_id(world, data->e0);
struct sim_ent *e1 = sim_ent_from_id(world, data->e1);
if (sim_ent_should_simulate(e0) && sim_ent_should_simulate(e1)) {
/* Bullet hit entity */
if (sim_ent_has_prop(e0, SIM_ENT_PROP_BULLET) || sim_ent_has_prop(e1, SIM_ENT_PROP_BULLET)) {
struct v2 normal = data->normal; /* Impact normal */
struct v2 vrel = v2_neg(data->vrel); /* Impact velocity */
struct sim_ent *target = e0;
struct sim_ent *bullet = e1;
if (sim_ent_has_prop(e0, SIM_ENT_PROP_BULLET)) {
target = e1;
bullet = e0;
normal = v2_neg(normal);
vrel = v2_neg(vrel);
}
struct sim_ent *src = sim_ent_from_id(world, bullet->bullet_src);
if (bullet->bullet_has_hit || sim_ent_id_eq(src->top, target->top)) {
/* Ignore collision if bullet already spent or if weapon and
* target share same top level parent */
/* NOTE: Since bullet is most likely just a sensor skip_solve is probably already true */
constraint->skip_solve = true;
} else {
struct v2 point = data->point;
/* Update bullet */
bullet->bullet_has_hit = true;
sim_ent_enable_prop(bullet, SIM_ENT_PROP_RELEASE);
/* Update tracer */
struct sim_ent *tracer = sim_ent_from_id(world, bullet->bullet_tracer);
if (sim_ent_should_simulate(tracer)) {
struct xform xf = sim_ent_get_xform(tracer);
xf.og = point;
sim_ent_set_xform(tracer, xf);
sim_ent_set_linear_velocity(tracer, V2(0, 0));
}
/* Update target */
struct v2 knockback = v2_mul(v2_norm(vrel), bullet->bullet_knockback);
sim_ent_apply_linear_impulse(target, knockback, point);
/* Create test blood */
/* TODO: Remove this */
{
struct xform xf = XFORM_TRS(.t = point, .r = rand_f64_from_state(&step_ctx->rand, 0, TAU));
struct sim_ent *decal = sim_ent_alloc_sync_src(root);
decal->sprite = sprite_tag_from_path(LIT("res/graphics/blood.ase"));
decal->sprite_tint = RGBA_32_F(1, 1, 1, 0.25f);
decal->layer = SIM_LAYER_FLOOR_DECALS;
sim_ent_set_xform(decal, xf);
f32 perp_range = 0.5;
struct v2 linear_velocity = v2_mul(normal, 0.5);
linear_velocity = v2_add(linear_velocity, v2_mul(v2_perp(normal), rand_f64_from_state(&step_ctx->rand, -perp_range, perp_range)));
f32 angular_velocity_range = 5;
f32 angular_velocity = rand_f64_from_seed(&step_ctx->rand, -angular_velocity_range, angular_velocity_range);
sim_ent_enable_prop(decal, SIM_ENT_PROP_PHYSICAL_KINEMATIC);
sim_ent_set_linear_velocity(decal, linear_velocity);
sim_ent_set_angular_velocity(decal, angular_velocity);
decal->linear_damping = 5.0f;
decal->angular_damping = 5.0f;
}
}
}
}
}
}
/* ========================== *
* Update
* ========================== */
void sim_step(struct sim_step_ctx *ctx)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
b32 is_master = ctx->is_master;
struct sim_snapshot *world = ctx->world;
struct sim_client_store *client_store = world->client->store;
struct sim_client *world_client = world->client;
struct sim_client *user_input_client = ctx->user_input_client;
struct sim_client *publish_client = ctx->publish_client;
struct sim_client *master_client = ctx->master_client;
i64 sim_dt_ns = ctx->sim_dt_ns;
/* ========================== *
* Begin frame
* ========================== */
world->sim_dt_ns = max_i64(0, sim_dt_ns);
world->sim_time_ns += world->sim_dt_ns;
f32 sim_dt = SECONDS_FROM_NS(world->sim_dt_ns);
struct sprite_scope *sprite_frame_scope = sprite_scope_begin();
struct sim_ent *root = sim_ent_from_id(world, SIM_ENT_ROOT_ID);
root->owner = world->client->player_id;
/* ========================== *
* Sync ents from cmd producing clients
* ========================== */
{
/* FIXME: Ensure only cmds are synced to master player */
for (u64 client_index = 0; client_index < client_store->num_clients_reserved; ++client_index) {
struct sim_client *client = &client_store->clients[client_index];
if (client->valid && client != master_client && client != world_client && client != publish_client) {
struct sim_ent *player = sim_ent_from_id(world, client->player_id);
/* Create player if necessary */
if (is_master && !player->valid) {
/* FIXME: Player never released upon disconnect */
player = sim_ent_alloc_sync_src(root);
player->player_client_handle = client->handle;
sim_ent_enable_prop(player, SIM_ENT_PROP_PLAYER);
player->predictor = player->id;
sim_ent_enable_prop(player, SIM_ENT_PROP_ACTIVE);
client->player_id = player->id;
if (client == user_input_client) {
user_input_client->player_id = player->id;
world_client->player_id = player->id;
world->local_player = player->id;
player->owner = player->id;
sim_ent_enable_prop(player, SIM_ENT_PROP_PLAYER_IS_MASTER);
}
logf_info("Created player with id %F for sim client %F. is_master: %F", FMT_UID(player->id.uid), FMT_HANDLE(client->handle), FMT_UINT(sim_ent_has_prop(player, SIM_ENT_PROP_PLAYER_IS_MASTER)));
}
/* Update rtt */
if (is_master && player->valid) {
player->player_last_rtt_ns = client->last_rtt_ns;
player->player_average_rtt_seconds -= player->player_average_rtt_seconds / 200;
player->player_average_rtt_seconds += SECONDS_FROM_NS(client->last_rtt_ns) / 200;
}
/* Sync ents from client */
if (player->valid) {
struct sim_snapshot *src_ss = sim_snapshot_from_tick(client, world->tick);
if (src_ss->valid) {
sim_snapshot_sync_ents(world, src_ss, player->id, 0);
}
}
}
}
/* Mark all incoming ents as sync dsts */
for (u64 i = 0; i < world->num_ents_reserved; ++i) {
struct sim_ent *ent = &world->ents[i];
if (ent->valid && sim_ent_has_prop(ent, SIM_ENT_PROP_SYNC_SRC) && !sim_ent_id_eq(ent->owner, world_client->player_id)) {
sim_ent_disable_prop(ent, SIM_ENT_PROP_SYNC_SRC);
sim_ent_enable_prop(ent, SIM_ENT_PROP_SYNC_DST);
}
}
/* Mark incoming cmds with correct client */
for (u64 i = 0; i < world->num_ents_reserved; ++i) {
struct sim_ent *ent = &world->ents[i];
if (ent->valid && sim_ent_has_prop(ent, SIM_ENT_PROP_CMD) && sim_ent_has_prop(ent, SIM_ENT_PROP_SYNC_DST)) {
ent->cmd_player = ent->owner;
}
}
/* Mark any locally created CMDs as sync sources */
if (!is_master) {
for (u64 i = 0; i < world->num_ents_reserved; ++i) {
struct sim_ent *ent = &world->ents[i];
if (sim_ent_is_valid_and_active(ent) && sim_ent_has_prop(ent, SIM_ENT_PROP_CMD)) {
if (!sim_ent_id_is_nil(ent->cmd_player) && sim_ent_id_eq(ent->cmd_player, world->local_player)) {
sim_ent_enable_prop(ent, SIM_ENT_PROP_SYNC_SRC);
}
}
}
}
}
/* ========================== *
* Release entities at beginning of frame
* ========================== */
sim_ent_release_all_with_prop(world, SIM_ENT_PROP_RELEASE);
sim_accel_reset(world, ctx->accel);
/* ========================== *
* Activate entities
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!ent->valid) continue;
if (sim_ent_has_prop(ent, SIM_ENT_PROP_SYNC_DST) && !sim_ent_is_owner(ent) && !sim_ent_should_predict(ent)) continue;
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_ACTIVE)) {
u64 atick = ent->activation_tick;
if (atick != 0 || world->tick >= atick) {
sim_ent_activate(ent, world->tick);
}
}
}
/* ========================== *
* Reset triggered entities
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGER_NEXT_TICK)) {
sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGER_NEXT_TICK);
sim_ent_enable_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK);
} else if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK)) {
sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK);
}
}
/* ========================== *
* Process client cmds
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *cmd_ent = &world->ents[ent_index];
if (!is_master && !sim_ent_should_simulate(cmd_ent)) continue;
if (sim_ent_has_prop(cmd_ent, SIM_ENT_PROP_CMD)) {
struct sim_ent *player = sim_ent_from_id(world, cmd_ent->cmd_player);
if (sim_ent_should_simulate(player)) {
b32 persist_cmd = false;
if (!is_master && !sim_ent_id_eq(player->id, world->local_player)) {
/* We are not the master and the command is not our own, skip processing */
continue;
}
enum sim_cmd_kind kind = cmd_ent->cmd_kind;
switch (kind) {
case SIM_CMD_KIND_CONTROL:
{
/* Player's will send control cmds a lot, so keep it around to prevent re-creating it each time */
persist_cmd = true;
/* Process control cmd for client */
struct sim_control old_control = player->player_control;
struct sim_control *control = &player->player_control;
*control = cmd_ent->cmd_control;
{
player->player_dbg_drag_start = false;
player->player_dbg_drag_stop = false;
if (v2_len_sq(control->move) > 1) {
/* Cap movement vector magnitude at 1 */
control->move = v2_norm(control->move);
}
/* Determine cursor pos from focus */
{
struct sim_ent_id player_control_ent_id = player->player_control_ent;
struct sim_ent *player_control_ent = sim_ent_from_id(world, player_control_ent_id);
if (player_control_ent->valid || sim_ent_id_is_nil(player_control_ent_id)) {
/* Only update cursor pos if focus ent is valid (or nil) */
player->player_cursor_pos = v2_add(sim_ent_get_xform(player_control_ent).og, player->player_control.focus);
}
}
player->player_hovered_ent = cmd_ent->cmd_control_hovered_ent;
u32 flags = control->flags;
if (flags & SIM_CONTROL_FLAG_DRAG) {
if (!(old_control.flags & SIM_CONTROL_FLAG_DRAG)) {
player->player_dbg_drag_start = true;
}
} else {
if (old_control.flags & SIM_CONTROL_FLAG_DRAG) {
player->player_dbg_drag_stop = true;
}
}
if (flags & SIM_CONTROL_FLAG_CLEAR_ALL) {
if (is_master && !(old_control.flags & SIM_CONTROL_FLAG_CLEAR_ALL)) {
test_clear_level(ctx);
}
}
if (flags & SIM_CONTROL_FLAG_SPAWN_TEST) {
if (!(old_control.flags & SIM_CONTROL_FLAG_SPAWN_TEST)) {
logf_info("Spawning (test)");
u32 count = 1;
f32 spread = 1;
for (u32 j = 0; j < count; ++j) {
spawn_test_entities(ctx, V2(0, (((f32)j / (f32)count) - 0.5) * spread));
}
}
}
if (flags & SIM_CONTROL_FLAG_TILE_TEST) {
#if 0
if (is_master) {
struct v2 cursor_pos = player->player_cursor_pos;
/* FIXME: Negative indices */
struct v2i32 tile_pos = V2I32(cursor_pos.x * SIM_TILES_PER_UNIT_SQRT, cursor_pos.y * SIM_TILES_PER_UNIT_SQRT);
struct v2i32 chunk_pos = V2I32(tile_pos.x * SIM_TILES_PER_CHUNK_SQRT, tile_pos.y * SIM_TILES_PER_CHUNK_SQRT);
struct v2i32 tile_pos_in_chunk = V2I32(tile_pos.x % SIM_TILES_PER_CHUNK_SQRT, tile_pos.y % SIM_TILES_PER_CHUNK_SQRT);
struct sim_ent_id chunk_id = sim_ent_chunk_id_from_chunk_pos(player->id, chunk_pos);
struct sim_ent *chunk_ent = sim_ent_from_id(world, chunk_id);
if (!chunk_ent->valid) {
chunk_ent = sim_ent_alloc_sync_src_with_id(root, chunk_id);
sim_ent_enable_prop(chunk_ent, SIM_ENT_PROP_TILE_CHUNK);
}
struct string old_data = sim_ent_get_tile_chunk_data(chunk_ent);
struct string new_data = ZI;
new_data.len = SIM_TILES_PER_CHUNK_SQRT * SIM_TILES_PER_CHUNK_SQRT;
new_data.text = arena_push_array(scratch.arena, u8, new_data.len);
if (old_data.len == new_data.len) {
MEMCPY(new_data.text, old_data.text, new_data.len);
} else {
MEMZERO(new_data.text, new_data.len);
}
u64 tile_index = tile_pos_in_chunk.x + (tile_pos_in_chunk.y * SIM_TILES_PER_CHUNK_SQRT);
new_data.text[tile_index] = SIM_TILE_KIND_TEST;
sim_ent_set_tile_chunk_data(chunk_ent, new_data);
}
#endif
}
}
} break;
#if 0
case SIM_CMD_KIND_CHAT:
{
struct sim_data_key msg_key = cmd_ent->cmd_chat_msg;
struct string msg = sim_data_from_key(sim_data_store, msg_key);
if (msg.len > 0) {
struct sim_ent *chat_ent = sim_ent_alloc_sync_src(root);
sim_ent_enable_prop(chat_ent, SIM_ENT_PROP_CHAT);
chat_ent->chat_player = player->id;
chat_ent->chat_msg = msg_key;
}
} break;
#endif
default:
{
/* Invalid cmd kind */
ASSERT(false);
} break;
}
/* Release cmd */
if (!persist_cmd) {
sim_ent_enable_prop(cmd_ent, SIM_ENT_PROP_RELEASE);
}
}
}
}
/* ========================== *
* Create client player ents
* ========================== */
if (is_master) {
for (u64 i = 0; i < world->num_ents_reserved; ++i) {
struct sim_ent *ent = &world->ents[i];
if (!sim_ent_should_simulate(ent)) continue;
if (sim_ent_has_prop(ent, SIM_ENT_PROP_PLAYER)) {
/* FIXME: Ents never released when client disconnects */
struct sim_ent *control_ent = sim_ent_from_id(world, ent->player_control_ent);
if (!control_ent->valid) {
control_ent = spawn_test_employee(ctx);
control_ent->predictor = ent->id;
sim_ent_enable_prop(control_ent, SIM_ENT_PROP_CONTROLLED);
ent->player_control_ent = control_ent->id;
control_ent->controlling_client = ent->id;
}
struct sim_ent *camera_ent = sim_ent_from_id(world, ent->player_camera_ent);
if (!camera_ent->valid) {
camera_ent = spawn_test_camera(world, control_ent);
camera_ent->predictor = ent->id;
ent->player_camera_ent = camera_ent->id;
}
}
}
}
/* ========================== *
* Update entity control from client control
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) {
struct sim_ent *player = sim_ent_from_id(world, ent->controlling_client);
if (player->valid) {
ent->control = player->player_control;
/* TODO: Move this */
if (ent->control.flags & SIM_CONTROL_FLAG_FIRE) {
sim_ent_enable_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED);
} else {
sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED);
}
}
}
}
/* ========================== *
* Update entities from sprite
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (sprite_tag_is_nil(ent->sprite)) continue;
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite);
/* Update animation */
{
struct sprite_sheet_span span = sprite_sheet_get_span(sheet, ent->sprite_span_name);
if (ent->animation_last_frame_change_time_ns == 0) {
ent->animation_last_frame_change_time_ns = SECONDS_FROM_NS(world->sim_time_ns);
}
f64 time_in_frame = SECONDS_FROM_NS(world->sim_time_ns - ent->animation_last_frame_change_time_ns);
u64 frame_index = ent->animation_frame;
if (frame_index < span.start || frame_index > span.end) {
frame_index = span.start;
}
if (span.end > 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_last_frame_change_time_ns = world->sim_time_ns;
}
}
ent->animation_frame = frame_index;
}
/* Update sprite local xform */
{
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("pivot"), ent->animation_frame);
struct v2 sprite_size = v2_div(sheet->frame_size, (f32)IMAGE_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 collider from sprite */
if (ent->sprite_collider_slice.len > 0) {
struct xform cxf = ent->sprite_local_xform;
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, ent->sprite_collider_slice, ent->animation_frame);
ent->local_collider = collider_from_quad(xform_mul_quad(cxf, quad_from_rect(slice.rect)));
}
/* Test collider */
#if 0
if (sim_ent_has_prop(ent, SIM_ENT_PROP_TEST)) {
//if ((true)) {
#if 0
ent->local_collider.points[0] = V2(0, 0);
ent->local_collider.count = 1;
ent->local_collider.radius = 0.5;
#elif 0
ent->local_collider.points[0] = v2_with_len(V2(0.08f, 0.17f), 0.15f);
ent->local_collider.points[1] = v2_with_len(V2(-0.07f, -0.2f), 0.15f);
ent->local_collider.count = 2;
ent->local_collider.radius = 0.075f;
#elif 1
#if 0
/* "Bad" winding order */
ent->local_collider.points[0] = V2(-0.15, 0.15);
ent->local_collider.points[1] = V2(0.15, 0.15);
ent->local_collider.points[2] = V2(0, -0.15);
#else
ent->local_collider.points[0] = V2(0, -0.15);
ent->local_collider.points[1] = V2(0.15, 0.15);
ent->local_collider.points[2] = V2(-0.15, 0.15);
#endif
ent->local_collider.count = 3;
ent->local_collider.radius = 0.25;
//ent->local_collider.radius = math_fabs(math_sin(ctx->tick.time) / 3);
#else
//ent->local_collider.radius = 0.5;
ent->local_collider.radius = 0.25;
//ent->local_collider.radius = 0.;
#endif
}
#endif
}
/* ========================== *
* Update attachments
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_ATTACHED)) continue;
struct sim_ent *parent = sim_ent_from_id(world, 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 = sim_ent_get_local_xform(ent);
xf.og = attach_pos;
xf = xform_basis_with_rotation_world(xf, v2_angle(attach_dir) + PI / 2);
sim_ent_set_local_xform(ent, xf);
}
/* ========================== *
* Test
* ========================== */
#if 0
for (u64 ent_index = 0; ent_index < ss_blended->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &ss_blended->ents[ent_index];
if (!sim_ent_is_valid_and_active(ent)) continue;
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_TEST)) continue;
#if 0
if (!ent->test_initialized) {
ent->test_initialized = true;
ent->test_start_local_xform = sim_ent_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);
(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));
struct xform xf = sim_ent_get_local_xform(ent);
xf.og = og;
xf = xform_rotated_to(xf, r);
xf = xform_scaled_to(xf, s);
sim_ent_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);
(UNUSED)og;
(UNUSED)rot;
(UNUSED)scale;
struct xform xf = sim_ent_get_local_xform(ent);
xf = xform_rotated_to(xf, rot);
xf = xform_scaled_to(xf, scale);
sim_ent_set_local_xform(ent, xf);
#endif
}
#endif
/* ========================== *
* Trigger equipped
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED)) {
struct sim_ent *eq = sim_ent_from_id(world, ent->equipped);
if (sim_ent_should_simulate(eq)) {
sim_ent_enable_prop(eq, SIM_ENT_PROP_TRIGGERED_THIS_TICK);
}
}
}
/* ========================== *
* Process triggered entities
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK)) continue;
if ((world->sim_time_ns - ent->last_triggered_ns < NS_FROM_SECONDS(ent->trigger_delay)) && ent->last_triggered_ns != 0) continue;
ent->last_triggered_ns = world->sim_time_ns;
/* Fire weapon */
if (sim_ent_has_prop(ent, SIM_ENT_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, LIT("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 sim_ent *bullet;
{
bullet = sim_ent_alloc_sync_src(root);
bullet->bullet_src = ent->id;
bullet->bullet_src_pos = rel_pos;
bullet->bullet_src_dir = rel_dir;
//bullet->bullet_impulse = 0.75f;
bullet->bullet_impulse = 2.0f;
bullet->bullet_knockback = 10;
bullet->mass_unscaled = 0.04f;
bullet->inertia_unscaled = 0.00001f;
bullet->layer = SIM_LAYER_BULLETS;
#if 1
/* Point collider */
bullet->local_collider.points[0] = V2(0, 0);
bullet->local_collider.count = 1;
#else
bullet->sprite = sprite_tag_from_path(LIT("res/graphics/bullet.ase"));
bullet->sprite_collider_slice = LIT("shape");
#endif
sim_ent_enable_prop(bullet, SIM_ENT_PROP_BULLET);
sim_ent_enable_prop(bullet, SIM_ENT_PROP_SENSOR);
}
/* Spawn tracer */
{
struct sim_ent *tracer = sim_ent_alloc_sync_src(root);
tracer->tracer_fade_duration = 0.025f;
tracer->layer = SIM_LAYER_TRACERS;
sim_ent_enable_prop(tracer, SIM_ENT_PROP_TRACER);
bullet->bullet_tracer = tracer->id;
}
}
}
/* ========================== *
* Create & update motor joints from control move
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) {
struct sim_ent *joint_ent = sim_ent_from_id(world, ent->move_joint);
if (is_master && !sim_ent_is_valid_and_active(joint_ent)) {
joint_ent = sim_ent_alloc_sync_src(root);
joint_ent->predictor = ent->predictor;
joint_ent->mass_unscaled = F32_INFINITY;
joint_ent->inertia_unscaled = F32_INFINITY;
sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOTOR_JOINT);
sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE);
ent->move_joint = joint_ent->id;
struct phys_motor_joint_def def = ZI;
def.e0 = joint_ent->id; /* Re-using joint entity as e0 */
def.e1 = ent->id;
def.correction_rate = 0;
def.max_force = ent->control_force;
def.max_torque = 0;
joint_ent->motor_joint_data = phys_motor_joint_from_def(def);
}
if (sim_ent_should_simulate(joint_ent)) {
sim_ent_set_xform(joint_ent, XFORM_IDENT); /* Reset joint ent position */
sim_ent_set_linear_velocity(joint_ent, v2_mul(v2_clamp_len(ent->control.move, 1), ent->control_force_max_speed));
}
}
}
/* ========================== *
* Create & update motor joints from control focus (aim)
* ========================== */
#if SIM_PLAYER_AIM
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) {
struct xform xf = sim_ent_get_xform(ent);
struct xform sprite_xf = xform_mul(xf, ent->sprite_local_xform);
/* Retrieve / create aim joint */
struct sim_ent *joint_ent = sim_ent_from_id(world, ent->aim_joint);
if (is_master && !sim_ent_is_valid_and_active(joint_ent)) {
joint_ent = sim_ent_alloc_sync_src(root);
joint_ent->predictor = ent->predictor;
joint_ent->mass_unscaled = F32_INFINITY;
joint_ent->inertia_unscaled = F32_INFINITY;
sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_PHYSICAL_KINEMATIC); /* Since we'll be setting velocity manually */
sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOTOR_JOINT);
sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE);
ent->aim_joint = joint_ent->id;
struct phys_motor_joint_def def = ZI;
def.e0 = joint_ent->id; /* Re-using joint entity as e0 */
def.e1 = ent->id;
def.max_force = 0;
def.max_torque = ent->control_torque;
joint_ent->motor_joint_data = phys_motor_joint_from_def(def);
}
if (sim_ent_should_simulate(joint_ent)) {
/* Set correction rate dynamically since motor velocity is only set for one frame */
joint_ent->motor_joint_data.correction_rate = 10 * sim_dt;
/* Solve for final angle using law of sines */
f32 new_angle;
{
struct v2 ent_pos = xf.og;
struct v2 focus_pos = v2_add(ent_pos, ent->control.focus);
struct v2 sprite_hold_pos;
struct v2 sprite_hold_dir;
{
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite);
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("attach.wep"), ent->animation_frame);
sprite_hold_pos = slice.center;
sprite_hold_dir = slice.dir;
}
struct v2 hold_dir = xform_basis_mul_v2(sprite_xf, sprite_hold_dir);
struct v2 hold_pos = xform_mul_v2(sprite_xf, sprite_hold_pos);
if (v2_eq(hold_pos, 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 */
sprite_hold_pos = v2_add(sprite_hold_pos, V2(0, -1));
hold_pos = xform_mul_v2(sprite_xf, sprite_hold_pos);
}
f32 forward_hold_angle_offset;
{
struct xform xf_unrotated = xform_basis_with_rotation_world(xf, 0);
struct v2 hold_pos_unrotated = xform_mul_v2(xf_unrotated, xform_mul_v2(ent->sprite_local_xform, sprite_hold_pos));
forward_hold_angle_offset = v2_angle_from_dirs(V2(0, -1), v2_sub(hold_pos_unrotated, xf_unrotated.og));
}
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);
new_angle = math_unwind_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);
}
f32 new_vel = 0;
if (!F32_IS_NAN(new_angle)) {
const f32 angle_error_allowed = 0.001f;
struct xform joint_xf = sim_ent_get_xform(joint_ent);
f32 diff = math_unwind_angle(new_angle - xform_get_rotation(joint_xf));
if (math_fabs(diff) > angle_error_allowed) {
/* Instantly snap joint ent to new angle */
new_vel = diff / sim_dt;
}
}
sim_ent_set_angular_velocity(joint_ent, new_vel);
}
}
}
#endif
/* ========================== *
* Create motor joints from ground friction (gravity)
* ========================== */
#if 1
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_PHYSICAL_DYNAMIC)) continue;
struct sim_ent *joint_ent = sim_ent_from_id(world, ent->ground_friction_joint);
struct phys_motor_joint_def def = ZI;
def.e0 = root->id;
def.e1 = ent->id;
def.correction_rate = 0;
def.max_force = ent->linear_ground_friction;
def.max_torque = ent->angular_ground_friction;
if (joint_ent->motor_joint_data.max_force != def.max_force || joint_ent->motor_joint_data.max_torque != def.max_torque) {
if (is_master && !sim_ent_is_valid_and_active(joint_ent)) {
joint_ent = sim_ent_alloc_sync_src(root);
joint_ent->predictor = ent->predictor;
sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOTOR_JOINT);
sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE);
joint_ent->motor_joint_data = phys_motor_joint_from_def(def);
ent->ground_friction_joint = joint_ent->id;
}
}
}
#endif
/* ========================== *
* Create mouse joints from client debug drag
* ========================== */
if (is_master) {
for (u64 i = 0; i < world->num_ents_reserved; ++i) {
struct sim_ent *player = &world->ents[i];
if (!sim_ent_should_simulate(player)) continue;
if (!sim_ent_has_prop(player, SIM_ENT_PROP_PLAYER)) continue;
struct v2 cursor = player->player_cursor_pos;
b32 start_dragging = player->player_dbg_drag_start;
b32 stop_dragging = player->player_dbg_drag_stop;
struct sim_ent *joint_ent = sim_ent_from_id(world, player->player_dbg_drag_joint_ent);
struct sim_ent *target_ent = sim_ent_from_id(world, joint_ent->mouse_joint_data.target);
if (stop_dragging) {
target_ent = sim_ent_nil();
} else if (start_dragging) {
target_ent = sim_ent_from_id(world, player->player_hovered_ent);
}
if (sim_ent_should_simulate(target_ent)) {
if (!sim_ent_is_valid_and_active(joint_ent)) {
/* FIXME: Joint ent may never release */
joint_ent = sim_ent_alloc_local(root);
joint_ent->mass_unscaled = F32_INFINITY;
joint_ent->inertia_unscaled = F32_INFINITY;
player->player_dbg_drag_joint_ent = joint_ent->id;
sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOUSE_JOINT);
sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE);
}
struct xform xf = sim_ent_get_xform(target_ent);
f32 mass = target_ent->mass_unscaled * math_fabs(xform_get_determinant(xf));
struct phys_mouse_joint_def def = ZI;
def.target = target_ent->id;
if (sim_ent_id_eq(joint_ent->mouse_joint_data.target, target_ent->id)) {
def.point_local_start = joint_ent->mouse_joint_data.point_local_start;
} else {
def.point_local_start = xform_invert_mul_v2(xf, cursor);
}
def.point_local_end = xform_invert_mul_v2(xf, cursor);
def.max_force = mass * 1000;
joint_ent->mouse_joint_data = phys_mouse_joint_from_def(def);
} else if (sim_ent_is_valid_and_active(joint_ent)) {
joint_ent->mouse_joint_data.target = target_ent->id;
}
}
}
/* ========================== *
* Physics step
* ========================== */
{
struct phys_step_ctx phys = ZI;
phys.sim_step_ctx = ctx;
phys.pre_solve_callback = on_collision;
phys_step(&phys, sim_dt);
}
/* ========================== *
* Update tracers
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_TRACER)) continue;
struct v2 end = sim_ent_get_xform(ent).og;
struct v2 tick_velocity = v2_mul(ent->tracer_start_velocity, sim_dt);
struct v2 gradient_start = v2_add(ent->tracer_gradient_start, tick_velocity);
struct v2 gradient_end = v2_add(ent->tracer_gradient_end, tick_velocity);
if (v2_dot(tick_velocity, v2_sub(gradient_start, end)) > 0) {
/* Tracer has disappeared */
sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE);
}
ent->tracer_gradient_start = gradient_start;
ent->tracer_gradient_end = gradient_end;
}
/* ========================== *
* Initialize bullet kinematics from sources
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_BULLET)) continue;
if (ent->activation_tick == world->tick) {
struct sim_ent *src = sim_ent_from_id(world, ent->bullet_src);
struct xform src_xf = sim_ent_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_with_len(impulse, ent->bullet_impulse);
#if 0
/* Add shooter velocity to bullet */
{
/* TODO: Add angular velocity as well? */
struct sim_ent *top = sim_ent_from_id(ss_blended, src->top);
impulse = v2_add(impulse, v2_mul(top->linear_velocity, dt));
}
#endif
struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(impulse) + PI / 2);
sim_ent_set_xform(ent, xf);
sim_ent_enable_prop(ent, SIM_ENT_PROP_PHYSICAL_KINEMATIC);
sim_ent_apply_linear_impulse_to_center(ent, impulse);
/* Initialize tracer */
struct sim_ent *tracer = sim_ent_from_id(world, ent->bullet_tracer);
if (sim_ent_should_simulate(tracer)) {
sim_ent_set_xform(tracer, xf);
sim_ent_enable_prop(tracer, SIM_ENT_PROP_PHYSICAL_KINEMATIC);
sim_ent_set_linear_velocity(tracer, ent->linear_velocity);
tracer->tracer_start = pos;
tracer->tracer_start_velocity = ent->linear_velocity;
tracer->tracer_gradient_end = pos;
tracer->tracer_gradient_start = v2_sub(pos, v2_mul(ent->linear_velocity, tracer->tracer_fade_duration));
}
/* Spawn quake */
{
struct sim_ent *quake = sim_ent_alloc_sync_src(root);
sim_ent_set_xform(quake, XFORM_POS(pos));
quake->quake_intensity = 0.2f;
quake->quake_fade = quake->quake_intensity / 0.1f;
sim_ent_enable_prop(quake, SIM_ENT_PROP_QUAKE);
}
}
}
/* ========================== *
* Update cameras
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_CAMERA)) continue;
struct xform xf = sim_ent_get_xform(ent);
/* Camera follow */
{
struct sim_ent *follow = sim_ent_from_id(world, ent->camera_follow);
f32 aspect_ratio = 1.0;
{
struct xform quad_xf = xform_mul(sim_ent_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(sim_ent_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)sim_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;
}
/* Camera shake */
{
/* TODO: Update based on distance to quake */
ent->shake = 0;
for (u64 quake_ent_index = 0; quake_ent_index < world->num_ents_reserved; ++quake_ent_index) {
struct sim_ent *quake = &world->ents[quake_ent_index];
if (!sim_ent_should_simulate(quake)) continue;
if (!sim_ent_has_prop(quake, SIM_ENT_PROP_QUAKE)) continue;
ent->shake += quake->quake_intensity;
}
}
sim_ent_set_xform(ent, xf);
}
/* ========================== *
* Update quakes
* ========================== */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!sim_ent_should_simulate(ent)) continue;
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_QUAKE)) continue;
ent->quake_intensity = max_f32(0, ent->quake_intensity - (ent->quake_fade * sim_dt));
if (ent->quake_intensity <= 0) {
sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE);
}
}
/* ========================== *
* Update relative layers
* ========================== */
{
struct temp_arena temp = arena_temp_begin(scratch.arena);
struct sim_ent **stack = arena_push(temp.arena, struct sim_ent *);
u64 stack_count = 1;
*stack = root;
while (stack_count > 0) {
struct sim_ent *parent;
arena_pop(temp.arena, struct sim_ent *, &parent);
--stack_count;
i32 parent_layer = parent->final_layer;
for (struct sim_ent *child = sim_ent_from_id(world, parent->first); child->valid; child = sim_ent_from_id(world, child->next)) {
if (sim_ent_should_simulate(child)) {
child->final_layer = parent_layer + child->layer;
*arena_push(temp.arena, struct sim_ent *) = child;
++stack_count;
}
}
}
arena_temp_end(temp);
}
/* ========================== *
* Release entities at end of frame
* ========================== */
sim_ent_release_all_with_prop(world, SIM_ENT_PROP_RELEASE);
/* ========================== *
* Sync to publish client
* ========================== */
if (publish_client->valid && world->tick > publish_client->last_tick) {
struct sim_snapshot *prev_pub_world = sim_snapshot_from_tick(publish_client, publish_client->last_tick);
struct sim_snapshot *pub_world = sim_snapshot_alloc(publish_client, prev_pub_world, world->tick);
/* Sync */
sim_snapshot_sync_ents(pub_world, world, world_client->player_id, 0);
/* Mark all synced ents as both sync dsts & sync srcs */
for (u64 ent_index = 2; ent_index < pub_world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &pub_world->ents[ent_index];
if (ent->valid) {
sim_ent_enable_prop(ent, SIM_ENT_PROP_SYNC_DST);
sim_ent_enable_prop(ent, SIM_ENT_PROP_SYNC_SRC);
}
}
pub_world->sim_dt_ns = world->sim_dt_ns;
pub_world->sim_time_ns = world->sim_time_ns;
pub_world->continuity_gen = world->continuity_gen;
pub_world->phys_iteration = world->phys_iteration;
pub_world->local_player = world->local_player;
}
/* ========================== *
* End frame
* ========================== */
sprite_scope_end(sprite_frame_scope);
scratch_end(scratch);
}