power_play/src/user.c

1029 lines
34 KiB
C

#include "user.h"
#include "renderer.h"
#include "font.h"
#include "texture.h"
#include "draw.h"
#include "intrinsics.h"
#include "app.h"
#include "game.h"
#include "asset_cache.h"
#include "string.h"
#include "scratch.h"
#include "math.h"
#include "console.h"
#include "sys.h"
#include "world.h"
#include "entity.h"
/* FIXME: remove this (testing) */
#include "sound.h"
#include "mixer.h"
struct view {
f32 px_per_unit;
struct v2 center;
f32 zoom;
f32 rot;
};
struct bind_state {
b32 is_held; /* Is this bind held down this frame */
u32 num_presses; /* How many times was this bind pressed since last frame */
u32 num_releases; /* How many times was this bind released since last frame */
};
struct blend_tick {
struct blend_tick *next;
struct blend_tick *prev;
struct world world;
};
GLOBAL struct {
b32 shutdown;
struct sys_thread user_thread;
struct arena arena;
struct sys_window *window;
struct renderer_canvas *world_canvas;
struct renderer_canvas *screen_canvas;
struct view world_view;
struct blend_tick *head_free_blend_tick;
struct blend_tick *head_blend_tick;
struct world world;
struct bind_state bind_states[USER_BIND_KIND_COUNT];
b32 debug_camera;
b32 debug_camera_panning;
struct v2 debug_camera_pan_start;
b32 debug_draw;
/* User thread input */
struct sys_mutex sys_events_mutex;
struct arena sys_events_arena;
/* Per-frame */
f64 time;
f64 dt;
struct v2 screen_size;
struct v2 screen_center;
struct v2 screen_mouse;
struct v2 screen_mouse_delta;
struct v2 world_mouse;
struct v2 last_focus_screen_pos;
} L = { 0 }, DEBUG_LVAR(L_user);
/* ========================== *
* Bind state
* ========================== */
/* TODO: Remove this */
GLOBAL READONLY enum user_bind_kind g_binds[SYS_BTN_COUNT] = {
[SYS_BTN_W] = USER_BIND_KIND_MOVE_UP,
[SYS_BTN_S] = USER_BIND_KIND_MOVE_DOWN,
[SYS_BTN_A] = USER_BIND_KIND_MOVE_LEFT,
[SYS_BTN_D] = USER_BIND_KIND_MOVE_RIGHT,
/* Testing */
[SYS_BTN_C] = USER_BIND_KIND_DEBUG_CLEAR,
[SYS_BTN_F6] = USER_BIND_KIND_DEBUG_DRAW,
[SYS_BTN_F7] = USER_BIND_KIND_DEBUG_CAMERA,
[SYS_BTN_MWHEELUP] = USER_BIND_KIND_ZOOM_IN,
[SYS_BTN_MWHEELDOWN] = USER_BIND_KIND_ZOOM_OUT,
[SYS_BTN_M3] = USER_BIND_KIND_PAN
};
/* ========================== *
* Window -> user communication
* ========================== */
INTERNAL struct sys_event_array pop_sys_events(struct arena *arena)
{
struct sys_event_array array = { 0 };
if (L.sys_events_arena.pos > 0) {
sys_mutex_lock(&L.sys_events_mutex);
{
struct buffer events_buff = arena_to_buffer(&L.sys_events_arena);
arena_align(arena, ALIGNOF(struct sys_event));
array.events = (struct sys_event *)arena_push_array(arena, u8, events_buff.size);
array.count = events_buff.size / sizeof(struct sys_event);
MEMCPY(array.events, events_buff.data, events_buff.size);
arena_reset(&L.sys_events_arena);
}
sys_mutex_unlock(&L.sys_events_mutex);
}
return array;
}
INTERNAL void window_event_callback(struct sys_event event)
{
sys_mutex_lock(&L.sys_events_mutex);
{
*arena_push(&L.sys_events_arena, struct sys_event) = event;
}
sys_mutex_unlock(&L.sys_events_mutex);
}
/* ========================== *
* Game -> user communication
* ========================== */
INTERNAL struct blend_tick *blend_tick_alloc(void)
{
struct blend_tick *bt = NULL;
if (L.head_free_blend_tick) {
bt = L.head_free_blend_tick;
L.head_free_blend_tick = bt->next;
*bt = (struct blend_tick) {
.world = bt->world
};
} else {
bt = arena_push_zero(&L.arena, struct blend_tick);
world_alloc(&bt->world);
}
if (L.head_blend_tick) {
bt->next = L.head_blend_tick;
L.head_blend_tick->prev = bt;
}
L.head_blend_tick = bt;
return bt;
}
INTERNAL void blend_tick_release(struct blend_tick *bt)
{
struct blend_tick *next = bt->next;
struct blend_tick *prev = bt->prev;
/* Remove from list */
if (next) {
next->prev = prev;
}
if (prev) {
prev->next = next;
}
if (bt == L.head_blend_tick) {
L.head_blend_tick = next;
}
/* Add to free list */
bt->next = L.head_free_blend_tick;
bt->prev = NULL;
L.head_free_blend_tick = bt;
}
struct interp_ticks {
struct world *from_tick;
struct world *to_tick;
};
INTERNAL struct interp_ticks pull_ticks(f64 blend_time)
{
__prof;
/* Find newest stored tick */
struct world *newest_tick = NULL;
for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) {
if (!newest_tick || bt->world.tick_id > newest_tick->tick_id) {
newest_tick = &bt->world;
}
}
/* Pull new tick from game thread if necessary */
if (!newest_tick || game_get_latest_tick_id() > newest_tick->tick_id) {
struct blend_tick *latest_bt = blend_tick_alloc();
newest_tick = &latest_bt->world;
game_get_latest_tick(newest_tick);
}
/* Find oldest tick */
struct world *oldest_tick = NULL;
for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) {
if (!oldest_tick || bt->world.tick_id < oldest_tick->tick_id) {
oldest_tick = &bt->world;
}
}
/* Find closest ticks to blend time */
struct world *from_tick = oldest_tick;
struct world *to_tick = newest_tick;
for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) {
f64 bt_time = sys_timestamp_seconds(bt->world.tick_ts);
if (bt_time < blend_time && bt_time > sys_timestamp_seconds(from_tick->tick_ts)) {
from_tick = &bt->world;
}
if (bt_time > blend_time && bt_time < sys_timestamp_seconds(to_tick->tick_ts)) {
to_tick = &bt->world;
}
}
/* Free any unused old ticks */
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct blend_tick **bts_to_free = arena_dry_push(scratch.arena, struct blend_tick *);
u64 bts_to_free_count = 0;
for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) {
f64 bt_time = sys_timestamp_seconds(bt->world.tick_ts);
if (bt_time < sys_timestamp_seconds(from_tick->tick_ts)) {
*arena_push(scratch.arena, struct blend_tick *) = bt;
++bts_to_free_count;
}
}
for (u64 i = 0; i < bts_to_free_count; ++i) {
blend_tick_release(bts_to_free[i]);
}
scratch_end(scratch);
}
return (struct interp_ticks) {
.from_tick = from_tick,
.to_tick = to_tick
};
}
/* ========================== *
* User -> game communication
* ========================== */
struct game_cmd_node {
struct game_cmd cmd;
struct game_cmd_node *next;
};
struct game_cmd_list {
struct arena *arena;
struct game_cmd_node *first;
struct game_cmd_node *last;
};
INTERNAL void queue_game_cmd(struct game_cmd_list *list, struct game_cmd cmd)
{
struct game_cmd_node *node = arena_push_zero(list->arena, struct game_cmd_node);
node->cmd = cmd;
if (list->first) {
list->last->next = node;
} else {
list->first = node;
}
list->last = node;
}
INTERNAL void pubilsh_game_cmds(struct game_cmd_list *list)
{
struct temp_arena scratch = scratch_begin(list->arena);
/* Construct array */
struct game_cmd_array array = { .cmds = arena_dry_push(scratch.arena, struct game_cmd) };
for (struct game_cmd_node *node = list->first; node; node = node->next) {
struct game_cmd *cmd = arena_push(scratch.arena, struct game_cmd);
*cmd = node->cmd;
++array.count;
}
/* Push array to game thread */
if (array.count > 0) {
game_push_cmds(array);
}
scratch_end(scratch);
}
/* ========================== *
* View
* ========================== */
INTERNAL struct xform view_get_xform(struct view view)
{
f32 scale = view.zoom * view.px_per_unit;
struct trs trs = TRS(
.t = v2_sub(L.screen_center, view.center),
.r = view.rot,
.s = V2(scale, scale)
);
struct v2 pivot = view.center;
struct xform res = XFORM_IDENT;
res = xform_translate(res, pivot);
res = xform_trs_pivot_rs(res, trs, pivot);
return res;
}
INTERNAL struct v2 view_xform_v2(struct view view, struct v2 p)
{
struct xform mtx = view_get_xform(view);
return xform_mul_v2(mtx, p);
}
INTERNAL struct v2 view_xform_basis_v2(struct view view, struct v2 p)
{
struct xform mtx = view_get_xform(view);
return xform_basis_mul_v2(mtx, p);
}
INTERNAL struct v2 view_inverse_xform_v2(struct view view, struct v2 p)
{
return xform_invert_mul_v2(view_get_xform(view), p);
}
INTERNAL struct v2 view_inverse_xform_basis_v2(struct view view, struct v2 p)
{
return xform_basis_invert_mul_v2(view_get_xform(view), p);
}
/* ========================== *
* Update
* ========================== */
/* TODO: remove this (testing) */
INTERNAL void debug_draw_xform(struct xform xf)
{
f32 thickness = 2.f / PIXELS_PER_UNIT / L.world_view.zoom;
f32 arrowhead_len = 15.f / PIXELS_PER_UNIT / L.world_view.zoom;
u32 color = RGBA_F(0, 1, 1, 0.3);
u32 color_x = RGBA_F(1, 0, 0, 0.3);
u32 color_y = RGBA_F(0, 1, 0, 0.3);
struct v2 pos = xf.og;
struct v2 x_ray = xform_get_right(xf);
struct v2 y_ray = xform_get_up(xf);
struct quad quad = quad_from_rect(RECT(0, 0, 1, -1));
quad = quad_mul_xform(quad_scale(quad, 0.075), xf);
draw_solid_arrow_ray(L.world_canvas, pos, x_ray, thickness, arrowhead_len, color_x);
draw_solid_arrow_ray(L.world_canvas, pos, y_ray, thickness, arrowhead_len, color_y);
draw_solid_quad(L.world_canvas, quad, color);
}
/* TODO: remove this (testing) */
INTERNAL void debug_draw_movement(struct entity *ent)
{
f32 thickness = 2.f / PIXELS_PER_UNIT / L.world_view.zoom;
f32 arrow_len = 15.f / PIXELS_PER_UNIT / L.world_view.zoom;
u32 color_vel = RGBA_F(1, 0.5, 0, 1);
u32 color_acc = RGBA_F(1, 1, 0.5, 1);
struct v2 pos = ent->world_xform.og;
struct v2 vel_ray = ent->velocity;
struct v2 acc_ray = ent->acceleration;
draw_solid_arrow_ray(L.world_canvas, pos, vel_ray, thickness, arrow_len, color_vel);
draw_solid_arrow_ray(L.world_canvas, pos, acc_ray, thickness, arrow_len, color_acc);
}
INTERNAL void user_update(void)
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct game_cmd_list cmd_list = {
.arena = scratch.arena
};
/* Get time */
f64 cur_time = sys_timestamp_seconds(sys_timestamp());
L.dt = max_f64(0.0, cur_time - L.time);
L.time += L.dt;
/* Get screen dimensions */
L.screen_size = sys_window_get_size(L.window);
/* Make screen_size an even number */
L.screen_size = V2(math_round(L.screen_size.x), math_round(L.screen_size.y));
L.screen_size = V2(
math_round(L.screen_size.x) % 2 == 0 ? L.screen_size.x : L.screen_size.x + 1,
math_round(L.screen_size.y) % 2 == 0 ? L.screen_size.y : L.screen_size.y + 1
);
L.screen_center = v2_mul(L.screen_size, 0.5);
/* ========================== *
* Produce interpolated tick
* ========================== */
b32 tick_is_first_frame = false;
{
__profscope(produce_interpolated_tick);
f64 blend_time_offset = (1.0 / GAME_FPS) * USER_INTERP_OFFSET_TICK_RATIO;
f64 blend_time = L.time > blend_time_offset ? L.time - blend_time_offset : 0;
/* Pull ticks */
struct interp_ticks interp_ticks = pull_ticks(blend_time);
struct world *t0 = interp_ticks.from_tick;
struct world *t1 = interp_ticks.to_tick;
tick_is_first_frame = (t0->tick_id == 0 || t1->tick_id == 0);
f32 tick_blend = 0;
{
f64 t0_time = sys_timestamp_seconds(t0->tick_ts);
f64 t1_time = sys_timestamp_seconds(t1->tick_ts);
if (t1_time > t0_time) {
tick_blend = (f32)((blend_time - t0_time) / (t1_time - t0_time));
}
tick_blend = clamp_f32(tick_blend, 0.0f, 1.0f);
}
world_copy_replace(&L.world, t1);
#if 1
/* Blend time */
L.world.time = math_lerp_f64(t0->time, t1->time, (f64)tick_blend);
/* Blend entities */
struct entity_array t0_entities = entity_store_as_array(&t0->entity_store);
struct entity_array t1_entities = entity_store_as_array(&t1->entity_store);
struct entity_array world_entities = entity_store_as_array(&L.world.entity_store);
u64 num_entities = min_u64(t0_entities.count, t1_entities.count);
for (u64 i = 0; i < num_entities; ++i) {
struct entity *e0 = &t0_entities.entities[i];
struct entity *e1 = &t1_entities.entities[i];
struct entity *e = &world_entities.entities[i];
if (e0->handle.gen == e1->handle.gen && e0->continuity_gen == e1->continuity_gen) {
e->rel_xform = xform_lerp(e0->rel_xform, e1->rel_xform, tick_blend);
e->world_xform = xform_lerp(e0->world_xform, e1->world_xform, tick_blend);
e->acceleration = v2_lerp(e0->acceleration, e1->acceleration, tick_blend);
e->velocity = v2_lerp(e0->velocity, e1->velocity, tick_blend);
e->player_acceleration = math_lerp_f32(e0->player_acceleration, e1->player_acceleration, tick_blend);
e->player_focus_dir = v2_lerp(e0->player_focus_dir, e1->player_focus_dir, tick_blend);
e->sprite_xform = xform_lerp(e0->sprite_xform, e1->sprite_xform, tick_blend);
e->animation_time_in_frame = math_lerp_f64(e0->animation_time_in_frame, e1->animation_time_in_frame, (f64)tick_blend);
e->camera_zoom = math_lerp_f32(e0->camera_zoom, e1->camera_zoom, tick_blend);
}
}
#else
(UNUSED)tick_blend;
#endif
}
struct entity_array entities_array = entity_store_as_array(&L.world.entity_store);
/* ========================== *
* Read sys events
* ========================== */
struct sys_event_array events = pop_sys_events(scratch.arena);
/* Reset bind states "was_pressed" */
for (u32 i = 0; i < ARRAY_COUNT(L.bind_states); ++i) {
L.bind_states[i] = (struct bind_state) {
.is_held = L.bind_states[i].is_held
};
}
L.screen_mouse_delta = V2(0, 0);
for (u64 entity_index = 0; entity_index < events.count; ++entity_index) {
struct sys_event *event = &events.events[entity_index];
/* Send event to console. Skip if consumed. */
if (console_process_event(*event)) {
continue;
}
if (event->kind == SYS_EVENT_KIND_QUIT) {
app_quit();
}
if (event->kind == SYS_EVENT_KIND_BUTTON_UP) {
#if DEVELOPER
/* Escape quit */
if (event->button == SYS_BTN_ESC) {
app_quit();
}
#endif
}
/* Text */
if (event->kind == SYS_EVENT_KIND_TEXT) {
/* TODO: remove this (sound test) */
{
//u32 flags = SOUND_FLAG_STEREO;
u32 flags = 0;
struct sound *sound = sound_load(STR("res/sounds/test.mp3"), flags);
if (sound) {
mixer_play_ex(sound, MIXER_DESC(.volume = 1.0, .speed = 1.0));
}
}
}
/* Update mouse pos */
if (event->kind == SYS_EVENT_KIND_MOUSE_MOVE) {
L.screen_mouse = event->mouse_position;
}
/* Update mouse delta */
if (event->kind == SYS_EVENT_KIND_RAW_MOUSE_MOVE) {
L.screen_mouse_delta = v2_add(L.screen_mouse_delta, event->raw_mouse_delta);
}
/* Update bind states */
if ((event->kind == SYS_EVENT_KIND_BUTTON_DOWN || event->kind == SYS_EVENT_KIND_BUTTON_UP) && !event->is_repeat) {
enum sys_btn button = event->button;
button = button >= SYS_BTN_COUNT ? SYS_BTN_NONE : button;
enum user_bind_kind bind = g_binds[button];
if (bind) {
b32 pressed = event->kind == SYS_EVENT_KIND_BUTTON_DOWN;
L.bind_states[bind].is_held = pressed;
if (pressed) {
++L.bind_states[bind].num_presses;
} else {
++L.bind_states[bind].num_releases;
}
}
}
}
/* ========================== *
* Debug commands
* ========================== */
/* Test clear world */
{
struct bind_state state = L.bind_states[USER_BIND_KIND_DEBUG_CLEAR];
if (state.num_presses || state.is_held) {
queue_game_cmd(&cmd_list, (struct game_cmd) {
.kind = GAME_CMD_KIND_CLEAR_ALL
});
}
}
/* ========================== *
* Update view from debug camera
* ========================== */
if (L.bind_states[USER_BIND_KIND_DEBUG_DRAW].num_presses > 0) {
L.debug_draw = !L.debug_draw;
}
if (L.bind_states[USER_BIND_KIND_DEBUG_CAMERA].num_presses > 0) {
if (!L.debug_camera) {
/* Move cursor to last crosshair pos */
sys_window_cursor_set_pos(L.window, L.last_focus_screen_pos);
}
L.debug_camera = !L.debug_camera;
}
if (L.debug_camera) {
sys_window_cursor_show(L.window);
sys_window_cursor_disable_keep_in_window(L.window);
/* Pan view */
if (L.bind_states[USER_BIND_KIND_PAN].is_held) {
if (!L.debug_camera_panning) {
L.debug_camera_pan_start = view_inverse_xform_v2(L.world_view, L.screen_mouse);
}
L.debug_camera_panning = true;
struct v2 offset = v2_sub(L.debug_camera_pan_start, view_inverse_xform_v2(L.world_view, L.screen_mouse));
L.world_view.center = v2_add(L.world_view.center, offset);
L.debug_camera_pan_start = view_inverse_xform_v2(L.world_view, L.screen_mouse);
} else {
L.debug_camera_panning = false;
}
/* Zoom view */
i32 input_zooms = L.bind_states[USER_BIND_KIND_ZOOM_IN].num_presses - L.bind_states[USER_BIND_KIND_ZOOM_OUT].num_presses;
if (input_zooms != 0) {
i32 dir = input_zooms >= 0 ? 1 : -1;
u32 zooms_abs = input_zooms >= 0 ? input_zooms : -input_zooms;
/* Zoom camera/view */
f32 zoom_rate = 2;
f32 zoom_min = 1.f / 128.f;
f32 zoom_max = 1.f * 128.f;
f32 new_zoom = L.world_view.zoom;
for (u32 i = 0; i < zooms_abs; ++i) {
if (dir > 0) {
new_zoom *= zoom_rate;
} else {
new_zoom *= 1.0f / zoom_rate;
}
new_zoom = clamp_f32(new_zoom, zoom_min, zoom_max);
}
struct v2 old_mouse = view_inverse_xform_v2(L.world_view, L.screen_mouse);
L.world_view.zoom = new_zoom;
struct v2 new_mouse = view_inverse_xform_v2(L.world_view, L.screen_mouse);
/* Offset view to zoom in on mouse */
struct v2 offset = v2_sub(old_mouse, new_mouse);
L.world_view.center = v2_add(L.world_view.center, offset);
}
} else {
/* Keep cursor invisible and in screen */
sys_window_cursor_hide(L.window);
sys_window_cursor_enable_keep_in_window(L.window);
enum entity_prop props[] = { ENTITY_PROP_CAMERA, ENTITY_PROP_CAMERA_ACTIVE };
struct entity_prop_array props_array = {
.props = props,
.count = ARRAY_COUNT(props)
};
struct entity *ent = entity_find_first_match_all(&L.world.entity_store, props_array);
struct v2 center = ent->world_xform.og;
f32 rot = xform_get_rotation(ent->world_xform);
f32 zoom = ent->camera_zoom;
zoom = zoom > 0 ? zoom : 1;
L.world_view.center = center;
L.world_view.rot = rot;
L.world_view.zoom = zoom;
L.world_mouse = view_inverse_xform_v2(L.world_view, L.screen_mouse);
}
/* ========================== *
* Construct movement input
* ========================== */
/* Movement */
{
struct v2 player_move_dir = { 0 };
for (enum user_bind_kind bind = 0; bind < (i32)ARRAY_COUNT(L.bind_states); ++bind) {
struct bind_state state = L.bind_states[bind];
if (!state.is_held && state.num_presses <= 0) {
continue;
}
switch (bind) {
/* Movement */
case USER_BIND_KIND_MOVE_UP: {
player_move_dir.y -= 1;
} break;
case USER_BIND_KIND_MOVE_DOWN: {
player_move_dir.y += 1;
} break;
case USER_BIND_KIND_MOVE_LEFT: {
player_move_dir.x -= 1;
} break;
case USER_BIND_KIND_MOVE_RIGHT: {
player_move_dir.x += 1;
} break;
default: break;
}
}
player_move_dir = view_inverse_xform_basis_v2(L.world_view, player_move_dir); /* Make move dir relative to world view */
player_move_dir = v2_norm(player_move_dir);
struct game_cmd cmd = (struct game_cmd) {
.kind = GAME_CMD_KIND_PLAYER_MOVE,
.move_dir = player_move_dir
};
if (!L.debug_camera) {
struct v2 focus_move_dir = view_inverse_xform_basis_v2(L.world_view, L.screen_mouse_delta); /* Make focus relative to world view direction */
focus_move_dir = v2_mul(focus_move_dir, MOUSE_SENSITIVITY);
cmd.focus_move_dir = focus_move_dir;
}
queue_game_cmd(&cmd_list, cmd);
}
/* ========================== *
* Draw test grid
* ========================== */
{
f32 thickness = 3.f / PIXELS_PER_UNIT / L.world_view.zoom;
u32 color = RGBA(0x3f, 0x3f, 0x3f, 0xFF);
u32 x_color = RGBA(0x3f, 0, 0, 0xFF);
u32 y_color = RGBA(0, 0x3f, 0, 0xFF);
i64 startx = -10;
i64 starty = -10;
i64 rows = 20;
i64 cols = 20;
/* Draw column lines */
struct v2 col_ray = V2(0, rows);
for (i64 col = starty; col <= (starty + cols); ++col) {
u32 line_color = color;
if (col == 0) {
line_color = y_color;
}
struct v2 pos = V2(col, starty);
draw_solid_ray(L.world_canvas, pos, col_ray, thickness, line_color);
}
struct v2 row_ray = V2(cols, 0);
for (i64 row = startx; row <= (startx + rows); ++row) {
u32 line_color = color;
if (row == 0) {
line_color = x_color;
}
struct v2 pos = V2(startx, row);
draw_solid_ray(L.world_canvas, pos, row_ray, thickness, line_color);
}
}
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ========================== *
* Iterate entities
* ========================== */
/* Iterate entities */
for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
__profscope(user_entity_iter);
struct entity *ent = &entities_array.entities[entity_index];
if (!ent->valid) continue;
b32 is_camera = entity_has_prop(ent, ENTITY_PROP_CAMERA);
/* Draw sprite */
if (ent->sprite_name.len > 0) {
struct string tex_name = ent->sprite_name;
/* Draw texture */
struct texture *texture = texture_load_async(tex_name);
if (texture) {
struct xform xf = xform_mul(ent->world_xform, ent->sprite_xform);
struct quad quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, xf);
u32 tint = ent->sprite_tint;
struct draw_texture_params params = DRAW_TEXTURE_PARAMS(.texture = texture, .tint = tint);
struct sheet *sheet = sheet_load(ent->sprite_name);
if (sheet) {
struct sheet_tag tag = sheet_get_tag(sheet, ent->sprite_tag_name);
u64 frame_index = tag.start + ent->animation_frame;
struct sheet_frame frame = sheet_get_frame(sheet, frame_index);
if (entity_has_prop(ent, ENTITY_PROP_ANIMATING)) {
f64 time_in_frame = ent->animation_time_in_frame;
while (time_in_frame > frame.duration) {
time_in_frame -= frame.duration;
++frame_index;
if (frame_index > tag.end) {
/* Loop animation */
frame_index = tag.start;
}
frame = sheet_get_frame(sheet, frame_index);
}
}
params.clip = frame.clip;
}
draw_texture_quad(L.world_canvas, params, quad);
#if 0
if (L.debug_draw && !is_camera) {
/* Debug draw sprite quad */
{
f32 thickness = 2.f;
u32 color = RGBA_F(1, 1, 0, 0.25);
draw_solid_quad_line(L.world_canvas, quad, (thickness / PIXELS_PER_UNIT / L.world_view.zoom), color);
}
/* Debug draw sprite transform */
{
debug_draw_xform(xf);
}
/* Debug draw sprite pivot */
{
u32 color = RGBA_F(1, 0, 0, 1);
draw_solid_circle(L.world_canvas, ent->world_xform.og, 0.02, color, 20);
}
}
#endif
}
}
/* Draw crosshair */
if (!L.debug_camera && entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
struct v2 focus_pos = v2_add(ent->world_xform.og, ent->player_focus_dir);
struct v2 focus_pos_screen = view_xform_v2(L.world_view, focus_pos);
u32 tint = RGBA_F(1, 1, 1, 0.5);
struct texture *t = texture_load_async(STR("res/graphics/crosshair.ase"));
if (t) {
struct xform xf = XFORM_TRS(.t = focus_pos_screen, .s = t->size);
struct quad quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, xf);
draw_texture_quad(L.screen_canvas, DRAW_TEXTURE_PARAMS(.texture = t, .tint = tint), quad);
}
L.last_focus_screen_pos = focus_pos_screen;
}
/* Debug draw info */
if (L.debug_draw && !is_camera) {
struct temp_arena temp = arena_temp_begin(scratch.arena);
#if 0
struct font *disp_font = font_load_async(STR("res/fonts/fixedsys.ttf"), 12.0f);
if (disp_font) {
struct xform xf = ent->world_xform;
struct trs trs = trs_from_xform(xf);
struct v2 velocity = ent->velocity;
struct v2 acceleration = ent->acceleration;
f32 offset = 1;
struct v2 pos = v2_add(xf.og, v2_mul(V2(0, -1), offset));
pos = view_xform_v2(L.world_view, pos);
pos = v2_round(pos);
struct string disp_name = ent->sprite_name;
struct string fmt = STR(
"sprite name: \"%F\"\n"
"pos: (%F, %F)\n"
"scale: (%F, %F)\n"
"rot: %F\n"
"velocity: (%F, %F)\n"
"acceleration: (%F, %F)\n"
);
struct string text = string_format(temp.arena, fmt,
FMT_STR(disp_name),
FMT_FLOAT((f64)trs.t.x), FMT_FLOAT((f64)trs.t.y),
FMT_FLOAT((f64)trs.s.x), FMT_FLOAT((f64)trs.s.y),
FMT_FLOAT((f64)trs.r),
FMT_FLOAT((f64)velocity.x), FMT_FLOAT((f64)velocity.y),
FMT_FLOAT((f64)acceleration.x), FMT_FLOAT((f64)acceleration.y)
);
draw_text(L.screen_canvas, disp_font, pos, text);
}
#endif
debug_draw_xform(ent->world_xform);
debug_draw_movement(ent);
/* Draw hierarchy */
struct entity *parent = entity_from_handle(&L.world.entity_store, ent->parent);
if (parent->valid) {
u32 color = RGBA_F(0.6, 0.6, 1, 0.75);
f32 thickness = 5;
f32 arrow_height = 15;
struct v2 start = view_xform_v2(L.world_view, ent->world_xform.og);
struct v2 end = view_xform_v2(L.world_view, parent->world_xform.og);
draw_solid_arrow_line(L.screen_canvas, start, end, thickness, arrow_height, color);
}
/* Draw focus */
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
u32 color = RGBA_F(0.75, 0, 0.75, 0.5);
f32 thickness = 3;
f32 arrow_height = 10;
struct v2 pos = view_xform_v2(L.world_view, ent->world_xform.og);
struct v2 focus_ray = view_xform_basis_v2(L.world_view, ent->player_focus_dir);
draw_solid_arrow_ray(L.screen_canvas, pos, focus_ray, thickness, arrow_height, color);
}
arena_temp_end(temp);
}
}
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* Update listener using world view */
mixer_set_listener(L.world_view.center, V2(-math_sin(L.world_view.rot), -math_cos(L.world_view.rot)));
/* Debug draw info */
if (L.debug_draw) {
struct temp_arena temp = arena_temp_begin(scratch.arena);
f32 spacing = 20;
struct v2 pos = V2(10, 8);
struct font *font = font_load(STR("res/fonts/fixedsys.ttf"), 12.0f);
draw_text(L.screen_canvas, font, pos, string_format(temp.arena, STR("time: %F"), FMT_FLOAT((f64)L.time)));
pos.y += spacing;
draw_text(L.screen_canvas, font, pos, string_format(temp.arena, STR("screen_size: (%F, %F)"), FMT_FLOAT((f64)L.screen_size.x), FMT_FLOAT((f64)L.screen_size.y)));
pos.y += spacing;
draw_text(L.screen_canvas, font, pos, string_format(temp.arena, STR("screen_center: (%F, %F)"), FMT_FLOAT((f64)L.screen_center.x), FMT_FLOAT((f64)L.screen_center.y)));
pos.y += spacing;
draw_text(L.screen_canvas, font, pos, string_format(temp.arena, STR("screen_mouse: (%F, %F)"), FMT_FLOAT((f64)L.screen_mouse.x), FMT_FLOAT((f64)L.screen_mouse.y)));
pos.y += spacing;
draw_text(L.screen_canvas, font, pos, string_format(temp.arena, STR("world_view.center: (%F, %F)"), FMT_FLOAT((f64)L.world_view.center.x), FMT_FLOAT((f64)L.world_view.center.y)));
pos.y += spacing;
draw_text(L.screen_canvas, font, pos, string_format(temp.arena, STR("world_view.rot: %F"), FMT_FLOAT((f64)L.world_view.rot)));
pos.y += spacing;
draw_text(L.screen_canvas, font, pos, string_format(temp.arena, STR("world_view.zoom: %F"), FMT_FLOAT((f64)L.world_view.zoom)));
pos.y += spacing;
draw_text(L.screen_canvas, font, pos, string_format(temp.arena, STR("world_mouse: (%F, %F)"), FMT_FLOAT((f64)L.world_mouse.x), FMT_FLOAT((f64)L.world_mouse.y)));
pos.y += spacing;
draw_text(L.screen_canvas, font, pos, string_format(temp.arena, STR("debug_camera: %F"), FMT_STR(L.debug_camera ? STR("true") : STR("false"))));
pos.y += spacing;
arena_temp_end(temp);
}
/* Push game cmds */
pubilsh_game_cmds(&cmd_list);
/* ========================== *
* Present
* ========================== */
/* Send canvases to GPU */
renderer_canvas_send_to_gpu(L.world_canvas);
renderer_canvas_send_to_gpu(L.screen_canvas);
/* Set canvas views before presenting */
renderer_canvas_set_view(L.world_canvas, view_get_xform(L.world_view));
renderer_canvas_set_view(L.screen_canvas, view_get_xform((struct view) {.px_per_unit = 1, .center = L.screen_center, .rot = 0, .zoom = 1}));
/* Present */
i32 vsync = VSYNC_ENABLED;
struct renderer_canvas **canvases = arena_dry_push(scratch.arena, struct renderer_canvas *);
u64 canvases_count = 0;
{
/* Only render world if not on first frame */
if (!tick_is_first_frame) {
*arena_push(scratch.arena, struct renderer_canvas *) = L.world_canvas;
++canvases_count;
}
*arena_push(scratch.arena, struct renderer_canvas *) = L.screen_canvas;
++canvases_count;
}
renderer_canvas_present(canvases, canvases_count, L.screen_size.x, L.screen_size.y, vsync);
scratch_end(scratch);
}
/* ========================== *
* Startup
* ========================== */
INTERNAL void user_thread_entry_point(void *arg)
{
(UNUSED)arg;
sys_timestamp_t last_frame_ts = 0;
f64 target_dt = USER_FRAME_LIMIT > (0) ? (1.0 / USER_FRAME_LIMIT) : 0;
while (!L.shutdown) {
__profscope(user_update_w_sleep);
sleep_frame(last_frame_ts, target_dt);
last_frame_ts = sys_timestamp();
user_update();
}
}
void user_startup(struct sys_window *window)
{
L.arena = arena_alloc(GIGABYTE(64));
L.sys_events_mutex = sys_mutex_alloc();
L.sys_events_arena = arena_alloc(GIGABYTE(64));
world_alloc(&L.world);
L.world_canvas = renderer_canvas_alloc();
L.world_view = (struct view) {
.px_per_unit = PIXELS_PER_UNIT,
.center = V2(0, 0),
.rot = 0,
.zoom = 1
};
L.screen_canvas = renderer_canvas_alloc();
L.window = window;
sys_window_register_event_callback(L.window, &window_event_callback);
L.user_thread = sys_thread_init(&user_thread_entry_point, NULL, STR("[P1] User thread"));
}
void user_shutdown(void)
{
L.shutdown = true;
sys_thread_join(&L.user_thread);
}