1358 lines
52 KiB
C
1358 lines
52 KiB
C
#include "sim.h"
|
|
#include "sim_ent.h"
|
|
#include "sim_client.h"
|
|
#include "sim_msg.h"
|
|
#include "sim_snapshot.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 "rng.h"
|
|
#include "space.h"
|
|
#include "byteio.h"
|
|
#include "host.h"
|
|
|
|
/* ========================== *
|
|
* Ctx
|
|
* ========================== */
|
|
|
|
|
|
struct sim_ctx *sim_ctx_alloc(struct sprite_startup_receipt *sprite_sr,
|
|
struct phys_startup_receipt *phys_sr,
|
|
struct host_startup_receipt *host_sr,
|
|
u16 host_port)
|
|
{
|
|
struct arena arena = arena_alloc(GIGABYTE(64));
|
|
struct sim_ctx *ctx = arena_push_zero(&arena, struct sim_ctx);
|
|
ctx->arena = arena;
|
|
|
|
(UNUSED)sprite_sr;
|
|
(UNUSED)phys_sr;
|
|
(UNUSED)host_sr;
|
|
|
|
/* Intialize host */
|
|
ctx->host = host_alloc(host_port);
|
|
|
|
/* Create bookkeeping */
|
|
ctx->contact_lookup = sim_ent_lookup_alloc(4096);
|
|
#if COLLIDER_DEBUG
|
|
ctx->collision_debug_lookup = sim_ent_lookup_alloc(4096);
|
|
#endif
|
|
ctx->space = space_alloc(SPACE_CELL_SIZE, SPACE_CELL_BUCKETS_SQRT);
|
|
|
|
/* Create emtpy snapshot */
|
|
sim_snapshot_alloc(&ctx->world);
|
|
ctx->world.world_timescale = SIM_TIMESCALE;
|
|
|
|
return ctx;
|
|
}
|
|
|
|
void sim_ctx_release(struct sim_ctx *ctx)
|
|
{
|
|
__prof;
|
|
|
|
/* Release snapshot */
|
|
sim_snapshot_release(&ctx->world);
|
|
|
|
/* Release bookkeeping */
|
|
space_release(ctx->space);
|
|
#if COLLIDER_DEBUG
|
|
sim_ent_lookup_release(&ctx->collision_debug_lookup);
|
|
#endif
|
|
sim_ent_lookup_release(&ctx->contact_lookup);
|
|
|
|
host_release(ctx->host);
|
|
arena_release(&ctx->arena);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Test
|
|
* ========================== */
|
|
|
|
/* TODO: Remove this */
|
|
|
|
INTERNAL void spawn_test_entities(struct sim_ctx *ctx)
|
|
{
|
|
struct sim_ent *root = sim_ent_from_handle(ctx->world.ent_store, ctx->world.ent_store->root);
|
|
root->mass_unscaled = F32_INFINITY;
|
|
root->inertia_unscaled = F32_INFINITY;
|
|
|
|
/* Enemy */
|
|
{
|
|
struct sim_ent *e = sim_ent_alloc(root);
|
|
|
|
struct v2 pos = V2(1, -2);
|
|
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 = 10;
|
|
e->inertia_unscaled = 10;
|
|
e->linear_ground_friction = 250;
|
|
e->angular_ground_friction = 200;
|
|
}
|
|
|
|
|
|
/* Big box */
|
|
#if 0
|
|
{
|
|
struct sim_ent *e = sim_ent_alloc(root);
|
|
|
|
struct v2 pos = V2(1, -0.5);
|
|
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 = 10;
|
|
e->linear_ground_friction = 100;
|
|
e->angular_ground_friction = 5;
|
|
}
|
|
#endif
|
|
|
|
/* Tiny box */
|
|
#if 0
|
|
{
|
|
struct sim_ent *e = sim_ent_alloc(root);
|
|
|
|
struct v2 pos = V2(1, -0.5);
|
|
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 struct sim_ent *spawn_test_player(struct sim_ctx *ctx)
|
|
{
|
|
struct sim_ent_store *store = ctx->world.ent_store;
|
|
struct sim_ent *root = sim_ent_from_handle(store, store->root);
|
|
|
|
/* Player */
|
|
struct sim_ent *player_ent = sim_ent_nil();
|
|
//if (!ctx->extra_spawn) {
|
|
{
|
|
|
|
struct sim_ent *e = sim_ent_alloc(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->local_collider.points[0] = v2_with_len(V2(0.08f, 0.17f), 0.15f);
|
|
e->local_collider.points[1] = v2_with_len(V2(-0.07f, -0.2f), 0.15f);
|
|
e->local_collider.count = 2;
|
|
e->local_collider.radius = 0.075f;
|
|
|
|
//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;
|
|
|
|
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->control_force = 500;
|
|
e->control_force = 500;
|
|
//e->control_force_max_speed = 4;
|
|
e->control_torque = 5000;
|
|
e->control_force_max_speed = 4;
|
|
|
|
sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC);
|
|
|
|
player_ent = e;
|
|
}
|
|
|
|
/* Player weapon */
|
|
if (player_ent->valid) {
|
|
struct sim_ent *e = sim_ent_alloc(player_ent);
|
|
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;
|
|
|
|
player_ent->equipped = e->handle;
|
|
}
|
|
|
|
return player_ent;
|
|
}
|
|
|
|
INTERNAL struct sim_ent *spawn_test_player_camera(struct sim_ctx *ctx, struct sim_ent *player_ent)
|
|
{
|
|
struct sim_ent_store *store = ctx->world.ent_store;
|
|
struct sim_ent *root = sim_ent_from_handle(store, store->root);
|
|
|
|
struct sim_ent *camera_ent = sim_ent_nil();
|
|
if (player_ent->valid) {
|
|
camera_ent = sim_ent_alloc(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 = player_ent->handle;
|
|
|
|
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;
|
|
}
|
|
|
|
/* ========================== *
|
|
* Release entities
|
|
* ========================== */
|
|
|
|
INTERNAL void release_entities_with_prop(struct sim_ctx *ctx, enum sim_ent_prop prop)
|
|
{
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
struct sim_ent_store *store = ctx->world.ent_store;
|
|
struct space *space = ctx->space;
|
|
|
|
struct sim_ent **ents_to_release = arena_dry_push(scratch.arena, struct sim_ent *);
|
|
u64 ents_to_release_count = 0;
|
|
for (u64 ent_index = 0; ent_index < store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &store->entities[ent_index];
|
|
if (!ent->valid) continue;
|
|
|
|
if (sim_ent_has_prop(ent, prop)) {
|
|
*arena_push(scratch.arena, struct sim_ent *) = ent;
|
|
++ents_to_release_count;
|
|
}
|
|
}
|
|
|
|
/* Release references */
|
|
for (u64 i = 0; i < ents_to_release_count; ++i) {
|
|
struct sim_ent *ent = ents_to_release[i];
|
|
/* Release space entry */
|
|
{
|
|
struct space_entry *space_entry = space_entry_from_handle(space, ent->space_handle);
|
|
if (space_entry->valid) {
|
|
space_entry_release(space_entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Release from store */
|
|
/* TODO: Breadth first iteration to only release parent entities (since
|
|
* child entities will be released along with parent anyway) */
|
|
for (u64 i = 0; i < ents_to_release_count; ++i) {
|
|
struct sim_ent *ent = ents_to_release[i];
|
|
if (ent->valid) {
|
|
sim_ent_release(store, ent);
|
|
}
|
|
}
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Respond to physics collisions
|
|
* ========================== */
|
|
|
|
INTERNAL PHYS_COLLISION_CALLBACK_FUNC_DEF(on_collision, collision_data_array, udata)
|
|
{
|
|
struct sim_ctx *ctx = (struct sim_ctx *)udata;
|
|
struct sim_ent_store *store = ctx->world.ent_store;
|
|
struct sim_ent *root = sim_ent_from_handle(store, store->root);
|
|
|
|
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_handle(store, data->e0);
|
|
struct sim_ent *e1 = sim_ent_from_handle(store, data->e1);
|
|
|
|
if (sim_ent_is_valid_and_active(e0) && sim_ent_is_valid_and_active(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_handle(store, bullet->bullet_src);
|
|
|
|
if (bullet->bullet_has_hit || sim_ent_handle_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_THIS_TICK);
|
|
|
|
/* Update tracer */
|
|
struct sim_ent *tracer = sim_ent_from_handle(store, bullet->bullet_tracer);
|
|
if (sim_ent_is_valid_and_active(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 = rng_rand_f32(0, TAU));
|
|
struct sim_ent *decal = sim_ent_alloc(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), rng_rand_f32(-perp_range, perp_range)));
|
|
|
|
f32 angular_velocity_range = 5;
|
|
f32 angular_velocity = rng_rand_f32(-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_update(struct sim_ctx *ctx, i64 target_dt_ns)
|
|
{
|
|
__prof;
|
|
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
/* ========================== *
|
|
* Begin frame
|
|
* ========================== */
|
|
|
|
++ctx->world.tick;
|
|
|
|
ctx->world.real_dt_ns = max_i64(0, target_dt_ns);
|
|
ctx->world.real_time_ns += ctx->world.real_dt_ns;
|
|
|
|
ctx->world.world_dt_ns = max_i64(0, target_dt_ns * ctx->world.world_timescale);
|
|
ctx->world.world_time_ns += ctx->world.world_dt_ns;
|
|
|
|
f64 sim_time = SECONDS_FROM_NS(ctx->world.world_time_ns);
|
|
ctx->sprite_frame_scope = sprite_scope_begin();
|
|
|
|
f64 real_dt = SECONDS_FROM_NS(ctx->world.real_dt_ns);
|
|
f64 real_time = SECONDS_FROM_NS(ctx->world.real_time_ns);
|
|
f64 world_dt = SECONDS_FROM_NS(ctx->world.world_dt_ns);
|
|
f64 world_time = SECONDS_FROM_NS(ctx->world.world_time_ns);
|
|
(UNUSED)real_dt;
|
|
(UNUSED)real_time;
|
|
(UNUSED)world_dt;
|
|
(UNUSED)world_time;
|
|
|
|
struct sim_ent *root = sim_ent_from_handle(ctx->world.ent_store, ctx->world.ent_store->root);;
|
|
struct sim_ent_store *ent_store = ctx->world.ent_store;
|
|
struct space *space = ctx->space;
|
|
struct sprite_scope *sprite_frame_scope = ctx->sprite_frame_scope;
|
|
|
|
/* ========================== *
|
|
* Spawn test entities
|
|
* ========================== */
|
|
|
|
/* TODO: remove this (testing) */
|
|
/* Initialize entities */
|
|
{
|
|
static b32 run = 0;
|
|
if (!run) {
|
|
run = 1;
|
|
spawn_test_entities(ctx);
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Release entities
|
|
* ========================== */
|
|
|
|
release_entities_with_prop(ctx, SIM_ENT_PROP_RELEASE_NEXT_TICK);
|
|
|
|
/* ========================== *
|
|
* Activate entities
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!ent->valid) continue;
|
|
|
|
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_ACTIVE)) {
|
|
u64 atick = ent->activation_tick;
|
|
if (atick != 0 || ctx->world.tick >= atick) {
|
|
sim_ent_activate(ent, ctx->world.tick);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Reset triggered entities
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_is_valid_and_active(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 host events into sim cmds
|
|
* ========================== */
|
|
|
|
struct sim_cmd_list *client_cmds;
|
|
{
|
|
host_update(ctx->host);
|
|
struct host_event_array host_events = host_pop_events(scratch.arena, ctx->host);
|
|
|
|
struct sim_cmd_list sim_cmds = ZI;
|
|
sim_cmds_from_host_events(scratch.arena, host_events, &sim_cmds);
|
|
|
|
/* Create connecting clients */
|
|
for (struct sim_cmd *cmd = sim_cmds.first; cmd; cmd = cmd->next) {
|
|
enum sim_cmd_kind kind = cmd->kind;
|
|
struct host_channel_id channel_id = cmd->channel_id;
|
|
struct sim_client *client = sim_client_from_channel_id(ctx->world.client_store, channel_id);
|
|
if (!client->valid && kind == SIM_CMD_KIND_SIM_CLIENT_CONNECT && !host_channel_id_is_nil(channel_id)) {
|
|
client = sim_client_alloc(ctx->world.client_store, channel_id);
|
|
/* TODO: Create player ent not here */
|
|
/* FIXME: Player ent never released upon disconnect */
|
|
struct sim_ent *player_ent = spawn_test_player(ctx);
|
|
sim_ent_enable_prop(player_ent, SIM_ENT_PROP_CONTROLLED);
|
|
struct sim_ent *camera_ent = spawn_test_player_camera(ctx, player_ent);
|
|
client->control_ent = player_ent->handle;
|
|
client->camera_ent = camera_ent->handle;
|
|
player_ent->controlling_client = client->handle;
|
|
}
|
|
}
|
|
|
|
/* Split cmds by client */
|
|
client_cmds = arena_push_array_zero(scratch.arena, struct sim_cmd_list, ctx->world.client_store->num_reserved);
|
|
{
|
|
struct sim_cmd *cmd = sim_cmds.first;
|
|
while (cmd) {
|
|
struct sim_cmd *next = cmd->next;
|
|
struct host_channel_id channel_id = cmd->channel_id;
|
|
struct sim_client *client = sim_client_from_channel_id(ctx->world.client_store, channel_id);
|
|
if (client->valid) {
|
|
struct sim_cmd_list *cmd_list = &client_cmds[client->handle.idx];
|
|
if (cmd_list->last) {
|
|
cmd_list->last->next = cmd;
|
|
} else {
|
|
cmd_list->first = cmd;
|
|
}
|
|
cmd_list->last = cmd;
|
|
cmd->next = NULL;
|
|
}
|
|
cmd = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Process sim cmds
|
|
* ========================== */
|
|
|
|
/* Process client cmds */
|
|
for (u64 i = 0; i < ctx->world.client_store->num_reserved; ++i) {
|
|
struct sim_client *client = &ctx->world.client_store->clients[i];
|
|
if (client->valid) {
|
|
struct host_channel_id channel_id = client->channel_id;
|
|
struct sim_cmd_list cmds = client_cmds[client->handle.idx];
|
|
struct sim_control *control = &client->control;
|
|
|
|
client->dbg_drag_start = false;
|
|
client->dbg_drag_stop = false;
|
|
|
|
for (struct sim_cmd *cmd = cmds.first; cmd; cmd = cmd->next) {
|
|
enum sim_cmd_kind kind = cmd->kind;
|
|
b32 start = cmd->state == SIM_CMD_STATE_START;
|
|
b32 stop = cmd->state == SIM_CMD_STATE_STOP;
|
|
switch (kind) {
|
|
/* TODO: Combine movement from multiple inputs? E.ctx-> a sudden
|
|
* start and immediate stop cmd should still move the player a
|
|
* tad. */
|
|
case SIM_CMD_KIND_PLAYER_CONTROL:
|
|
{
|
|
struct v2 move = cmd->move_dir;
|
|
struct v2 focus = cmd->aim_dir;
|
|
if (v2_len_sq(move) > 1) {
|
|
/* Cap movement vector magnitude at 1 */
|
|
move = v2_norm(move);
|
|
}
|
|
control->move = move;
|
|
control->focus = focus;
|
|
} break;
|
|
|
|
case SIM_CMD_KIND_PLAYER_FIRE:
|
|
{
|
|
/* TODO: Allow sub-tick firing (start & stop on same tick should still fire for one tick */
|
|
b32 firing = control->firing;
|
|
if (start) {
|
|
firing = true;
|
|
}
|
|
if (stop) {
|
|
firing = false;
|
|
}
|
|
control->firing = firing;
|
|
} break;
|
|
|
|
/* Cursor */
|
|
case SIM_CMD_KIND_CURSOR_MOVE:
|
|
{
|
|
client->cursor_pos = cmd->cursor_pos;
|
|
} break;
|
|
|
|
case SIM_CMD_KIND_DRAG_OBJECT:
|
|
{
|
|
if (cmd->state == SIM_CMD_STATE_START) {
|
|
client->dbg_drag_start = true;
|
|
} else if (cmd->state == SIM_CMD_STATE_STOP) {
|
|
client->dbg_drag_stop = true;
|
|
}
|
|
} break;
|
|
|
|
/* Clear level */
|
|
case SIM_CMD_KIND_CLEAR_ALL:
|
|
{
|
|
/* TODO */
|
|
} break;
|
|
|
|
/* Spawn test */
|
|
case SIM_CMD_KIND_SPAWN_TEST:
|
|
{
|
|
logf_info("Spawning (test)");
|
|
spawn_test_entities(ctx);
|
|
} break;
|
|
|
|
/* Disconnect client */
|
|
case SIM_CMD_KIND_SIM_CLIENT_DISCONNECT:
|
|
{
|
|
host_queue_disconnect(ctx->host, channel_id);
|
|
sim_client_release(client);
|
|
} break;
|
|
|
|
default: break;
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update entity control from client control
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_is_valid_and_active(ent)) continue;
|
|
|
|
if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) {
|
|
struct sim_client *client = sim_client_from_handle(ctx->world.client_store, ent->controlling_client);
|
|
if (client->valid) {
|
|
ent->control = client->control;
|
|
/* TODO: Move this */
|
|
if (ent->control.firing) {
|
|
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 < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_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 + world_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_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 < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_is_valid_and_active(ent)) continue;
|
|
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_ATTACHED)) continue;
|
|
|
|
struct sim_ent *parent = sim_ent_from_handle(ent_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 = 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 < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[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 < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_is_valid_and_active(ent)) continue;
|
|
|
|
if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED)) {
|
|
struct sim_ent *eq = sim_ent_from_handle(ent_store, ent->equipped);
|
|
if (sim_ent_is_valid_and_active(eq)) {
|
|
sim_ent_enable_prop(eq, SIM_ENT_PROP_TRIGGERED_THIS_TICK);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Process triggered entities
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_is_valid_and_active(ent)) continue;
|
|
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK)) continue;
|
|
if ((sim_time - ent->last_triggered < ent->trigger_delay) && ent->last_triggered != 0) continue;
|
|
|
|
ent->last_triggered = sim_time;
|
|
|
|
/* 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(root);
|
|
|
|
bullet->bullet_src = ent->handle;
|
|
bullet->bullet_src_pos = rel_pos;
|
|
bullet->bullet_src_dir = rel_dir;
|
|
bullet->bullet_impulse = 0.75f;
|
|
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(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->handle;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Create motor joints from control move
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_is_valid_and_active(ent)) continue;
|
|
|
|
if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) {
|
|
struct sim_ent *joint_ent = sim_ent_from_handle(ent_store, ent->move_joint);
|
|
if (!sim_ent_is_valid_and_active(joint_ent)) {
|
|
joint_ent = sim_ent_alloc(root);
|
|
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->handle;
|
|
|
|
struct phys_motor_joint_def def = ZI;
|
|
def.e0 = joint_ent->handle; /* Re-using joint entity as e0 */
|
|
def.e1 = ent->handle;
|
|
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);
|
|
}
|
|
|
|
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 motor joints from control focus (aim)
|
|
* ========================== */
|
|
|
|
#if SIM_PLAYER_AIM
|
|
for (u64 ent_index = 0; ent_index < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_is_valid_and_active(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_handle(ent_store, ent->aim_joint);
|
|
if (!sim_ent_is_valid_and_active(joint_ent)) {
|
|
joint_ent = sim_ent_alloc(root);
|
|
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 */
|
|
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->handle;
|
|
|
|
struct phys_motor_joint_def def = ZI;
|
|
def.e0 = joint_ent->handle; /* Re-using joint entity as e0 */
|
|
def.e1 = ent->handle;
|
|
def.max_force = 0;
|
|
def.max_torque = ent->control_torque;
|
|
joint_ent->motor_joint_data = phys_motor_joint_from_def(def);
|
|
}
|
|
|
|
/* Set correction rate dynamically since motor velocity is only set for one frame */
|
|
joint_ent->motor_joint_data.correction_rate = 10 * world_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 / real_dt;
|
|
}
|
|
}
|
|
sim_ent_set_angular_velocity(joint_ent, new_vel);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* ========================== *
|
|
* Create motor joints from ground friction (gravity)
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_is_valid_and_active(ent)) continue;
|
|
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_PHYSICAL_DYNAMIC)) continue;
|
|
|
|
struct sim_ent *joint_ent = sim_ent_from_handle(ent_store, ent->ground_friction_joint);
|
|
|
|
struct phys_motor_joint_def def = ZI;
|
|
def.e0 = root->handle;
|
|
def.e1 = ent->handle;
|
|
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 (!sim_ent_is_valid_and_active(joint_ent)) {
|
|
joint_ent = sim_ent_alloc(root);
|
|
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->handle;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Create mouse joints from client debug drag
|
|
* ========================== */
|
|
|
|
for (u64 i = 0; i < ctx->world.client_store->num_reserved; ++i) {
|
|
struct sim_client *client = &ctx->world.client_store->clients[i];
|
|
if (client->valid) {
|
|
struct v2 cursor = client->cursor_pos;
|
|
b32 start_dragging = client->dbg_drag_start;
|
|
b32 stop_dragging = client->dbg_drag_stop;
|
|
|
|
struct sim_ent *joint_ent = sim_ent_from_handle(ent_store, client->dbg_drag_joint_ent);
|
|
struct sim_ent *target_ent = sim_ent_from_handle(ent_store, joint_ent->mouse_joint_data.target);
|
|
|
|
if (start_dragging) {
|
|
struct xform mouse_xf = xform_from_pos(cursor);
|
|
struct collider_shape mouse_shape = ZI;
|
|
mouse_shape.points[0] = V2(0, 0);
|
|
mouse_shape.count = 1;
|
|
|
|
for (u64 sim_ent_index = 0; sim_ent_index < ent_store->num_reserved; ++sim_ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[sim_ent_index];
|
|
if (!sim_ent_is_valid_and_active(ent)) continue;
|
|
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_PHYSICAL_DYNAMIC)) continue;
|
|
|
|
struct collider_shape ent_collider = ent->local_collider;
|
|
if (ent_collider.count > 0) {
|
|
struct xform ent_xf = sim_ent_get_xform(ent);
|
|
/* TODO: Can just use boolean GJK */
|
|
struct collider_collision_points_result res = collider_collision_points(&ent_collider, &mouse_shape, ent_xf, mouse_xf);
|
|
if (res.num_points > 0) {
|
|
target_ent = ent;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (stop_dragging) {
|
|
target_ent = sim_ent_nil();
|
|
}
|
|
|
|
if (sim_ent_is_valid_and_active(target_ent)) {
|
|
if (!sim_ent_is_valid_and_active(joint_ent)) {
|
|
/* FIXME: Joint ent never released */
|
|
joint_ent = sim_ent_alloc(root);
|
|
joint_ent->mass_unscaled = F32_INFINITY;
|
|
joint_ent->inertia_unscaled = F32_INFINITY;
|
|
client->dbg_drag_joint_ent = joint_ent->handle;
|
|
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->handle;
|
|
if (!sim_ent_handle_eq(joint_ent->mouse_joint_data.target, target_ent->handle)) {
|
|
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->handle;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Physics
|
|
* ========================== */
|
|
|
|
{
|
|
struct phys_ctx phys = ZI;
|
|
phys.tick = ctx->world.tick;
|
|
phys.store = ent_store;
|
|
phys.space = space;
|
|
phys.contact_lookup = &ctx->contact_lookup;
|
|
phys.pre_solve_callback = on_collision;
|
|
phys.pre_solve_callback_udata = ctx;
|
|
#if COLLIDER_DEBUG
|
|
phys.debug_lookup = &phys->collision_debug_lookup;
|
|
#endif
|
|
|
|
/* Step */
|
|
ctx->last_phys_iteration = phys_step(&phys, world_dt, ctx->last_phys_iteration);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update tracers
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_is_valid_and_active(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, world_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_NEXT_TICK);
|
|
}
|
|
|
|
ent->tracer_gradient_start = gradient_start;
|
|
ent->tracer_gradient_end = gradient_end;
|
|
}
|
|
|
|
/* ========================== *
|
|
* Initialize bullet kinematics from sources
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_is_valid_and_active(ent)) continue;
|
|
if (!sim_ent_has_prop(ent, SIM_ENT_PROP_BULLET)) continue;
|
|
|
|
if (ent->activation_tick == ctx->world.tick) {
|
|
struct sim_ent *src = sim_ent_from_handle(ent_store, 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_handle(ent_store, 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_handle(ent_store, ent->bullet_tracer);
|
|
if (sim_ent_is_valid_and_active(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(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 < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_is_valid_and_active(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_handle(ent_store, 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)world_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 < ent_store->num_reserved; ++quake_ent_index) {
|
|
struct sim_ent *quake = &ent_store->entities[quake_ent_index];
|
|
if (!sim_ent_is_valid_and_active(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 < ent_store->num_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &ent_store->entities[ent_index];
|
|
if (!sim_ent_is_valid_and_active(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 * world_dt));
|
|
if (ent->quake_intensity <= 0) {
|
|
sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE_NEXT_TICK);
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* 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_handle(ent_store, parent->first); child->valid; child = sim_ent_from_handle(ent_store, child->next)) {
|
|
if (sim_ent_is_valid_and_active(child)) {
|
|
child->final_layer = parent_layer + child->layer;
|
|
*arena_push(temp.arena, struct sim_ent *) = child;
|
|
++stack_count;
|
|
}
|
|
}
|
|
}
|
|
|
|
arena_temp_end(temp);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Release entities
|
|
* ========================== */
|
|
|
|
release_entities_with_prop(ctx, SIM_ENT_PROP_RELEASE_THIS_TICK);
|
|
|
|
/* ========================== *
|
|
* Publish tick
|
|
* ========================== */
|
|
|
|
for (u64 i = 0; i < ctx->world.client_store->num_reserved; ++i) {
|
|
struct sim_client *client = &ctx->world.client_store->clients[i];
|
|
if (client->valid) {
|
|
struct temp_arena temp = arena_temp_begin(scratch.arena);
|
|
|
|
/* TODO: Not like this */
|
|
struct sim_event snapshot_event = ZI;
|
|
snapshot_event.tick = ctx->world.tick;
|
|
snapshot_event.kind = SIM_EVENT_KIND_SNAPSHOT;
|
|
snapshot_event.snapshot_data = sim_snapshot_encode(temp.arena, client, &ctx->world);
|
|
|
|
struct sim_event_list l = ZI;
|
|
l.first = &snapshot_event;
|
|
l.last = &snapshot_event;
|
|
struct string msg = sim_string_from_events(temp.arena, l);
|
|
|
|
host_queue_write(ctx->host, client->channel_id, msg, 0);
|
|
//host_queue_write(ctx->host, HOST_CHANNEL_ID_ALL, msg, 0);
|
|
|
|
arena_temp_end(temp);
|
|
}
|
|
}
|
|
|
|
host_update(ctx->host);
|
|
__profframe("Sim");
|
|
|
|
/* ========================== *
|
|
* End frame cache scopes
|
|
* ========================== */
|
|
|
|
sprite_scope_end(sprite_frame_scope);
|
|
|
|
scratch_end(scratch);
|
|
}
|