844 lines
27 KiB
C
844 lines
27 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 "tick.h"
|
|
|
|
/* FIXME: remove this (testing) */
|
|
#include "sound.h"
|
|
#include "mixer.h"
|
|
|
|
/* NOTE: If vsync is enabled and lower then this will be ignored */
|
|
#define USER_FPS 300
|
|
|
|
struct view {
|
|
f32 px_per_unit;
|
|
|
|
struct v2 center;
|
|
f32 zoom;
|
|
f32 rot;
|
|
};
|
|
|
|
GLOBAL struct {
|
|
b32 shutdown;
|
|
struct sys_thread user_thread;
|
|
|
|
struct sys_window *window;
|
|
struct renderer_canvas *world_canvas;
|
|
struct renderer_canvas *ui_canvas;
|
|
struct view world_view;
|
|
struct tick blend_ticks[2];
|
|
|
|
b32 debug_camera;
|
|
b32 debug_camera_panning;
|
|
struct v2 debug_camera_panning_from;
|
|
|
|
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;
|
|
} L = { 0 } DEBUG_LVAR(L_user);
|
|
|
|
|
|
/* ========================== *
|
|
* Bind state
|
|
* ========================== */
|
|
|
|
struct bind_state {
|
|
b32 pressed; /* Is this bind held down this frame */
|
|
u32 num_presses; /* How many times was this bind pressed since last frame */
|
|
};
|
|
|
|
GLOBAL struct bind_state g_bind_states[USER_BIND_KIND_COUNT] = { 0 };
|
|
|
|
/* TODO: Remove this */
|
|
|
|
GLOBAL 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_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
|
|
};
|
|
|
|
/* ========================== *
|
|
* Window -> user communication
|
|
* ========================== */
|
|
|
|
INTERNAL void window_event_callback(struct sys_event event)
|
|
{
|
|
sys_mutex_lock(&L.sys_events_mutex);
|
|
{
|
|
struct sys_event *write_event = arena_push(&L.sys_events_arena, struct sys_event);
|
|
*write_event = event;
|
|
}
|
|
sys_mutex_unlock(&L.sys_events_mutex);
|
|
}
|
|
|
|
INTERNAL struct sys_event_array pull_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;
|
|
}
|
|
|
|
/* ========================== *
|
|
* Game -> user communication
|
|
* ========================== */
|
|
|
|
struct interp_ticks {
|
|
struct tick *from_tick;
|
|
struct tick *to_tick;
|
|
};
|
|
|
|
INTERNAL struct interp_ticks pull_interp_ticks(void)
|
|
{
|
|
/* Function is currently hard-coded for 2 blend ticks */
|
|
ASSERT(ARRAY_COUNT(L.blend_ticks) == 2);
|
|
|
|
/* Determine from & to tick from existing ticks */
|
|
struct tick *from_tick = NULL;
|
|
struct tick *to_tick = NULL;
|
|
if (L.blend_ticks[1].id > L.blend_ticks[0].id) {
|
|
from_tick = &L.blend_ticks[0];
|
|
to_tick = &L.blend_ticks[1];
|
|
} else {
|
|
from_tick = &L.blend_ticks[1];
|
|
to_tick = &L.blend_ticks[0];
|
|
}
|
|
|
|
/* Check for new tick from game thread */
|
|
u64 latest_tick_id = game_get_latest_tick_id();
|
|
if (latest_tick_id > to_tick->id) {
|
|
/* Swap pointers */
|
|
struct tick *temp = from_tick;
|
|
from_tick = to_tick;
|
|
to_tick = temp;
|
|
/* Pull game tick */
|
|
game_get_latest_tick(to_tick);
|
|
}
|
|
|
|
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 push_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 mat3x3 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 mat3x3 res = mat3x3_from_translate(pivot);
|
|
res = mat3x3_trs_pivot_rs(res, trs, pivot);
|
|
|
|
return res;
|
|
}
|
|
|
|
INTERNAL struct v2 view_xform_point(struct view view, struct v2 p)
|
|
{
|
|
struct mat3x3 mtx = view_get_xform(view);
|
|
return mat3x3_mul_v2(mtx, p);
|
|
}
|
|
|
|
INTERNAL struct v2 view_inverse_xform_point(struct view view, struct v2 p)
|
|
{
|
|
struct mat3x3 mtx_inverse = mat3x3_inverse(view_get_xform(view));
|
|
return mat3x3_mul_v2(mtx_inverse, p);
|
|
}
|
|
|
|
INTERNAL struct v2 view_mouse_pos(struct view view)
|
|
{
|
|
return view_inverse_xform_point(view, L.screen_mouse);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update
|
|
* ========================== */
|
|
|
|
/* TODO: remove this (testing) */
|
|
INTERNAL void debug_draw_xform(struct mat3x3 mtx)
|
|
{
|
|
f32 thickness = 2.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 = mat3x3_get_pos(mtx);
|
|
struct v2 x_ray = mat3x3_get_right(mtx);
|
|
struct v2 y_ray = mat3x3_get_up(mtx);
|
|
|
|
struct quad quad = quad_from_rect(RECT(0, 0, 1, -1));
|
|
quad = quad_mul_mat3x3(quad_scale(quad, 0.075), mtx);
|
|
|
|
draw_solid_ray(L.world_canvas, pos, x_ray, thickness, color_x);
|
|
draw_solid_ray(L.world_canvas, pos, y_ray, thickness, 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;
|
|
|
|
u32 color_vel = RGBA_F(1, 0.5, 0, 1);
|
|
u32 color_acc = RGBA_F(1, 1, 0.5, 1);
|
|
|
|
struct v2 pos = mat3x3_get_pos(ent->world_xform);
|
|
struct v2 vel_ray = ent->velocity;
|
|
struct v2 acc_ray = ent->acceleration;
|
|
|
|
draw_solid_ray(L.world_canvas, pos, vel_ray, thickness, color_vel);
|
|
draw_solid_ray(L.world_canvas, pos, acc_ray, thickness, 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_f32(L.screen_size.x), math_round_f32(L.screen_size.y));
|
|
L.screen_size = V2(
|
|
math_round_f32(L.screen_size.x) % 2 == 0 ? L.screen_size.x : L.screen_size.x + 1,
|
|
math_round_f32(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);
|
|
|
|
/* Read input */
|
|
L.screen_mouse = sys_window_get_mouse_pos(L.window);
|
|
struct sys_event_array events = pull_sys_events(scratch.arena);
|
|
|
|
/* ========================== *
|
|
* Read sys events
|
|
* ========================== */
|
|
|
|
/* Reset bind states "was_pressed" */
|
|
for (u32 i = 0; i < ARRAY_COUNT(g_bind_states); ++i) {
|
|
g_bind_states[i] = (struct bind_state) {
|
|
.pressed = g_bind_states[i].pressed
|
|
};
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
/* Move camera/view */
|
|
if (event->button == SYS_BTN_M3) {
|
|
if (event->kind == SYS_EVENT_KIND_BUTTON_DOWN) {
|
|
L.debug_camera_panning = true;
|
|
L.debug_camera_panning_from = view_mouse_pos(L.world_view);
|
|
} else if (event->kind == SYS_EVENT_KIND_BUTTON_UP) {
|
|
L.debug_camera_panning = false;
|
|
}
|
|
}
|
|
|
|
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 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;
|
|
g_bind_states[bind].pressed = pressed;
|
|
if (pressed) {
|
|
++g_bind_states[bind].num_presses;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Process movement input
|
|
* ========================== */
|
|
|
|
/* Movement */
|
|
{
|
|
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.pressed && 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;
|
|
}
|
|
}
|
|
queue_game_cmd(&cmd_list, (struct game_cmd) {
|
|
.kind = GAME_CMD_KIND_PLAYER_MOVE,
|
|
.dir = v2_norm(input_move_dir)
|
|
});
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update view from debug camera
|
|
* ========================== */
|
|
|
|
if (g_bind_states[USER_BIND_KIND_DEBUG_DRAW].num_presses > 0) {
|
|
L.debug_draw = !L.debug_draw;
|
|
}
|
|
|
|
if (g_bind_states[USER_BIND_KIND_DEBUG_CAMERA].num_presses > 0) {
|
|
L.debug_camera = !L.debug_camera;
|
|
}
|
|
|
|
if (L.debug_camera) {
|
|
|
|
/* Pan view */
|
|
if (L.debug_camera_panning) {
|
|
struct v2 offset = v2_sub(L.debug_camera_panning_from, view_mouse_pos(L.world_view));
|
|
L.world_view.center = v2_add(L.world_view.center, offset);
|
|
L.debug_camera_panning_from = view_mouse_pos(L.world_view);
|
|
}
|
|
|
|
/* 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) {
|
|
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_mouse_pos(L.world_view);
|
|
L.world_view.zoom = new_zoom;
|
|
struct v2 new_mouse = view_mouse_pos(L.world_view);
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Produce interpolated tick
|
|
* ========================== */
|
|
|
|
/* Pull ticks */
|
|
struct interp_ticks interp_ticks = pull_interp_ticks();
|
|
struct tick *t0 = interp_ticks.from_tick;
|
|
struct tick *t1 = interp_ticks.to_tick;
|
|
|
|
/* Produce interpolated tick */
|
|
struct tick *tick = arena_push_zero(scratch.arena, struct tick);
|
|
{
|
|
__profscope(produce_interpolated_tick);
|
|
|
|
sys_timestamp_t tick_publish_delta = t1->published_ts - t0->published_ts;
|
|
f32 tick_blend = (f32)((sys_timestamp() - t1->published_ts) / (f64)tick_publish_delta);
|
|
|
|
tick_cpy(tick, t1);
|
|
|
|
#if 1
|
|
/* Blend time */
|
|
tick->time = math_lerp_f64(t0->time, t1->time, (f64)tick_blend);
|
|
|
|
/* Blend entities */
|
|
u64 num_entities = min_u64(t0->entities_count, t1->entities_count);
|
|
for (u64 i = 0; i < num_entities; ++i) {
|
|
struct entity *e = &tick->entities[i];
|
|
struct entity *e0 = &t0->entities[i];
|
|
struct entity *e1 = &t1->entities[i];
|
|
if (e0->handle.gen == e1->handle.gen && e0->continuity_gen == e1->continuity_gen) {
|
|
e->rel_trs = trs_lerp(e0->rel_trs, e1->rel_trs, tick_blend);
|
|
e->world_xform = mat3x3_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->sprite_trs = trs_lerp(e0->sprite_trs, e1->sprite_trs, tick_blend);
|
|
e->sprite_pivot_norm = v2_lerp(e0->sprite_pivot_norm, e1->sprite_pivot_norm, tick_blend);
|
|
|
|
e->camera_rot = math_lerp_angle(e0->camera_rot, e1->camera_rot, tick_blend);
|
|
e->camera_zoom = math_lerp_f32(e0->camera_zoom, e1->camera_zoom, tick_blend);
|
|
}
|
|
}
|
|
#else
|
|
(UNUSED)tick_blend;
|
|
#endif
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update view from game camera
|
|
* ========================== */
|
|
|
|
/* Find camera */
|
|
for (u64 entity_index = 0; entity_index < tick->entities_count; ++entity_index) {
|
|
struct entity *ent = &tick->entities[entity_index];
|
|
if (!ent->valid) continue;
|
|
|
|
if (entity_has_prop(ent, ENTITY_PROP_CAMERA) && ent->camera_active && !L.debug_camera) {
|
|
struct v2 center = mat3x3_get_pos(ent->world_xform);
|
|
f32 rot = ent->camera_rot;
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* 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 < tick->entities_count; ++entity_index) {
|
|
struct entity *ent = &tick->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 quad quad = QUAD_UNIT_SQUARE_CENTERED;
|
|
|
|
struct mat3x3 mtx;
|
|
{
|
|
struct v2 scale_div_2 = v2_mul(ent->sprite_trs.s, 0.5f);
|
|
struct v2 pivot = v2_mul_v2(ent->sprite_pivot_norm, scale_div_2);
|
|
mtx = mat3x3_trs_pivot_r(ent->world_xform, ent->sprite_trs, pivot);
|
|
|
|
quad = quad_mul_mat3x3(quad, mtx);
|
|
}
|
|
|
|
struct texture *texture = texture_load_async(tex_name);
|
|
if (texture) {
|
|
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);
|
|
|
|
struct sheet_frame frame = sheet_get_frame(sheet, tag.start);
|
|
if (entity_has_prop(ent, ENTITY_PROP_ANIMATING)) {
|
|
b32 looping = ent->animation_looping;
|
|
f64 time_in_anim = tick->time - ent->animation_start_time;
|
|
|
|
u64 frame_index = tag.start;
|
|
while (time_in_anim > 0) {
|
|
frame = sheet_get_frame(sheet, frame_index);
|
|
time_in_anim -= frame.duration;
|
|
++frame_index;
|
|
if (frame_index > tag.end) {
|
|
frame_index = tag.start;
|
|
if (!looping) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
params.clip = frame.clip;
|
|
}
|
|
|
|
draw_texture_quad(L.world_canvas, params, quad);
|
|
}
|
|
|
|
if (L.debug_draw && !is_camera) {
|
|
#if 0
|
|
/* 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);
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
/* Debug draw sprite transform */
|
|
{
|
|
debug_draw_xform(mtx);
|
|
}
|
|
#endif
|
|
|
|
/* Debug draw sprite pivot */
|
|
{
|
|
u32 color = RGBA_F(1, 0, 0, 1);
|
|
|
|
struct mat3x3 mtx_pre_pivot = ent->world_xform;
|
|
mtx_pre_pivot = mat3x3_trs(mtx_pre_pivot, ent->sprite_trs);
|
|
|
|
draw_solid_circle(L.world_canvas, mat3x3_get_pos(mtx_pre_pivot), 0.02, color, 20);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Debug draw info */
|
|
if (L.debug_draw && !is_camera) {
|
|
struct font *disp_font = font_load_async(STR("res/fonts/fixedsys.ttf"), 12.0f);
|
|
if (disp_font) {
|
|
struct mat3x3 mtx = ent->world_xform;
|
|
struct trs trs = trs_from_mat3x3(mtx);
|
|
struct v2 velocity = ent->velocity;
|
|
struct v2 acceleration = ent->acceleration;
|
|
|
|
f32 offset = 1;
|
|
struct v2 pos = v2_add(mat3x3_get_pos(mtx), v2_mul(V2(0, -1), offset));
|
|
pos = view_xform_point(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(scratch.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.ui_canvas, disp_font, pos, text);
|
|
}
|
|
|
|
debug_draw_xform(ent->world_xform);
|
|
debug_draw_movement(ent);
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/* 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)));
|
|
|
|
/* Send world mouse pos */
|
|
struct v2 world_mouse = view_mouse_pos(L.world_view);
|
|
queue_game_cmd(&cmd_list, (struct game_cmd) {
|
|
.kind = GAME_CMD_KIND_PLAYER_FOCUS,
|
|
.pos = world_mouse
|
|
});
|
|
|
|
/* Debug draw info */
|
|
if (L.debug_draw) {
|
|
f32 spacing = 20;
|
|
struct v2 pos = V2(10, 8);
|
|
struct font *font = font_load(STR("res/fonts/fixedsys.ttf"), 12.0f);
|
|
|
|
draw_text(L.ui_canvas, font, pos, string_format(scratch.arena, STR("time: %F"), FMT_FLOAT((f64)L.time)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(L.ui_canvas, font, pos, string_format(scratch.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.ui_canvas, font, pos, string_format(scratch.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.ui_canvas, font, pos, string_format(scratch.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.ui_canvas, font, pos, string_format(scratch.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.ui_canvas, font, pos, string_format(scratch.arena, STR("world_view.rot: %F"), FMT_FLOAT((f64)L.world_view.rot)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(L.ui_canvas, font, pos, string_format(scratch.arena, STR("world_view.zoom: %F"), FMT_FLOAT((f64)L.world_view.zoom)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(L.ui_canvas, font, pos, string_format(scratch.arena, STR("world_mouse: (%F, %F)"), FMT_FLOAT((f64)world_mouse.x), FMT_FLOAT((f64)world_mouse.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(L.ui_canvas, font, pos, string_format(scratch.arena, STR("debug_camera: %F"), FMT_STR(L.debug_camera ? STR("true") : STR("false"))));
|
|
pos.y += spacing;
|
|
}
|
|
|
|
/* Push game cmds */
|
|
push_game_cmds(&cmd_list);
|
|
|
|
/* ========================== *
|
|
* Present
|
|
* ========================== */
|
|
|
|
/* Send canvases to GPU */
|
|
renderer_canvas_send_to_gpu(L.world_canvas);
|
|
renderer_canvas_send_to_gpu(L.ui_canvas);
|
|
|
|
/* Set canvas views before presenting */
|
|
renderer_canvas_set_view(L.world_canvas, view_get_xform(L.world_view));
|
|
renderer_canvas_set_view(L.ui_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 = NULL;
|
|
u64 canvases_count = 0;
|
|
|
|
if (t0->id <= 0 || t1->id <= 0) {
|
|
/* Don't render world on first frame */
|
|
struct renderer_canvas *present_canvases[] = { L.ui_canvas };
|
|
canvases = present_canvases;
|
|
canvases_count = ARRAY_COUNT(present_canvases);
|
|
} else {
|
|
struct renderer_canvas *present_canvases[] = { L.world_canvas, L.ui_canvas };
|
|
canvases = present_canvases;
|
|
canvases_count = ARRAY_COUNT(present_canvases);
|
|
}
|
|
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_FPS > 0 ? (1.0 / USER_FPS) : 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.window = window;
|
|
sys_window_register_event_callback(L.window, &window_event_callback);
|
|
|
|
L.sys_events_mutex = sys_mutex_alloc();
|
|
L.sys_events_arena = arena_alloc(GIGABYTE(64));
|
|
|
|
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.ui_canvas = renderer_canvas_alloc();
|
|
|
|
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);
|
|
}
|