From bb9b374471d0192abc71fe0d5c1b47321153c3cd Mon Sep 17 00:00:00 2001 From: jacob Date: Mon, 4 Mar 2024 18:39:10 -0600 Subject: [PATCH] entity trees, xform storing & lerping --- CMakeLists.txt | 3 +- src/common.h | 11 +-- src/entity.h | 25 +++++-- src/game.c | 184 +++++++++++++++++++++++++++++++++++++++++-------- src/math.h | 164 ++++++++++++++++++++++++++++++++++++------- src/mixer.c | 2 +- src/tick.h | 3 + src/user.c | 164 +++++++++++++++++++++++++++++++++---------- 8 files changed, 453 insertions(+), 103 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a333b758..1af52dc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,8 +174,9 @@ if (RTC) message(FATAL_ERROR "CRTLIB (C runtime library) Must be enabled when compiling with RTC (runtime checks)") endif() # NOTE: Adress sanitizer is disable for now because for some reason it's hiding local variables in debug mode. + set(COMPILER_FLAGS "${COMPILER_FLAGS} -DRTC=1") # set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=undefined -fno-sanitize=alignment -DRTC=1") - set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=undefined -fsanitize-trap=undefined -fno-sanitize=alignment -DRTC=1") + # set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=undefined -fsanitize-trap=undefined -fno-sanitize=alignment -DRTC=1") # set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=address -fsanitize=undefined -fno-sanitize=alignment -DRTC=1") endif() diff --git a/src/common.h b/src/common.h index aba7ddd9..1fbcc3af 100644 --- a/src/common.h +++ b/src/common.h @@ -216,6 +216,7 @@ extern "C" { #define RGB_F(r, g, b) RGBA_F(r, g, b, 1.f) #define COLOR_WHITE RGB(0xFF, 0xFF, 0xFF) +#define COLOR_BLACK RGB(0, 0, 0 ) #define COLOR_RED RGB(0xFF, 0, 0 ) #define COLOR_GREEN RGB(0, 0xFF, 0 ) #define COLOR_BLUE RGB(0, 0, 0xFF) @@ -406,17 +407,11 @@ struct v4_array { }; struct mat3x3 { - union { - f32 e[3][3]; - struct v3 rows[3]; - }; + f32 e[3][3]; }; struct mat4x4 { - union { - f32 e[4][4]; - struct v4 rows[4]; - }; + f32 e[4][4]; }; #define RECT(x, y, width, height) (struct rect) { (x), (y), (width), (height) } diff --git a/src/entity.h b/src/entity.h index 84ccd135..7c127ebd 100644 --- a/src/entity.h +++ b/src/entity.h @@ -31,25 +31,33 @@ enum entity_prop { struct entity { b32 active; - u64 gen; + struct entity_handle handle; u64 continuity_gen; - u64 props[(ENTITY_PROP_COUNT + 63) / 64]; + struct entity *next_free; - /* TODO: Entity tree */ + /* Tree */ + struct entity_handle parent; + struct entity_handle next; + struct entity_handle prev; + struct entity_handle first; + struct entity_handle last; /* ====================================================================== */ /* Translation, rotation, scale in relation to parent entity */ struct trs rel_trs; + struct mat3x3 world_xform; /* Calculated post-physics */ + /* ====================================================================== */ /* Sprite */ struct string sprite_name; struct trs sprite_trs; - struct v2 sprite_pivot; /* Normalized. <0, 0> is center, <1, 1> is bottom right corner, etc. */ + struct v2 sprite_pivot_norm; /* Pivot x & y are each normalized about sprite dimensions. <0, 0> is center, <1, 1> is bottom right corner, etc. */ + u32 sprite_tint; /* ====================================================================== */ /* Animation */ @@ -82,6 +90,15 @@ struct entity { }; +/* ========================== * + * Handle + * ========================== */ + +INLINE b32 entity_is_valid(struct entity_handle eh) +{ + return eh.gen != 0; +} + /* ========================== * * Prop * ========================== */ diff --git a/src/game.c b/src/game.c index 84755f85..3f0443b2 100644 --- a/src/game.c +++ b/src/game.c @@ -17,8 +17,6 @@ GLOBAL struct { struct sys_thread game_thread; f64 timescale; - f64 dt; - f64 time; struct entity *free_entity_head; struct tick tick; @@ -43,16 +41,17 @@ INTERNAL struct entity *entity_alloc(void) entity = L.free_entity_head; L.free_entity_head = entity->next_free; *entity = (struct entity) { - .gen = entity->gen + .handle = entity->handle }; } else { /* Make new */ if (L.tick.entities_count >= MAX_ENTITIES) { sys_panic(STR("MAX_ENTITIES reached")); } - entity = &L.tick.entities[L.tick.entities_count++]; + u64 idx = L.tick.entities_count++; + entity = &L.tick.entities[idx]; *entity = (struct entity) { - .gen = 1 + .handle = { .gen = 1, .idx = idx } }; } return entity; @@ -69,6 +68,31 @@ INTERNAL void entity_release(struct entity *entity) } #endif +INTERNAL struct entity *entity_from_handle(struct entity_handle eh) +{ + if (eh.idx < ARRAY_COUNT(L.tick.entities)) { + struct entity *entity = &L.tick.entities[eh.idx]; + if (entity->handle.gen == eh.gen) { + return entity; + } + } + return NULL; +} + +INTERNAL void entity_tree_attach(struct entity *parent, struct entity *child) +{ + if (entity_is_valid(parent->last)) { + struct entity *last_child = entity_from_handle(parent->last); + last_child->next = child->handle; + child->prev = last_child->handle; + } + parent->last = child->handle; + if (!entity_is_valid(parent->first)) { + parent->first = child->handle; + } + child->parent = parent->handle; +} + /* ========================== * * Game cmd * ========================== */ @@ -130,8 +154,8 @@ INTERNAL void game_update(void) #endif ++L.tick.id; - L.dt = max_f64(0.0, (1.0 / GAME_FPS) * L.timescale); - L.time += L.dt; + L.tick.dt = max_f64(0.0, (1.0 / GAME_FPS) * L.timescale); + L.tick.time += L.tick.dt; /* TODO: remove this (testing) */ /* Initialize entities */ @@ -140,28 +164,78 @@ INTERNAL void game_update(void) run = 1; /* Player ent */ + struct entity *player_ent; { struct string sprite_name = STR("res/graphics/timmy.ase"); struct sheet *sheet = sheet_load(sprite_name); f32 meters_width = sheet->image_size.x / PIXELS_PER_UNIT; f32 meters_height = sheet->image_size.y / PIXELS_PER_UNIT; + struct v2 sprite_size = V2(meters_width, meters_height); struct v2 pos = V2(0, 0); - //f32 rot = PI / 2; - f32 rot = 0; - struct v2 size = V2(meters_width, meters_height); struct entity *e = entity_alloc(); e->active = true; - e->rel_trs = TRS(.t = pos, .r = rot, .s = V2(2, 2)); + e->rel_trs = TRS(.t = pos, .r = PI / 4, .s = V2(2, 1)); e->sprite_name = sprite_name; - e->sprite_trs = TRS(.t = V2(1, 0), .r = 0, .s = size); - e->sprite_pivot = V2(1, 0); + e->sprite_trs = TRS(.s = sprite_size); + e->sprite_pivot_norm = V2(0, 0.65); + e->sprite_tint = COLOR_WHITE; + + //entity_enable_prop(e, ENTITY_PROP_TEST); + entity_enable_prop(e, ENTITY_PROP_TEST_FOLLOW_MOUSE); + + player_ent = e; + } + + /* Child ent */ + struct entity *child_ent; + { + struct string sprite_name = STR("res/graphics/timmy.ase"); + + struct v2 sprite_size = V2(1, 1); + struct v2 pos = V2(2, 2); + + struct entity *e = entity_alloc(); + e->active = true; + e->rel_trs = TRS(.t = pos, .r = 0, .s = V2(1, 1)); + + e->sprite_name = sprite_name; + e->sprite_trs = TRS(.s = sprite_size); + //e->sprite_pivot_norm = V2(0, 0.65); + e->sprite_tint = RGBA_F(1, 0, 0, 0.5); entity_enable_prop(e, ENTITY_PROP_TEST); - entity_enable_prop(e, ENTITY_PROP_TEST_FOLLOW_MOUSE); + //entity_enable_prop(e, ENTITY_PROP_TEST_FOLLOW_MOUSE); + + entity_tree_attach(player_ent, e); + + child_ent = e; + } + + /* Child ent 2 */ + { + struct string sprite_name = STR("res/graphics/timmy.ase"); + + struct v2 sprite_size = V2(1, 1); + + struct v2 pos = V2(0, -1); + + struct entity *e = entity_alloc(); + e->active = true; + e->rel_trs = TRS(.t = pos, .r = PI / 2, .s = V2(1, 1)); + + e->sprite_name = sprite_name; + e->sprite_trs = TRS(.s = sprite_size); + //e->sprite_pivot_norm = V2(0, 0.65); + e->sprite_tint = RGBA_F(0, 1, 0, 0.2); + + //entity_enable_prop(e, ENTITY_PROP_TEST); + //entity_enable_prop(e, ENTITY_PROP_TEST_FOLLOW_MOUSE); + + entity_tree_attach(child_ent, e); } } @@ -186,7 +260,7 @@ INTERNAL void game_update(void) /* ---------------------------------------------------------------------- */ /* ========================== * - * Update entities + * Update entities pre-physics * ========================== */ for (u64 entity_index = 0; entity_index < ARRAY_COUNT(L.tick.entities); ++entity_index) { @@ -213,7 +287,7 @@ INTERNAL void game_update(void) struct sheet_tag tag = sheet_get_tag(sheet, ent->animation_name); if (ent->animation_start_gen == ent->animation_gen) { - ent->animation_time_in_frame += L.dt; + ent->animation_time_in_frame += L.tick.dt; } else { ent->animation_frame = sheet_get_frame(sheet, tag.start); ent->animation_start_gen = ent->animation_gen; @@ -236,9 +310,37 @@ INTERNAL void game_update(void) } } break_animation: + (UNUSED)0; /* Disable label at end of compound statement warning */ + + /* ENTITY_PROP_TEST */ + if (entity_has_prop(ent, ENTITY_PROP_TEST)) { + f32 t = ((f32)L.tick.time); + f32 r = t * 0.5f; + f32 s = 1 + (math_abs_f32(math_sin(t * 5)) * 3); + (UNUSED)r; + (UNUSED)s; + + ent->rel_trs.r = ent->test_start_rel_trs.r + (r * -5.f); + //ent->sprite_trs.r = ent->test_start_sprite_trs.r + (-r * 3.0f); + + + //ent->rel_trs.s = v2_mul(ent->test_start_rel_trs.s, s); + ent->rel_trs.s.x = ent->test_start_rel_trs.s.x * s; + //ent->sprite_trs.s = v2_mul(ent->test_start_sprite_trs.s, s); + } + } + + /* ========================== * + * Update entity physics + * ========================== */ + + for (u64 entity_index = 0; entity_index < ARRAY_COUNT(L.tick.entities); ++entity_index) { + struct entity *ent = &L.tick.entities[entity_index]; + if (!ent->active) continue; + if (entity_is_valid(ent->parent)) continue; /* Only update parent entities */ /* ========================== * - * Update position + * Update position from mouse * ========================== */ /* ENTITY_PROP_TEST_FOLLOW_MOUSE */ @@ -247,28 +349,52 @@ break_animation: ent->test_start_rel_trs.t = L.tick.player_focus; } - /* ENTITY_PROP_TEST */ - if (entity_has_prop(ent, ENTITY_PROP_TEST)) { - f32 t = ((f32)L.time); - f32 r = t * 0.5f; + /* ========================== * + * Calculate tree trs + * ========================== */ + ent->world_xform = mat3x3_from_trs(ent->rel_trs); - ent->rel_trs.r = ent->test_start_rel_trs.r + (r * 0.5f); - ent->sprite_trs.r = ent->test_start_sprite_trs.r + (r * 3.0f); + struct entity *child = entity_from_handle(ent->first); + struct mat3x3 parent_xform = ent->world_xform; + while (child) { + child->world_xform = mat3x3_trs(parent_xform, child->rel_trs); - - f32 s = 1 + (math_abs_f32(math_sin(t * 5)) * 0.25f); - ent->rel_trs.s = v2_mul(ent->test_start_rel_trs.s, s); - ent->sprite_trs.s = v2_mul(ent->test_start_sprite_trs.s, s * 1.5f); + /* Depth first iteration */ + if (child->first.gen) { + /* Next child */ + parent_xform = child->world_xform; + child = entity_from_handle(child->first); + } else if (child->next.gen) { + /* Next sibling */ + child = entity_from_handle(child->next); + } else if (child->parent.gen && entity_from_handle(child->parent)->next.gen) { + /* Next parent sibling */ + struct entity *parent = entity_from_handle(child->parent); + struct entity *grandparent = entity_from_handle(parent->parent); + parent_xform = grandparent->world_xform; + child = entity_from_handle(parent->next); + } else { + child = NULL; + } } + } + + /* ========================== * + * Update entities post-physics + * ========================== */ + + for (u64 entity_index = 0; entity_index < ARRAY_COUNT(L.tick.entities); ++entity_index) { + struct entity *ent = &L.tick.entities[entity_index]; + if (!ent->active) continue; /* ========================== * - * Update sound emitter + * Update sound emitter * ========================== */ if (entity_has_prop(ent, ENTITY_PROP_TEST_SOUND_EMITTER)) { struct mixer_desc desc = ent->sound_desc; desc.speed = L.timescale; - desc.pos = ent->rel_trs.t; + desc.pos = mat3x3_get_pos(ent->world_xform); struct sound *sound = sound_load_async(ent->sound_name, 0); b32 played = ent->sound_handle.gen != 0; if (sound) { diff --git a/src/math.h b/src/math.h index a7223db3..9d6f773d 100644 --- a/src/math.h +++ b/src/math.h @@ -6,6 +6,9 @@ #define PI ((f32)3.14159265358979323846) #define TAU ((f32)6.28318530717958647693) +INLINE struct trs trs_from_mat3x3(struct mat3x3 m); +INLINE struct trs trs_lerp(struct trs a, struct trs b, f32 t); + /* ========================== * * Rounding * ========================== */ @@ -62,13 +65,15 @@ INLINE f64 math_abs_f64(f64 f) INLINE i32 math_sign_f32(f32 f) { u32 bits = *(u32 *)&f; - return bits & ((u32)1 << 31); + i32 sign_bit = bits & ((u32)1 << 31); + return 1 + (sign_bit * -2); } INLINE i32 math_sign_f64(f64 f) { u64 bits = *(u64 *)&f; - return bits & ((u64)1 << 31); + i32 sign_bit = bits & ((u64)1 << 31); + return 1 + (sign_bit * -2); } /* ========================== * @@ -186,6 +191,17 @@ INLINE f32 math_lerp_f32(f32 val0, f32 val1, f32 t) return val0 + ((val1 - val0) * t); } +INLINE f64 math_lerp_f64(f64 val0, f64 val1, f64 t) +{ + return val0 + ((val1 - val0) * t); +} + +INLINE f32 math_lerp_angle(f32 a, f32 b, f32 t) { + f32 diff = math_mod_f32(b - a, TAU); + diff = math_mod_f32(2.0f * diff, TAU) - diff; + return a + diff * t; +} + /* ========================== * * Trig * ========================== */ @@ -202,26 +218,55 @@ INLINE f32 math_lerp_f32(f32 val0, f32 val1, f32 t) INLINE f32 math_sin(f32 x) { const f32 c = 0.225; - - /* Wrap to range [0, TAU] */ - x -= (TAU * (f32)math_floor_f32(x / TAU)); - - /* Wrap to range [-PI, PI] */ - x += (TAU * (x < -PI)) - (TAU * (x > PI)); - - /* Parabola */ + x -= (TAU * (f32)math_floor_f32(x / TAU)); /* [0, TAU] */ + x += (TAU * (x < -PI)) - (TAU * (x > PI)); /* [-PI, PI] */ f32 y = (4.0f/PI) * x + (-4.0f/(PI*PI)) * x * math_abs_f32(x); - - /* Precision adjustment */ y = c * (y * math_abs_f32(y) - y) + y; - return y; - } INLINE f32 math_cos(f32 x) { - return math_sin(x + (TAU / 4.0f)); + return math_sin(x + (PI / 2.0f)); +} + +/* https://mazzo.li/posts/vectorized-atan2.html */ +INLINE f32 math_atan2(f32 x, f32 y) { + const f32 a1 = 0.99997726f; + const f32 a3 = -0.33262347f; + const f32 a5 = 0.19354346f; + const f32 a7 = -0.11643287f; + const f32 a9 = 0.05265332f; + const f32 a11 = -0.01172120f; + + /* Ensure input is in [-1, +1] */ + b32 swap = math_abs_f32(x) < math_abs_f32(y); + f32 s = (swap ? x : y) / (swap ? y : x); + + /* Approximate atan */ + f32 s_sq = s*s; + f32 res = s * (a1 + s_sq * (a3 + s_sq * (a5 + s_sq * (a7 + s_sq * (a9 + s_sq * a11))))); + res = swap ? (s >= 0.0f ? (PI / 2.f) : -(PI / 2.f)) - res : res; + + /* Adjust quadrants */ + if (x >= 0.0f && y >= 0.0f) {} /* 1st quadrant */ + else if (x < 0.0f && y >= 0.0f) { res = PI + res; } /* 2nd quadrant */ + else if (x < 0.0f && y < 0.0f) { res = -PI + res; } /* 3rd quadrant */ + else if (x >= 0.0f && y < 0.0f) {} /* 4th quadrant */ + + return res; +} + +INLINE f32 math_asin(f32 x) +{ + /* TODO: Dedicated arcsin approximation */ + return (PI / 2.0f) - math_atan2(x, math_sqrt(1.0f - (x*x))); +} + +INLINE f32 math_acos(f32 x) +{ + /* TODO: Dedicated arccos approximation */ + return math_atan2(x, math_sqrt(1.0f - (x*x))); } /* ========================== * @@ -238,6 +283,17 @@ INLINE struct v2 v2_mul_v2(struct v2 a, struct v2 b) return V2(a.x * b.x, a.y * b.y); } +INLINE struct v2 v2_div(struct v2 a, f32 s) +{ + f32 d = 1 / s; + return V2(a.x * d, a.y * d); +} + +INLINE struct v2 v2_div_v2(struct v2 a, struct v2 b) +{ + return V2(a.x * (1 / b.x), a.y * (1 / b.y)); +} + INLINE struct v2 v2_neg(struct v2 a) { return V2(-a.x, -a.y); @@ -282,7 +338,7 @@ INLINE f32 v2_dot(struct v2 a, struct v2 b) return a.x * b.x + a.y * b.y; } -INLINE f32 v2_det(struct v2 a, struct v2 b) +INLINE f32 v2_wedge(struct v2 a, struct v2 b) { return a.x * b.y - a.y * b.x; } @@ -502,6 +558,49 @@ INLINE struct v2 mat3x3_get_pos(struct mat3x3 m) return V2(m.e[2][0], m.e[2][1]); } +INLINE f32 mat3x3_get_determinant(struct mat3x3 m) +{ + return m.e[0][0] * m.e[1][1] - m.e[0][1] * m.e[1][0]; +} + +INLINE f32 mat3x3_get_rot(struct mat3x3 m) +{ + return math_atan2(m.e[0][0], m.e[0][1]); +} + +INLINE struct v2 mat3x3_get_scale(struct mat3x3 m) +{ + f32 det_sign = math_sign_f32(mat3x3_get_determinant(m)); + struct v2 bx = V2(m.e[0][0], m.e[0][1]); + struct v2 by = V2(m.e[1][0], m.e[1][1]); + return V2(v2_len(bx), det_sign * v2_len(by)); +} + +INLINE f32 mat3x3_get_skew(struct mat3x3 m) +{ + f32 det = mat3x3_get_determinant(m); + i32 det_sign = math_sign_f32(det); + + struct v2 bx_norm = v2_norm(V2(m.e[0][0], m.e[0][1])); + + struct v2 by_norm = v2_norm(V2(m.e[1][0], m.e[1][1])); + by_norm = v2_mul(by_norm, det_sign); + + f32 dot = v2_dot(bx_norm, by_norm); + + return math_acos(dot) - (PI * 0.5f); +} + +INLINE struct mat3x3 mat3x3_lerp(struct mat3x3 a, struct mat3x3 b, f32 t) +{ + struct trs trs_a = trs_from_mat3x3(a); + struct trs trs_b = trs_from_mat3x3(b); + + struct trs trs = trs_lerp(trs_a, trs_b, t); + + return mat3x3_from_trs(trs); +} + /* ========================== * * Mat4x4 * ========================== */ @@ -568,11 +667,30 @@ INLINE struct trs trs_lerp(struct trs a, struct trs b, f32 t) { struct trs res; res.t = v2_lerp(a.t, b.t, t); - res.r = math_lerp_f32(a.r, b.r, t); + res.r = math_lerp_angle(a.r, b.r, t); res.s = v2_lerp(a.s, b.s, t); return res; } +INLINE struct trs trs_from_mat3x3(struct mat3x3 m) +{ + struct trs trs = { 0 }; + trs.t = mat3x3_get_pos(m); + trs.r = mat3x3_get_rot(m); + trs.s = mat3x3_get_scale(m); + + trs.t = V2(m.e[2][0], m.e[2][1]); + + struct v2 bx = V2(m.e[0][0], m.e[0][1]); + struct v2 by = V2(m.e[1][0], m.e[1][1]); + + trs.s = V2(v2_len(bx), v2_len(by)); + + trs.r = math_atan2(bx.x, bx.y); + + return trs; +} + /* ========================== * * Quad * ========================== */ @@ -623,15 +741,11 @@ INLINE struct quad quad_scale(struct quad q, f32 s) INLINE struct quad quad_mul_mat3x3(struct quad quad, struct mat3x3 m) { - struct v3 p1_v3 = mat3x3_mul_v3(m, V3(quad.p1.x, quad.p1.y, 1)); - struct v3 p2_v3 = mat3x3_mul_v3(m, V3(quad.p2.x, quad.p2.y, 1)); - struct v3 p3_v3 = mat3x3_mul_v3(m, V3(quad.p3.x, quad.p3.y, 1)); - struct v3 p4_v3 = mat3x3_mul_v3(m, V3(quad.p4.x, quad.p4.y, 1)); return (struct quad) { - V2(p1_v3.x, p1_v3.y), - V2(p2_v3.x, p2_v3.y), - V2(p3_v3.x, p3_v3.y), - V2(p4_v3.x, p4_v3.y) + mat3x3_mul_v2(m, quad.p1), + mat3x3_mul_v2(m, quad.p2), + mat3x3_mul_v2(m, quad.p3), + mat3x3_mul_v2(m, quad.p4) }; } diff --git a/src/mixer.c b/src/mixer.c index ae6c5472..2fef44cb 100644 --- a/src/mixer.c +++ b/src/mixer.c @@ -434,7 +434,7 @@ struct mixed_pcm_f32 mixer_update(struct arena *arena, u64 frame_count) /* Calculate pan */ f32 pan_start = effect_data->spatial_pan; - f32 pan_end = v2_det(listener_dir, sound_rel_dir) * pan_scale; + f32 pan_end = v2_wedge(listener_dir, sound_rel_dir) * pan_scale; effect_data->spatial_pan = pan_end; /* Spatialize samples */ diff --git a/src/tick.h b/src/tick.h index 2b32a5c0..54110483 100644 --- a/src/tick.h +++ b/src/tick.h @@ -7,6 +7,9 @@ struct tick { u64 id; /* Starts at 1 */ sys_timestamp_t published_ts; + f64 dt; + f64 time; + struct v2 player_focus; /* Mouse cursor pos in world coordinates */ u64 entities_count; /* Includes 'released' & non-active entities */ diff --git a/src/user.c b/src/user.c index 070f0fac..354293cd 100644 --- a/src/user.c +++ b/src/user.c @@ -198,8 +198,7 @@ INTERNAL struct mat3x3 view_get_xform(struct view view) INTERNAL struct v2 view_inverse_point(struct view view, struct v2 p) { struct mat3x3 mtx_inverse = mat3x3_inverse(view_get_xform(view)); - struct v3 p_inverse = mat3x3_mul_v3(mtx_inverse, V3(p.x, p.y, 1)); - return V2(p_inverse.x, p_inverse.y); + return mat3x3_mul_v2(mtx_inverse, p); } INTERNAL struct v2 view_mouse_pos(struct view view) @@ -207,6 +206,10 @@ INTERNAL struct v2 view_mouse_pos(struct view view) return view_inverse_point(view, L.screen_mouse); } +/* ========================== * + * Update + * ========================== */ + /* TODO: remove this (testing) */ INTERNAL void debug_draw_xform(struct mat3x3 mtx) { @@ -217,18 +220,16 @@ INTERNAL void debug_draw_xform(struct mat3x3 mtx) struct v2 pos = mat3x3_get_pos(mtx); struct v2 x_ray = mat3x3_get_right(mtx); - struct v2 y_ray = mat3x3_get_down(mtx); - struct quad quad = quad_mul_mat3x3(quad_scale(QUAD_UNIT_SQUARE, 0.075), 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); } -/* ========================== * - * Update - * ========================== */ - INTERNAL void user_update(void) { struct temp_arena scratch = scratch_begin_no_conflict(); @@ -360,17 +361,32 @@ INTERNAL void user_update(void) tick_cpy(tick, t1); - /* Blend entities */ #if 1 + /* Blend time */ + tick->time = math_lerp_f64(t0->time, t1->time, (f64)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->gen == e1->gen && e0->continuity_gen == e1->continuity_gen) { + if (e0->handle.gen == e1->handle.gen && e0->continuity_gen == e1->continuity_gen) { e->rel_trs = trs_lerp(e0->rel_trs, e1->rel_trs, blend); e->sprite_trs = trs_lerp(e0->sprite_trs, e1->sprite_trs, blend); - e->sprite_pivot = v2_lerp(e0->sprite_pivot, e1->sprite_pivot, blend); + e->sprite_pivot_norm = v2_lerp(e0->sprite_pivot_norm, e1->sprite_pivot_norm, blend); + + if (e->handle.idx == 0) { + struct mat3x3 world_xform_test = mat3x3_lerp(e0->world_xform, e1->world_xform, blend); + (UNUSED)world_xform_test; + //if (world_xform_test.e[1][0] != e0->world_xform.e[1][0] || world_xform_test.e[1][1] != e0->world_xform.e[1][1]) { + // DEBUGBREAK; + //} + //DEBUGBREAK; + } + + + e->world_xform = mat3x3_lerp(e0->world_xform, e1->world_xform, blend); } } #else @@ -388,10 +404,10 @@ INTERNAL void user_update(void) u32 x_color = RGBA(0x3f, 0, 0, 0xFF); u32 y_color = RGBA(0, 0x3f, 0, 0xFF); - i64 startx = -5; - i64 starty = -5; - i64 rows = 10; - i64 cols = 10; + i64 startx = -10; + i64 starty = -10; + i64 rows = 20; + i64 cols = 20; /* Draw column lines */ @@ -417,6 +433,71 @@ INTERNAL void user_update(void) } } +#if 1 + /* Draw math functions */ + { + u32 detail = 1000; + + f32 thickness = 2.f / PIXELS_PER_UNIT / L.world_view.zoom; + + /* sin */ + { + u32 color = COLOR_RED; + f32 x_start = -10; + f32 x_end = 10; + struct v2_array v = { .points = arena_push_array(scratch.arena, struct v2, detail), .count = detail }; + for (u32 i = 0; i < detail; ++i) { + f32 x = x_start + ((x_end - x_start) * (i / ((f32)detail - 1))); + f32 y = math_sin(x); + v.points[i] = V2(x, -y); + } + draw_solid_poly_line(L.world_canvas, v, false, thickness, color); + } + + /* arcsin */ + { + u32 color = COLOR_BLUE; + f32 x_start = -1; + f32 x_end = 1; + struct v2_array v = { .points = arena_push_array(scratch.arena, struct v2, detail), .count = detail }; + for (u32 i = 0; i < detail; ++i) { + f32 x = x_start + ((x_end - x_start) * (i / ((f32)detail - 1))); + f32 y = math_asin(x); + v.points[i] = V2(x, -y); + } + draw_solid_poly_line(L.world_canvas, v, false, thickness, color); + } + + /* arccos */ + { + u32 color = COLOR_GREEN; + f32 x_start = -1; + f32 x_end = 1; + struct v2_array v = { .points = arena_push_array(scratch.arena, struct v2, detail), .count = detail }; + for (u32 i = 0; i < detail; ++i) { + f32 x = x_start + ((x_end - x_start) * (i / ((f32)detail - 1))); + f32 y = math_acos(x); + v.points[i] = V2(x, -y); + } + draw_solid_poly_line(L.world_canvas, v, false, thickness, color); + } + + /* arctan */ + { + u32 color = COLOR_BLACK; + f32 x_start = -10; + f32 x_end = 10; + struct v2_array v = { .points = arena_push_array(scratch.arena, struct v2, detail), .count = detail }; + for (u32 i = 0; i < detail; ++i) { + f32 x = x_start + ((x_end - x_start) * (i / ((f32)detail - 1))); + f32 y = math_atan2(1, x); + v.points[i] = V2(x, -y); + } + draw_solid_poly_line(L.world_canvas, v, false, thickness, color); + } + } +#endif + /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ @@ -429,12 +510,6 @@ INTERNAL void user_update(void) struct entity *ent = &tick->entities[entity_index]; if (!ent->active) continue; - /* Debug draw transform */ - { - struct mat3x3 mtx = mat3x3_from_trs(ent->rel_trs); - debug_draw_xform(mtx); - } - /* Draw sprite */ if (ent->sprite_name.len > 0) { struct string tex_name = ent->sprite_name; @@ -443,57 +518,70 @@ INTERNAL void user_update(void) struct quad quad = QUAD_UNIT_SQUARE_CENTERED; /* TODO: Do this calculation in game loop */ +#if 0 struct mat3x3 mtx; { /* Entity trs */ - mtx = mat3x3_from_trs(ent->rel_trs); + mtx = mat3x3_from_trs(ent->world_trs); /* Sprite trs & pivot */ - struct v2 pivot = v2_mul_v2(ent->sprite_pivot, v2_mul(ent->sprite_trs.s, 0.5f)); + 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(mtx, ent->sprite_trs, pivot); quad = quad_mul_mat3x3(quad, mtx); } +#else + 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); + } +#endif struct texture *texture = texture_load_async(tex_name); if (texture) { - - struct draw_texture_params params; + u32 tint = ent->sprite_tint; + struct draw_texture_params params = DRAW_TEXTURE_PARAMS(.texture = texture, .tint = tint); if (entity_has_prop(ent, ENTITY_PROP_SPRITE_ANIMATED)) { struct sheet_frame frame = ent->animation_frame; + params.clip = frame.clip; params = DRAW_TEXTURE_PARAMS(.texture = texture, .clip = frame.clip); - } else { - params = DRAW_TEXTURE_PARAMS(.texture = texture); } draw_texture_quad(L.world_canvas, params, quad); } +#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 = mat3x3_from_trs(ent->rel_trs); + struct mat3x3 mtx_pre_pivot = ent->world_xform; mtx_pre_pivot = mat3x3_trs(mtx_pre_pivot, ent->sprite_trs); - struct v2 p = mat3x3_get_pos(mtx_pre_pivot); - draw_solid_circle(L.world_canvas, p, 0.05, color, 20); - - draw_solid_circle(L.world_canvas, mat3x3_get_pos(mtx_pre_pivot), 0.05, color, 20); + draw_solid_circle(L.world_canvas, mat3x3_get_pos(mtx_pre_pivot), 0.02, color, 20); } - /* Debug draw sprite name */ +#if 0 + /* Debug draw sprite info */ if (entity_has_prop(ent, ENTITY_PROP_TEST)) { struct font *disp_font = font_load_async(STR("res/fonts/fixedsys.ttf"), 12.0f); @@ -515,7 +603,7 @@ INTERNAL void user_update(void) ); struct string text = string_format(scratch.arena, fmt, FMT_STR(disp_name), - FMT_FLOAT((f64)ent->rel_trs.r), + FMT_FLOAT((f64)ent->world_trs.r), FMT_FLOAT((f64)ent->sprite_trs.r) ); @@ -523,6 +611,12 @@ INTERNAL void user_update(void) draw_text_ex(L.world_canvas, disp_font, pos, 1.0f / PIXELS_PER_UNIT, text); } } +#endif + } + + /* Debug draw transform */ + { + debug_draw_xform(ent->world_xform); } } @@ -540,7 +634,7 @@ INTERNAL void user_update(void) .pos = world_mouse }); - /* Draw debug info */ + /* Debug draw info */ { f32 spacing = 20; struct v2 pos = V2(10, 8);