1179 lines
43 KiB
C
1179 lines
43 KiB
C
#include "user.h"
|
|
#include "renderer.h"
|
|
#include "font.h"
|
|
#include "sprite.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 "sys.h"
|
|
#include "world.h"
|
|
#include "entity.h"
|
|
#include "mixer.h"
|
|
#include "atomic.h"
|
|
#include "app.h"
|
|
|
|
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_presses_and_repeats; /* Same as `num_presses` but includes key repeats as well */
|
|
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 {
|
|
struct atomic_i32 user_thread_shutdown;
|
|
struct sys_thread user_thread;
|
|
|
|
struct arena arena;
|
|
|
|
struct sys_window *window;
|
|
struct renderer_canvas *world_canvas;
|
|
struct renderer_canvas *viewport_bg_canvas;
|
|
struct renderer_canvas *viewport_canvas;
|
|
struct xform 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_cursor;
|
|
struct v2 viewport_screen_offset;
|
|
struct v2 viewport_size;
|
|
struct v2 viewport_center;
|
|
struct v2 viewport_cursor;
|
|
struct v2 world_cursor;
|
|
} G = { 0 }, DEBUG_ALIAS(G, G_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,
|
|
[SYS_BTN_M1] = USER_BIND_KIND_FIRE,
|
|
|
|
/* Testing */
|
|
|
|
[SYS_BTN_C] = USER_BIND_KIND_DEBUG_CLEAR,
|
|
[SYS_BTN_V] = USER_BIND_KIND_DEBUG_SPAWN,
|
|
[SYS_BTN_N] = USER_BIND_KIND_DEBUG_STEP,
|
|
[SYS_BTN_F1] = USER_BIND_KIND_DEBUG_PAUSE,
|
|
[SYS_BTN_F2] = USER_BIND_KIND_DEBUG_CAMERA,
|
|
[SYS_BTN_F3] = USER_BIND_KIND_DEBUG_DRAW,
|
|
[SYS_BTN_F11] = USER_BIND_KIND_FULLSCREEN,
|
|
[SYS_BTN_MWHEELUP] = USER_BIND_KIND_ZOOM_IN,
|
|
[SYS_BTN_MWHEELDOWN] = USER_BIND_KIND_ZOOM_OUT,
|
|
[SYS_BTN_M3] = USER_BIND_KIND_PAN,
|
|
[SYS_BTN_CTRL] = USER_BIND_KIND_CTRL_TEST
|
|
};
|
|
|
|
/* ========================== *
|
|
* Startup
|
|
* ========================== */
|
|
|
|
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(user_shutdown);
|
|
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_thread_entry_point, arg);
|
|
INTERNAL SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(window_event_callback, event);
|
|
|
|
struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr,
|
|
struct renderer_startup_receipt *renderer_sr,
|
|
struct font_startup_receipt *font_sr,
|
|
struct sprite_startup_receipt *sprite_sr,
|
|
struct draw_startup_receipt *draw_sr,
|
|
struct game_startup_receipt *game_sr,
|
|
struct asset_cache_startup_receipt *asset_cache_sr,
|
|
struct mixer_startup_receipt *mixer_sr,
|
|
struct sys_window *window)
|
|
{
|
|
(UNUSED)work_sr;
|
|
(UNUSED)renderer_sr;
|
|
(UNUSED)font_sr;
|
|
(UNUSED)sprite_sr;
|
|
(UNUSED)draw_sr;
|
|
(UNUSED)game_sr;
|
|
(UNUSED)asset_cache_sr;
|
|
(UNUSED)mixer_sr;
|
|
|
|
G.arena = arena_alloc(GIGABYTE(64));
|
|
G.sys_events_mutex = sys_mutex_alloc();
|
|
G.sys_events_arena = arena_alloc(GIGABYTE(64));
|
|
world_alloc(&G.world);
|
|
G.world_canvas = renderer_canvas_alloc();
|
|
G.world_view = XFORM_TRS(.t = V2(0, 0), .r = 0, .s = V2(PIXELS_PER_UNIT, PIXELS_PER_UNIT));
|
|
G.viewport_bg_canvas = renderer_canvas_alloc();
|
|
G.viewport_canvas = renderer_canvas_alloc();
|
|
G.window = window;
|
|
sys_window_register_event_callback(G.window, &window_event_callback);
|
|
|
|
G.user_thread = sys_thread_alloc(&user_thread_entry_point, NULL, STR("[P1] User thread"));
|
|
app_register_exit_callback(&user_shutdown);
|
|
|
|
return (struct user_startup_receipt) { 0 };
|
|
}
|
|
|
|
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(user_shutdown)
|
|
{
|
|
__prof;
|
|
atomic_i32_eval_exchange(&G.user_thread_shutdown, true);
|
|
sys_thread_wait_release(&G.user_thread);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Window -> user communication
|
|
* ========================== */
|
|
|
|
INTERNAL struct sys_event_array pop_sys_events(struct arena *arena)
|
|
{
|
|
struct sys_event_array array = { 0 };
|
|
struct sys_lock lock = sys_mutex_lock_e(&G.sys_events_mutex);
|
|
{
|
|
struct buffer events_buff = arena_to_buffer(&G.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(&G.sys_events_arena);
|
|
}
|
|
sys_mutex_unlock(&lock);
|
|
return array;
|
|
}
|
|
|
|
INTERNAL SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(window_event_callback, event)
|
|
{
|
|
struct sys_lock lock = sys_mutex_lock_e(&G.sys_events_mutex);
|
|
{
|
|
*arena_push(&G.sys_events_arena, struct sys_event) = event;
|
|
}
|
|
sys_mutex_unlock(&lock);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Game -> user communication
|
|
* ========================== */
|
|
|
|
INTERNAL struct blend_tick *blend_tick_alloc(void)
|
|
{
|
|
struct blend_tick *bt = NULL;
|
|
if (G.head_free_blend_tick) {
|
|
bt = G.head_free_blend_tick;
|
|
G.head_free_blend_tick = bt->next;
|
|
*bt = (struct blend_tick) {
|
|
.world = bt->world
|
|
};
|
|
} else {
|
|
bt = arena_push_zero(&G.arena, struct blend_tick);
|
|
world_alloc(&bt->world);
|
|
}
|
|
if (G.head_blend_tick) {
|
|
bt->next = G.head_blend_tick;
|
|
G.head_blend_tick->prev = bt;
|
|
}
|
|
G.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 == G.head_blend_tick) {
|
|
G.head_blend_tick = next;
|
|
}
|
|
|
|
/* Add to free list */
|
|
bt->next = G.head_free_blend_tick;
|
|
bt->prev = NULL;
|
|
G.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 = G.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 = G.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 = G.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 = G.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 game_cmd_node *first;
|
|
struct game_cmd_node *last;
|
|
};
|
|
|
|
INTERNAL void queue_game_cmd(struct arena *arena, struct game_cmd_list *list, struct game_cmd cmd)
|
|
{
|
|
struct game_cmd_node *node = arena_push_zero(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_no_conflict();
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update
|
|
* ========================== */
|
|
|
|
/* TODO: remove this (testing) */
|
|
INTERNAL void debug_draw_xform(struct xform xf)
|
|
{
|
|
f32 thickness = 2.f;
|
|
f32 arrowhead_len = 15.f;
|
|
u32 color = RGBA_32_F(0, 1, 1, 0.3);
|
|
u32 color_x = RGBA_32_F(1, 0, 0, 0.3);
|
|
u32 color_y = RGBA_32_F(0, 1, 0, 0.3);
|
|
|
|
struct v2 pos = xform_mul_v2(G.world_view, xf.og);
|
|
struct v2 x_ray = xform_basis_mul_v2(G.world_view, xform_get_right(xf));
|
|
struct v2 y_ray = xform_basis_mul_v2(G.world_view, xform_get_up(xf));
|
|
|
|
f32 ray_scale = 1;
|
|
x_ray = v2_mul(x_ray, ray_scale);
|
|
y_ray = v2_mul(y_ray, ray_scale);
|
|
|
|
struct quad quad = quad_from_rect(RECT(0, 0, 1, -1));
|
|
quad = quad_mul_xform(quad_scale(quad, 0.075f), xf);
|
|
|
|
draw_solid_arrow_ray(G.viewport_canvas, pos, x_ray, thickness, arrowhead_len, color_x);
|
|
draw_solid_arrow_ray(G.viewport_canvas, pos, y_ray, thickness, arrowhead_len, color_y);
|
|
draw_solid_quad(G.viewport_canvas, quad, color);
|
|
}
|
|
|
|
/* TODO: remove this (testing) */
|
|
INTERNAL void debug_draw_movement(struct entity *ent)
|
|
{
|
|
f32 thickness = 2.f;
|
|
f32 arrow_len = 15.f;
|
|
|
|
u32 color_vel = RGBA_32_F(1, 0.5, 0, 1);
|
|
|
|
struct xform xf = entity_get_xform(ent);
|
|
struct v2 velocity = v2_div(v2_sub(xf.og, ent->verlet_xform.og), G.world.dt);
|
|
|
|
struct v2 pos = xform_mul_v2(G.world_view, xf.og);
|
|
struct v2 vel_ray = xform_basis_mul_v2(G.world_view, velocity);
|
|
|
|
draw_solid_arrow_ray(G.viewport_canvas, pos, vel_ray, thickness, arrow_len, color_vel);
|
|
}
|
|
|
|
INTERNAL void user_update(void)
|
|
{
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
/* ========================== *
|
|
* Begin frame
|
|
* ========================== */
|
|
|
|
f64 cur_time = sys_timestamp_seconds(sys_timestamp());
|
|
G.dt = max_f64(0.0, cur_time - G.time);
|
|
G.time += G.dt;
|
|
G.screen_size = sys_window_get_size(G.window);
|
|
|
|
struct entity_store *store = G.world.entity_store;
|
|
struct sprite_scope *sprite_frame_scope = sprite_scope_begin();
|
|
struct game_cmd_list cmd_list = { 0 };
|
|
|
|
/* ========================== *
|
|
* Produce interpolated tick
|
|
* ========================== */
|
|
|
|
b32 tick_is_first_frame = false;
|
|
{
|
|
__profscope(produce_interpolated_tick);
|
|
|
|
#if USER_INTERP_ENABLED
|
|
f64 blend_time_offset = (1.0 / GAME_FPS) * USER_INTERP_OFFSET_TICK_RATIO;
|
|
f64 blend_time = G.time > blend_time_offset ? G.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(&G.world, t0);
|
|
|
|
/* Blend world globals */
|
|
G.world.time = math_lerp64(t0->time, t1->time, (f64)tick_blend);
|
|
G.world.dt = math_lerp64(t0->dt, t1->dt, (f64)tick_blend);
|
|
|
|
/* Blend entities */
|
|
u64 num_entities = min_u64(t0->entity_store->reserved, t1->entity_store->reserved);
|
|
{
|
|
__profscope(tick_blending);
|
|
for (u64 i = 0; i < num_entities; ++i) {
|
|
struct entity *e = &store->entities[i];
|
|
struct entity *e0 = &t0->entity_store->entities[i];
|
|
struct entity *e1 = &t1->entity_store->entities[i];
|
|
ASSERT(!e->valid || e->cached_global_xform_dirty == false); /* Game thread should have cached all global xforms before publishing */
|
|
|
|
if (e0->valid && e1->valid
|
|
&& entity_has_prop(e0, ENTITY_PROP_ACTIVE) && entity_has_prop(e1, ENTITY_PROP_ACTIVE)
|
|
&& e0->handle.gen == e1->handle.gen
|
|
&& e0->continuity_gen == e1->continuity_gen) {
|
|
e->local_xform = xform_lerp(e0->local_xform, e1->local_xform, tick_blend);
|
|
e->cached_global_xform = xform_lerp(e0->cached_global_xform, e1->cached_global_xform, tick_blend);
|
|
|
|
e->verlet_xform = xform_lerp(e0->verlet_xform, e1->verlet_xform, tick_blend);
|
|
|
|
e->control_move_force = math_lerp(e0->control_move_force, e1->control_move_force, tick_blend);
|
|
|
|
e->control.move = v2_lerp(e0->control.move, e1->control.move, tick_blend);
|
|
e->control.focus = v2_lerp(e0->control.focus, e1->control.focus, tick_blend);
|
|
|
|
e->sprite_local_xform = xform_lerp(e0->sprite_local_xform, e1->sprite_local_xform, tick_blend);
|
|
e->animation_time_in_frame = math_lerp64(e0->animation_time_in_frame, e1->animation_time_in_frame, (f64)tick_blend);
|
|
e->animation_frame = (u32)math_round_to_int(math_lerp(e0->animation_frame, e1->animation_frame, tick_blend));
|
|
|
|
e->camera_quad_xform = xform_lerp(e0->camera_quad_xform, e1->camera_quad_xform, tick_blend);
|
|
e->camera_xform_target = xform_lerp(e0->camera_xform_target, e1->camera_xform_target, tick_blend);
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
struct interp_ticks interp_ticks = pull_ticks(G.time);
|
|
world_copy_replace(&G.world, interp_ticks.to_tick);
|
|
tick_is_first_frame = G.world.tick_id == 0;
|
|
#endif
|
|
}
|
|
|
|
/* ========================== *
|
|
* Find important entities
|
|
* ========================== */
|
|
|
|
struct entity *active_camera;
|
|
{
|
|
enum entity_prop props[] = { ENTITY_PROP_CAMERA, ENTITY_PROP_CAMERA_ACTIVE };
|
|
active_camera = entity_find_first_match_all(store, (struct entity_prop_array) { .count = ARRAY_COUNT(props), .props = props });
|
|
}
|
|
|
|
/* ========================== *
|
|
* Read sys events
|
|
* ========================== */
|
|
|
|
struct sys_event_array events = pop_sys_events(scratch.arena);
|
|
|
|
/* Reset bind pressed / released states */
|
|
for (u32 i = 0; i < ARRAY_COUNT(G.bind_states); ++i) {
|
|
G.bind_states[i] = (struct bind_state) {
|
|
.is_held = G.bind_states[i].is_held
|
|
};
|
|
}
|
|
|
|
for (u64 entity_index = 0; entity_index < events.count; ++entity_index) {
|
|
struct sys_event *event = &events.events[entity_index];
|
|
|
|
if (event->kind == SYS_EVENT_KIND_QUIT) {
|
|
app_exit();
|
|
}
|
|
|
|
if (event->kind == SYS_EVENT_KIND_BUTTON_UP) {
|
|
#if DEVELOPER
|
|
/* Escape quit */
|
|
if (event->button == SYS_BTN_ESC) {
|
|
app_exit();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Update mouse pos */
|
|
if (event->kind == SYS_EVENT_KIND_CURSOR_MOVE) {
|
|
G.screen_cursor = event->cursor_position;
|
|
}
|
|
|
|
/* Update bind states */
|
|
if ((event->kind == SYS_EVENT_KIND_BUTTON_DOWN || event->kind == SYS_EVENT_KIND_BUTTON_UP)) {
|
|
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;
|
|
b32 out_of_bounds = button >= SYS_BTN_M1 && button <= SYS_BTN_M5 &&
|
|
(G.viewport_cursor.x < 0 ||
|
|
G.viewport_cursor.y < 0 ||
|
|
G.viewport_cursor.x > G.viewport_size.x ||
|
|
G.viewport_cursor.y > G.viewport_size.y);
|
|
G.bind_states[bind].is_held = pressed && !out_of_bounds;
|
|
if (pressed) {
|
|
if (!out_of_bounds) {
|
|
++G.bind_states[bind].num_presses_and_repeats;
|
|
if (!event->is_repeat) {
|
|
++G.bind_states[bind].num_presses;
|
|
}
|
|
}
|
|
} else {
|
|
++G.bind_states[bind].num_releases;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Debug commands
|
|
* ========================== */
|
|
|
|
/* Test fullscreen */
|
|
{
|
|
struct bind_state state = G.bind_states[USER_BIND_KIND_FULLSCREEN];
|
|
if (state.num_presses) {
|
|
struct sys_window_settings settings = sys_window_get_settings(G.window);
|
|
settings.flags ^= SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN;
|
|
sys_window_update_settings(G.window, &settings);
|
|
}
|
|
}
|
|
|
|
/* Test clear world */
|
|
{
|
|
struct bind_state state = G.bind_states[USER_BIND_KIND_DEBUG_CLEAR];
|
|
if (state.num_presses) {
|
|
queue_game_cmd(scratch.arena, &cmd_list, (struct game_cmd) {
|
|
.kind = GAME_CMD_KIND_CLEAR_ALL
|
|
});
|
|
}
|
|
}
|
|
|
|
/* Test pause */
|
|
{
|
|
struct bind_state state = G.bind_states[USER_BIND_KIND_DEBUG_PAUSE];
|
|
if (state.num_presses) {
|
|
queue_game_cmd(scratch.arena, &cmd_list, (struct game_cmd) {
|
|
.kind = GAME_CMD_KIND_PAUSE
|
|
});
|
|
}
|
|
}
|
|
|
|
/* Test step */
|
|
{
|
|
struct bind_state state = G.bind_states[USER_BIND_KIND_DEBUG_STEP];
|
|
for (u32 i = 0; i < state.num_presses_and_repeats; ++i) {
|
|
queue_game_cmd(scratch.arena, &cmd_list, (struct game_cmd) {
|
|
.kind = GAME_CMD_KIND_STEP
|
|
});
|
|
}
|
|
}
|
|
|
|
/* Test spawn */
|
|
{
|
|
struct bind_state state = G.bind_states[USER_BIND_KIND_DEBUG_SPAWN];
|
|
if (state.num_presses) {
|
|
queue_game_cmd(scratch.arena, &cmd_list, (struct game_cmd) {
|
|
.kind = GAME_CMD_KIND_SPAWN_TEST
|
|
});
|
|
}
|
|
}
|
|
|
|
if (G.bind_states[USER_BIND_KIND_DEBUG_DRAW].num_presses > 0) {
|
|
G.debug_draw = !G.debug_draw;
|
|
}
|
|
|
|
if (G.bind_states[USER_BIND_KIND_DEBUG_CAMERA].num_presses > 0) {
|
|
G.debug_camera = !G.debug_camera;
|
|
}
|
|
|
|
|
|
/* ========================== *
|
|
* Update viewport
|
|
* ========================== */
|
|
|
|
/* Calculate screen viewport dimensions */
|
|
if (G.debug_camera) {
|
|
G.viewport_size = G.screen_size;
|
|
G.viewport_screen_offset = V2(0, 0);
|
|
} else {
|
|
|
|
/* Determine viewport size by camera & window dimensions */
|
|
f32 aspect_ratio = 1.0;
|
|
{
|
|
struct xform quad_xf = xform_mul(entity_get_xform(active_camera), active_camera->camera_quad_xform);
|
|
struct v2 camera_size = xform_get_scale(quad_xf);
|
|
if (!v2_eq(camera_size, V2(0, 0))) {
|
|
aspect_ratio = camera_size.x / camera_size.y;
|
|
}
|
|
}
|
|
f32 width = G.screen_size.x;
|
|
f32 height = G.screen_size.y;
|
|
if (width / height > aspect_ratio) {
|
|
width = height * aspect_ratio;
|
|
} else {
|
|
height = math_ceil(width / aspect_ratio);
|
|
}
|
|
G.viewport_size = V2(width, height);
|
|
|
|
/* Center viewport in window */
|
|
f32 x = 0;
|
|
f32 y = 0;
|
|
x = math_round(G.screen_size.x / 2 - width / 2);
|
|
y = math_round(G.screen_size.y / 2 - height / 2);
|
|
G.viewport_screen_offset = V2(x, y);
|
|
}
|
|
|
|
G.viewport_center = v2_mul(G.viewport_size, 0.5);
|
|
G.viewport_cursor = v2_sub(G.screen_cursor, G.viewport_screen_offset);
|
|
|
|
/* ========================== *
|
|
* Update view
|
|
* ========================== */
|
|
|
|
if (G.debug_camera) {
|
|
G.world_view = xform_with_rotation(G.world_view, 0);
|
|
|
|
/* Pan view */
|
|
if (G.bind_states[USER_BIND_KIND_PAN].is_held) {
|
|
if (!G.debug_camera_panning) {
|
|
G.debug_camera_pan_start = xform_invert_mul_v2(G.world_view, G.viewport_cursor);
|
|
}
|
|
G.debug_camera_panning = true;
|
|
struct v2 offset = v2_sub(G.debug_camera_pan_start, xform_invert_mul_v2(G.world_view, G.viewport_cursor));
|
|
G.world_view = xform_translate(G.world_view, v2_neg(offset));
|
|
G.debug_camera_pan_start = xform_invert_mul_v2(G.world_view, G.viewport_cursor);
|
|
} else {
|
|
G.debug_camera_panning = false;
|
|
}
|
|
|
|
/* Zoom view */
|
|
i32 input_zooms = G.bind_states[USER_BIND_KIND_ZOOM_IN].num_presses - G.bind_states[USER_BIND_KIND_ZOOM_OUT].num_presses;
|
|
if (input_zooms != 0) {
|
|
/* Zoom to cursor */
|
|
f32 zoom_rate = 2;
|
|
f32 zoom = math_pow(zoom_rate, input_zooms);
|
|
struct v2 world_cursor = xform_invert_mul_v2(G.world_view, G.viewport_cursor);
|
|
G.world_view = xform_translate(G.world_view, world_cursor);
|
|
G.world_view = xform_scale(G.world_view, V2(zoom, zoom));
|
|
G.world_view = xform_translate(G.world_view, v2_neg(world_cursor));
|
|
}
|
|
} else {
|
|
struct xform xf = entity_get_xform(active_camera);
|
|
|
|
struct v2 center = xf.og;
|
|
f32 rot = xform_get_rotation(xf);
|
|
|
|
/* Scale view into viewport based on camera size */
|
|
struct v2 size = G.viewport_size;
|
|
{
|
|
struct xform quad_xf = xform_mul(xf, active_camera->camera_quad_xform);
|
|
struct v2 camera_size = xform_get_scale(quad_xf);
|
|
if (!v2_eq(camera_size, V2(0, 0))) {
|
|
size = v2_div_v2(size, camera_size);
|
|
}
|
|
}
|
|
f32 scale = min_f32(size.x, size.y);
|
|
|
|
struct trs trs = TRS(
|
|
.t = v2_sub(G.viewport_center, center),
|
|
.r = rot,
|
|
.s = V2(scale, scale)
|
|
);
|
|
|
|
struct v2 pivot = center;
|
|
G.world_view = XFORM_IDENT;
|
|
G.world_view = xform_translate(G.world_view, pivot);
|
|
G.world_view = xform_trs_pivot_rs(G.world_view, trs, pivot);
|
|
}
|
|
G.world_cursor = xform_invert_mul_v2(G.world_view, G.viewport_cursor);
|
|
|
|
/* ========================== *
|
|
* Update listener
|
|
* ========================== */
|
|
|
|
{
|
|
struct v2 up = V2(0, -1);
|
|
struct v2 listener_pos = xform_invert_mul_v2(G.world_view, G.viewport_center);
|
|
struct v2 listener_dir = v2_norm(xform_basis_invert_mul_v2(G.world_view, up));
|
|
mixer_set_listener(listener_pos, listener_dir);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Draw test BG
|
|
* ========================== */
|
|
|
|
{
|
|
u32 color = RGBA_32_F(0.2f, 0.2f, 0.2f, 1.f);
|
|
draw_solid_rect(G.viewport_bg_canvas, RECT(0, 0, G.viewport_size.x, G.viewport_size.y), color);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Draw test grid
|
|
* ========================== */
|
|
|
|
{
|
|
f32 thickness = 3.f;
|
|
u32 color = RGBA_32(0x3f, 0x3f, 0x3f, 0xFF);
|
|
u32 x_color = RGBA_32(0x3f, 0, 0, 0xFF);
|
|
u32 y_color = RGBA_32(0, 0x3f, 0, 0xFF);
|
|
|
|
i64 startx = -10;
|
|
i64 starty = -10;
|
|
i64 rows = 20;
|
|
i64 cols = 20;
|
|
|
|
/* Draw column lines */
|
|
struct v2 col_ray = xform_basis_mul_v2(G.world_view, 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 = xform_mul_v2(G.world_view, V2(col, starty));
|
|
draw_solid_ray(G.viewport_bg_canvas, pos, col_ray, thickness, line_color);
|
|
}
|
|
|
|
struct v2 row_ray = xform_basis_mul_v2(G.world_view, 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 = xform_mul_v2(G.world_view, V2(startx, row));
|
|
draw_solid_ray(G.viewport_bg_canvas, pos, row_ray, thickness, line_color);
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/* ========================== *
|
|
* Draw entities
|
|
* ========================== */
|
|
|
|
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
|
|
__profscope(user_entity_iter);
|
|
struct entity *ent = &store->entities[entity_index];
|
|
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
|
|
if (sprite_tag_is_nil(ent->sprite)) continue;
|
|
|
|
struct sprite_tag sprite = ent->sprite;
|
|
|
|
struct entity *parent = entity_from_handle(store, ent->parent);
|
|
|
|
struct xform xf = entity_get_xform(ent);
|
|
struct xform parent_xf = entity_get_xform(parent);
|
|
|
|
b32 skip_debug_draw = !G.debug_camera && ent == active_camera;
|
|
b32 skip_debug_draw_transform = entity_has_prop(ent, ENTITY_PROP_CAMERA);
|
|
|
|
struct xform sprite_xform = xf;
|
|
|
|
/* Draw bullet trail */
|
|
#if 0
|
|
if (entity_has_prop(ent, ENTITY_PROP_BULLET)) {
|
|
f32 max_len = 0.75;
|
|
|
|
f32 thickness = 0.0025;
|
|
u32 color = RGBA_32_F(1, 1, 1, 1);
|
|
|
|
struct v2 end = xf.og;
|
|
|
|
struct v2 start;
|
|
struct entity *src = entity_from_handle(store, ent->bullet_src);
|
|
if (src->valid) {
|
|
start = xform_mul_v2(src->sprite_local_xform, sprite_sheet_get_slice(sprite_sheet_from_tag_await(sprite_frame_scope, src->sprite), STR("out"), src->animation_frame).center);
|
|
start = xform_mul_v2(entity_get_xform(src), start);
|
|
} else {
|
|
start = v2_sub(end, v2_div(ent->velocity, 50));
|
|
}
|
|
|
|
struct v2 rel = v2_sub(start, end);
|
|
if (v2_len(rel) > max_len) {
|
|
rel = v2_mul(v2_norm(rel), max_len);
|
|
start = v2_add(end, rel);
|
|
}
|
|
|
|
draw_solid_line(G.world_canvas, start, end, thickness, color);
|
|
}
|
|
#endif
|
|
|
|
/* Draw sprite */
|
|
if (!sprite_tag_is_nil(sprite)) {
|
|
/* Calculate sprite xform */
|
|
sprite_xform = xform_mul(xf, ent->sprite_local_xform);
|
|
|
|
/* Async load */
|
|
struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, sprite);
|
|
struct sprite_texture *texture = sprite_texture_from_tag_async(sprite_frame_scope, sprite);
|
|
(UNUSED)texture;
|
|
|
|
/* TODO: Fade in placeholder if texture isn't loaded */
|
|
if (sheet->loaded) {
|
|
struct sprite_sheet_frame frame = sprite_sheet_get_frame(sheet, ent->animation_frame);
|
|
struct quad quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, sprite_xform);
|
|
struct draw_sprite_params params = DRAW_SPRITE_PARAMS(.sprite = sprite, .tint = ent->sprite_tint, .clip = frame.clip);
|
|
draw_sprite_quad(G.world_canvas, params, quad);
|
|
}
|
|
}
|
|
|
|
/* Debug draw info */
|
|
if (G.debug_draw && !skip_debug_draw) {
|
|
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->xform_world;
|
|
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 = xform_mul_v2(G.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(G.viewport_canvas, disp_font, pos, text);
|
|
}
|
|
#endif
|
|
|
|
if (entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) {
|
|
debug_draw_movement(ent);
|
|
}
|
|
|
|
if (!skip_debug_draw_transform) {
|
|
debug_draw_xform(xf);
|
|
}
|
|
|
|
/* Draw focus arrow */
|
|
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
|
|
struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, ent->sprite);
|
|
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, STR("attach.wep"), ent->animation_frame);
|
|
struct v2 start = xform_mul_v2(sprite_xform, slice.center);
|
|
start = xform_mul_v2(G.world_view, start);
|
|
struct v2 end = v2_add(xf.og, ent->control.focus);
|
|
end = xform_mul_v2(G.world_view, end);
|
|
draw_solid_arrow_line(G.viewport_canvas, start, end, 3, 10, RGBA_32_F(1, 1, 1, 0.5));
|
|
}
|
|
|
|
/* Draw slices */
|
|
if (!sprite_tag_is_nil(ent->sprite)) {
|
|
struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, sprite);
|
|
|
|
for (u64 i = 0; i < sheet->slice_groups_count; ++i) {
|
|
struct sprite_sheet_slice_group *group = &sheet->slice_groups[i];
|
|
if (string_ends_with(group->name, STR(".ray"))) continue;
|
|
|
|
for (u32 j = 0; j < group->per_frame_count; ++j) {
|
|
struct sprite_sheet_slice slice = group->frame_slices[(ent->animation_frame * group->per_frame_count) + j];
|
|
|
|
struct v2 center = xform_mul_v2(sprite_xform, slice.center);
|
|
center = xform_mul_v2(G.world_view, center);
|
|
|
|
if (!slice.has_ray) {
|
|
struct quad quad = quad_from_rect(slice.rect);
|
|
quad = quad_mul_xform(quad, sprite_xform);
|
|
quad = quad_mul_xform(quad, G.world_view);
|
|
draw_solid_quad_line(G.viewport_canvas, quad, 2, RGBA_32_F(1, 0, 0.5, 1));
|
|
}
|
|
|
|
draw_solid_circle(G.viewport_canvas, center, 3, RGBA_32_F(1, 0, 0, 1), 20);
|
|
|
|
if (slice.has_ray) {
|
|
struct v2 ray = xform_basis_mul_v2(sprite_xform, slice.dir);
|
|
ray = xform_basis_mul_v2(G.world_view, ray);
|
|
ray = v2_mul(v2_norm(ray), 25);
|
|
draw_solid_arrow_ray(G.viewport_canvas, center, ray, 2, 10, RGBA_32_F(1, 0, 0.5, 1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Draw hierarchy */
|
|
if (entity_has_prop(parent, ENTITY_PROP_ACTIVE) && !parent->is_root) {
|
|
u32 color = RGBA_32_F(0.6, 0.6, 1, 0.75);
|
|
f32 thickness = 5;
|
|
f32 arrow_height = 15;
|
|
|
|
struct v2 start = xform_mul_v2(G.world_view, xf.og);
|
|
struct v2 end = xform_mul_v2(G.world_view, parent_xf.og);
|
|
draw_solid_arrow_line(G.viewport_canvas, start, end, thickness, arrow_height, color);
|
|
}
|
|
|
|
/* Draw camera rect */
|
|
if (entity_has_prop(ent, ENTITY_PROP_CAMERA)) {
|
|
u32 color = ent == active_camera ? RGBA_32_F(1, 1, 1, 0.5) : RGBA_32_F(0, 0.75, 0, 0.5);
|
|
f32 thickness = 3;
|
|
|
|
|
|
struct xform quad_xf = xform_mul(xf, ent->camera_quad_xform);
|
|
struct quad quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, quad_xf);
|
|
quad = quad_mul_xform(quad, G.world_view);
|
|
|
|
draw_solid_quad_line(G.viewport_canvas, quad, thickness, color);
|
|
}
|
|
|
|
arena_temp_end(temp);
|
|
}
|
|
}
|
|
|
|
/* Draw crosshair or show cursor */
|
|
if (!G.debug_camera) {
|
|
struct v2 crosshair_pos = G.viewport_cursor;
|
|
u32 tint = RGBA_32_F(1, 1, 1, 1);
|
|
|
|
struct sprite_tag crosshair_tag = sprite_tag_from_path(STR("res/graphics/crosshair.ase"));
|
|
struct sprite_texture *t = sprite_texture_from_tag_async(sprite_frame_scope, crosshair_tag);
|
|
|
|
struct v2 size = V2(t->width, t->height);
|
|
struct xform xf = XFORM_TRS(.t = crosshair_pos, .s = size);
|
|
struct quad quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, xf);
|
|
draw_sprite_quad(G.viewport_canvas, DRAW_SPRITE_PARAMS(.sprite = crosshair_tag, .tint = tint), quad);
|
|
|
|
struct rect cursor_clip = RECT_FROM_V2(G.viewport_screen_offset, G.viewport_size);
|
|
cursor_clip.pos = v2_add(cursor_clip.pos, v2_mul(size, 0.5f));
|
|
cursor_clip.pos = v2_add(cursor_clip.pos, V2(1, 1));
|
|
cursor_clip.size = v2_sub(cursor_clip.size, size);
|
|
sys_window_cursor_hide(G.window);
|
|
sys_window_cursor_enable_clip(G.window, cursor_clip);
|
|
} else {
|
|
sys_window_cursor_disable_clip(G.window);
|
|
sys_window_cursor_show(G.window);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Construct player control cmd
|
|
* ========================== */
|
|
|
|
{
|
|
/* Queue player move cmd */
|
|
struct v2 input_move_dir = { 0 };
|
|
{
|
|
for (enum user_bind_kind bind = 0; bind < (i32)ARRAY_COUNT(G.bind_states); ++bind) {
|
|
struct bind_state state = G.bind_states[bind];
|
|
|
|
if (!state.is_held && state.num_presses <= 0) {
|
|
continue;
|
|
}
|
|
|
|
switch (bind) {
|
|
/* Movement */
|
|
case USER_BIND_KIND_MOVE_UP: {
|
|
input_move_dir.y -= 1;
|
|
} break;
|
|
|
|
case USER_BIND_KIND_MOVE_DOWN: {
|
|
input_move_dir.y += 1;
|
|
} break;
|
|
|
|
case USER_BIND_KIND_MOVE_LEFT: {
|
|
input_move_dir.x -= 1;
|
|
} break;
|
|
|
|
case USER_BIND_KIND_MOVE_RIGHT: {
|
|
input_move_dir.x += 1;
|
|
} break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
input_move_dir = xform_basis_invert_mul_v2(G.world_view, input_move_dir); /* Make move dir relative to world view */
|
|
input_move_dir = v2_norm(input_move_dir);
|
|
}
|
|
struct v2 input_aim_pos = G.world_cursor;
|
|
if (!G.debug_camera) {
|
|
queue_game_cmd(scratch.arena, &cmd_list, (struct game_cmd) {
|
|
.kind = GAME_CMD_KIND_PLAYER_MOVE,
|
|
.move_dir = input_move_dir,
|
|
.aim_pos = input_aim_pos
|
|
});
|
|
|
|
/* Queue player fire cmd */
|
|
queue_game_cmd(scratch.arena, &cmd_list, (struct game_cmd) {
|
|
.kind = GAME_CMD_KIND_PLAYER_FIRE,
|
|
.state = (G.bind_states[USER_BIND_KIND_FIRE].num_presses > 0 || G.bind_states[USER_BIND_KIND_FIRE].is_held) ? GAME_CMD_STATE_START : GAME_CMD_STATE_STOP
|
|
});
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/* Debug draw info */
|
|
if (G.debug_draw) {
|
|
|
|
f32 spacing = 20;
|
|
struct v2 pos = V2(10, 8);
|
|
struct font *font = font_load_async(STR("res/fonts/fixedsys.ttf"), 12.0f);
|
|
if (font) {
|
|
struct temp_arena temp = arena_temp_begin(scratch.arena);
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("time: %F"), FMT_FLOAT((f64)G.time)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world time: %F"), FMT_FLOAT((f64)G.world.time)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("entities: %F/%F"), FMT_UINT(G.world.entity_store->allocated), FMT_UINT(G.world.entity_store->reserved)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("screen_size: (%F, %F)"), FMT_FLOAT((f64)G.screen_size.x), FMT_FLOAT((f64)G.screen_size.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("screen_cursor: (%F, %F)"), FMT_FLOAT((f64)G.screen_cursor.x), FMT_FLOAT((f64)G.screen_cursor.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("viewport_screen_offset: (%F, %F)"), FMT_FLOAT((f64)G.viewport_screen_offset.x), FMT_FLOAT((f64)G.viewport_screen_offset.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("viewport_size: (%F, %F)"), FMT_FLOAT((f64)G.viewport_size.x), FMT_FLOAT((f64)G.viewport_size.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("viewport_center: (%F, %F)"), FMT_FLOAT((f64)G.viewport_center.x), FMT_FLOAT((f64)G.viewport_center.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("viewport_cursor: (%F, %F)"), FMT_FLOAT((f64)G.viewport_cursor.x), FMT_FLOAT((f64)G.viewport_cursor.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world_view.og: (%F, %F)"), FMT_FLOAT((f64)G.world_view.og.x), FMT_FLOAT((f64)G.world_view.og.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world_view rotation: %F"), FMT_FLOAT((f64)xform_get_rotation(G.world_view))));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world_view scale: (%F, %F)"), FMT_FLOAT((f64)xform_get_scale(G.world_view).x), FMT_FLOAT((f64)xform_get_scale(G.world_view).x)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world_cursor: (%F, %F)"), FMT_FLOAT((f64)G.world_cursor.x), FMT_FLOAT((f64)G.world_cursor.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("debug_camera: %F"), FMT_STR(G.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(G.viewport_bg_canvas);
|
|
renderer_canvas_send_to_gpu(G.world_canvas);
|
|
renderer_canvas_send_to_gpu(G.viewport_canvas);
|
|
|
|
/* Set canvas views before presenting */
|
|
renderer_canvas_set_view(G.viewport_bg_canvas, XFORM_IDENT);
|
|
renderer_canvas_set_view(G.world_canvas, G.world_view);
|
|
renderer_canvas_set_view(G.viewport_canvas, XFORM_IDENT);
|
|
|
|
/* Present */
|
|
i32 vsync = VSYNC_ENABLED;
|
|
|
|
struct renderer_canvas **canvases = arena_dry_push(scratch.arena, struct renderer_canvas *);
|
|
u64 canvases_count = 0;
|
|
{
|
|
/* Viewport background canvas */
|
|
*arena_push(scratch.arena, struct renderer_canvas *) = G.viewport_bg_canvas;
|
|
++canvases_count;
|
|
|
|
/* World canvas */
|
|
if (!tick_is_first_frame) {
|
|
/* Only render world if not on first frame */
|
|
*arena_push(scratch.arena, struct renderer_canvas *) = G.world_canvas;
|
|
++canvases_count;
|
|
}
|
|
|
|
/* Viewport canvas */
|
|
*arena_push(scratch.arena, struct renderer_canvas *) = G.viewport_canvas;
|
|
++canvases_count;
|
|
}
|
|
|
|
renderer_canvas_present(canvases, canvases_count, G.screen_size, RECT_FROM_V2(G.viewport_screen_offset, G.viewport_size), vsync, sprite_frame_scope);
|
|
|
|
/* ========================== *
|
|
* End frame cache scopes
|
|
* ========================== */
|
|
|
|
sprite_scope_end(sprite_frame_scope);
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
/* ========================== *
|
|
* User thread entry point
|
|
* ========================== */
|
|
|
|
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_thread_entry_point, arg)
|
|
{
|
|
(UNUSED)arg;
|
|
|
|
sys_timestamp_t last_frame_ts = 0;
|
|
f64 target_dt = USER_FRAME_LIMIT > (0) ? (1.0 / USER_FRAME_LIMIT) : 0;
|
|
|
|
while (!atomic_i32_eval(&G.user_thread_shutdown)) {
|
|
__profscope(user_update_w_sleep);
|
|
sleep_frame(last_frame_ts, target_dt);
|
|
last_frame_ts = sys_timestamp();
|
|
user_update();
|
|
}
|
|
}
|