diff --git a/src/ase.c b/src/ase.c index 55028805..d37fb08c 100644 --- a/src/ase.c +++ b/src/ase.c @@ -830,11 +830,14 @@ struct ase_decode_sheet_result ase_decode_sheet(struct arena *arena, struct buff u64 image_height = frame_height * frames_y; make_image_dimensions_squareish(&ase_header, &frames_x, &frames_y, &image_width, &image_height); + u32 num_frames = 0; + struct ase_frame *frame_head = NULL; + u32 num_spans = 0; struct ase_span *span_head = NULL; - u32 num_frames = 0; - struct ase_frame *frame_head = NULL; + u32 num_slice_keys = 0; + struct ase_slice_key *slice_key_head = NULL; /* Iterate frames */ for (u16 i = 0; i < ase_header.frames; ++i) { @@ -851,6 +854,9 @@ struct ase_decode_sheet_result ase_decode_sheet(struct arena *arena, struct buff frame->next = frame_head; frame_head = frame; + frame->index = i; + frame->duration = frame_header.frame_duration_ms / 1000.0; + u32 frame_tile_x = i % frames_x; u32 frame_tile_y = i / frames_x; @@ -858,13 +864,13 @@ struct ase_decode_sheet_result ase_decode_sheet(struct arena *arena, struct buff u32 frame_y1 = frame_tile_y * frame_height; u32 frame_x2 = frame_x1 + frame_width; u32 frame_y2 = frame_y1 + frame_height; - - struct v2 clip_p1 = { (f32)frame_x1 / (f32)image_width, (f32)frame_y1 / (f32)image_height }; - struct v2 clip_p2 = { (f32)frame_x2 / (f32)image_width, (f32)frame_y2 / (f32)image_height }; + frame->x1 = frame_x1; + frame->y1 = frame_y1; + frame->x2 = frame_x2; + frame->y2 = frame_y2; frame->index = i; frame->duration = frame_header.frame_duration_ms / 1000.0; - frame->clip = (struct clip_rect) { clip_p1, clip_p2 }; /* Iterate chunks in frame */ for (u32 j = 0; j < num_chunks; ++j) { @@ -903,9 +909,60 @@ struct ase_decode_sheet_result ase_decode_sheet(struct arena *arena, struct buff } break; + case CHUNK_TYPE_SLICE: { + struct ase_slice_key *slice_key = arena_push_zero(arena, struct ase_slice_key); + slice_key->next = slice_key_head; + slice_key_head = slice_key; + + u32 num_slices = br_read_u32(&br); + slice_key->num_slices = num_slices; + + u32 flags = br_read_u32(&br); + br_seek(&br, 4); + + struct string name; + { + u16 str_len = br_read_u16(&br); + u8 *str_bytes = br_read_raw(&br, str_len); + name = (struct string) { + str_len, + arena_push_array(arena, u8, str_len) + }; + MEMCPY(name.text, str_bytes, str_len); + } + slice_key->name = name; + + for (u32 k = 0; k < num_slices; ++k) { + struct ase_slice *slice = arena_push_zero(arena, struct ase_slice); + slice->next = slice_key->slice_head; + slice_key->slice_head = slice; + + u32 start = br_read_u32(&br); + i32 x = br_read_i32(&br); + i32 y = br_read_i32(&br); + u32 width = br_read_u32(&br); + u32 height = br_read_u32(&br); + if (flags & 0x01) { + /* Skip 9-patches info */ + br_seek(&br, 128); + } + if (flags & 0x02) { + /* Skip pivot info */ + br_seek(&br, 64); + } + + slice->start = start; + slice->x1 = x; + slice->y1 = y; + slice->x2 = max_u32((x + width) - 1, 0); + slice->y2 = max_u32((y + height) - 1, 0); + } + + ++num_slice_keys; + } break; + /* TODO */ //case CHUNK_TYPE_USER_DATA - //case CHUNK_TYPE_SLICE default: { br_seek_to(&br, chunk_end_pos); @@ -922,8 +979,10 @@ struct ase_decode_sheet_result ase_decode_sheet(struct arena *arena, struct buff res.frame_size = V2(frame_width, frame_height); res.num_frames = num_frames; res.num_spans = num_spans; + res.num_slice_keys = num_slice_keys; res.frame_head = frame_head; res.span_head = span_head; + res.slice_key_head = slice_key_head; return res; } diff --git a/src/ase.h b/src/ase.h index 9e2b1a88..433b92b2 100644 --- a/src/ase.h +++ b/src/ase.h @@ -12,6 +12,22 @@ struct ase_error_list { struct ase_error *last; }; +struct ase_slice { + u32 start; + i32 x1; + i32 y1; + i32 x2; + i32 y2; + struct ase_slice *next; +}; + +struct ase_slice_key { + struct string name; + u32 num_slices; + struct ase_slice *slice_head; + struct ase_slice_key *next; +}; + struct ase_span { struct string name; u32 start; @@ -21,8 +37,11 @@ struct ase_span { struct ase_frame { u32 index; + u32 x1; + u32 y1; + u32 x2; + u32 y2; f64 duration; - struct clip_rect clip; struct ase_frame *next; }; @@ -36,8 +55,10 @@ struct ase_decode_sheet_result { struct v2 frame_size; u32 num_frames; u32 num_spans; + u32 num_slice_keys; struct ase_frame *frame_head; struct ase_span *span_head; + struct ase_slice_key *slice_key_head; struct ase_error_list errors; }; diff --git a/src/entity.h b/src/entity.h index e70b7131..916cda72 100644 --- a/src/entity.h +++ b/src/entity.h @@ -75,7 +75,7 @@ struct entity { /* ENTITY_PROP_ANIMATING */ f64 animation_time_in_frame; - u64 animation_frame; + u32 animation_frame; /* ====================================================================== */ /* Testing */ diff --git a/src/game.c b/src/game.c index 9659ef9b..1cab9ca0 100644 --- a/src/game.c +++ b/src/game.c @@ -194,6 +194,7 @@ INTERNAL void game_update(void) e->sprite_span_name = STR("idle.unarmed"); //e->sprite_span_name = STR("idle.two_handed"); +#if 0 struct v2 sprite_pos = V2(0, 0); f32 sprite_rot = 0; struct v2 sprite_size = V2(0.5f, 0.5f); @@ -211,6 +212,7 @@ INTERNAL void game_update(void) sprite_xf = xform_translate(sprite_xf, v2_neg(sprite_pivot)); sprite_xf = xform_scale(sprite_xf, sprite_size); e->sprite_quad_xform = sprite_xf; +#endif e->sprite_tint = COLOR_WHITE; entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED); diff --git a/src/renderer.h b/src/renderer.h index 9caf6f8b..ff7b602b 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -2,6 +2,7 @@ #define RENDERER_H struct sys_window; +struct sprite_scope; #define RENDERER_TEXTURE_MAX_WIDTH 16384 #define RENDERER_TEXTURE_MAX_HEIGHT 16384 @@ -69,7 +70,7 @@ void renderer_canvas_ensure_texture_cmd(struct renderer_canvas *canvas, struct t void renderer_canvas_send_to_gpu(struct renderer_canvas *canvas); -void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_count, struct v2 screen_size, struct rect viewport, i32 vsync); +void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_count, struct v2 screen_size, struct rect viewport, i32 vsync, struct sprite_scope *sprite_scope); /* ========================== * * Texture diff --git a/src/renderer_d3d11.c b/src/renderer_d3d11.c index 15b3ef90..f0f94732 100644 --- a/src/renderer_d3d11.c +++ b/src/renderer_d3d11.c @@ -838,12 +838,10 @@ INTERNAL void resize_viewport(struct rect viewport) * research if that is smart first). * * I'm thinking we may also just need to lock texture modification access while presenting */ -void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_count, struct v2 screen_size, struct rect viewport, i32 vsync) +void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_count, struct v2 screen_size, struct rect viewport, i32 vsync, struct sprite_scope *sprite_scope) { __prof; - struct sprite_scope *sprite_scope = sprite_scope_begin(); - /* Resize back buffer */ if (!v2_eq(G.backbuffer_size, screen_size)) { resize_backbuffer(screen_size); @@ -925,8 +923,6 @@ void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_cou __profframe(0); } renderer_capture_image_for_profiler(viewport.width, viewport.height); - - sprite_scope_end(sprite_scope); } /* ========================== * diff --git a/src/sprite.c b/src/sprite.c index ad9b511f..4fff468a 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -30,6 +30,7 @@ #define SHEET_ARENA_RESERVE MEGABYTE(64) #define SHEET_SPAN_LOOKUP_TABLE_BUCKET_RATIO 2.0 +#define SHEET_SLICE_LOOKUP_TABLE_BUCKET_RATIO 2.0 /* ========================== * * Loader cmd structs @@ -398,10 +399,14 @@ INTERNAL struct sprite_sheet init_sheet_from_ase_result(struct arena *arena, str sheet.frames_count = ase.num_frames; for (struct ase_frame *ase_frame = ase.frame_head; ase_frame; ase_frame = ase_frame->next) { u32 index = ase_frame->index; + + struct v2 clip_p1 = { (f32)ase_frame->x1 / (f32)ase.image_size.x, (f32)ase_frame->y1 / (f32)ase.image_size.y }; + struct v2 clip_p2 = { (f32)ase_frame->x2 / (f32)ase.image_size.x, (f32)ase_frame->y2 / (f32)ase.image_size.y }; + sheet.frames[index] = (struct sprite_sheet_frame) { .index = index, .duration = ase_frame->duration, - .clip = ase_frame->clip + .clip = (struct clip_rect) { clip_p1, clip_p2 } }; } @@ -419,7 +424,135 @@ INTERNAL struct sprite_sheet init_sheet_from_ase_result(struct arena *arena, str }; fixed_dict_set(arena, &sheet.spans_dict, name, span); } + } + /* Init slices */ + if (ase.num_slice_keys > 0) { + struct temp_arena scratch = scratch_begin(arena); + + struct temp_ase_slice_key_node { + struct ase_slice_key *key; + struct temp_ase_slice_key_node *next; + + u32 index_in_frame; + u32 earliest_frame; + }; + + struct temp_slice_group_node { + struct string name; + u64 per_frame_count; + struct temp_ase_slice_key_node *temp_ase_slice_key_head; + struct temp_slice_group_node *next; + + struct sprite_slice_group *final_slice_group; + }; + + /* Group slices by name and find out counts per frame */ + u64 num_temp_slice_group_nodes = 0; + struct temp_slice_group_node *temp_slice_group_head = NULL; + { + struct fixed_dict temp_slice_dict = fixed_dict_init(scratch.arena, (u64)(ase.num_slice_keys * 2)); + for (struct ase_slice_key *ase_slice_key = ase.slice_key_head; ase_slice_key; ase_slice_key = ase_slice_key->next) { + struct string name = ase_slice_key->name; + struct temp_slice_group_node *temp_slice_group_node = fixed_dict_get(&temp_slice_dict, name); + if (!temp_slice_group_node) { + temp_slice_group_node = arena_push_zero(scratch.arena, struct temp_slice_group_node); + temp_slice_group_node->name = name; + fixed_dict_set(scratch.arena, &temp_slice_dict, name, temp_slice_group_node); + + ++num_temp_slice_group_nodes; + temp_slice_group_node->next = temp_slice_group_head; + temp_slice_group_head = temp_slice_group_node; + } + + struct temp_ase_slice_key_node *node = arena_push_zero(scratch.arena, struct temp_ase_slice_key_node); + node->key = ase_slice_key; + node->next = temp_slice_group_node->temp_ase_slice_key_head; + node->earliest_frame = node->key->slice_head->start; /* To be overwritten later after iterating */ + temp_slice_group_node->temp_ase_slice_key_head = node; + + ++temp_slice_group_node->per_frame_count; + } + } + + /* Allocate slice groups & fill originals in 2d array */ + sheet.slice_groups_count = num_temp_slice_group_nodes; + sheet.slice_groups_dict = fixed_dict_init(arena, (u64)(num_temp_slice_group_nodes * SHEET_SLICE_LOOKUP_TABLE_BUCKET_RATIO)); + + for (struct temp_slice_group_node *temp_slice_group_node = temp_slice_group_head; temp_slice_group_node; temp_slice_group_node = temp_slice_group_node->next) { + struct sprite_slice_group *slice_group = arena_push_zero(arena, struct sprite_slice_group); + slice_group->name = string_copy(arena, temp_slice_group_node->name); + slice_group->per_frame_count = temp_slice_group_node->per_frame_count; + + arena_align(arena, alignof(struct sprite_sheet_slice)); + slice_group->frame_slices = (struct sprite_sheet_slice *)arena_push_array_zero(arena, u8, (ase.num_frames * slice_group->per_frame_count) * sizeof(struct sprite_sheet_slice)); + + u64 index_in_frame = 0; + for (struct temp_ase_slice_key_node *node = temp_slice_group_node->temp_ase_slice_key_head; node; node = node->next) { + struct ase_slice_key *key = node->key; + + for (struct ase_slice *ase_slice = key->slice_head; ase_slice; ase_slice = ase_slice->next) { + u32 start = ase_slice->start; + + struct sprite_sheet_slice *slice = &slice_group->frame_slices[(start * slice_group->per_frame_count) + index_in_frame]; + slice->original = true; + + f32 x1 = ase_slice->x1; + f32 y1 = ase_slice->y1; + f32 x2 = ase_slice->x2; + f32 y2 = ase_slice->y2; + + slice->center = V2(x1 + ((x2 - x1) / 2.f), y1 + ((y2 - y1) / 2.f)); + + node->index_in_frame = index_in_frame; + if (start < node->earliest_frame) { + node->earliest_frame = start; + } + } + + ++index_in_frame; + } + + temp_slice_group_node->final_slice_group = slice_group; + fixed_dict_set(arena, &sheet.slice_groups_dict, slice_group->name, slice_group); + } + + /* Propogate original slices into next frames (and first slices into previous frames) */ + for (struct temp_slice_group_node *temp_slice_group_node = temp_slice_group_head; temp_slice_group_node; temp_slice_group_node = temp_slice_group_node->next) { + struct sprite_slice_group *slice_group = temp_slice_group_node->final_slice_group; + + for (struct temp_ase_slice_key_node *node = temp_slice_group_node->temp_ase_slice_key_head; node; node = node->next) { + struct ase_slice_key *key = node->key; + u32 index_in_frame = node->index_in_frame; + for (struct ase_slice *ase_slice = key->slice_head; ase_slice; ase_slice = ase_slice->next) { + u32 start = ase_slice->start; + + struct sprite_sheet_slice *slice = &slice_group->frame_slices[(start * slice_group->per_frame_count) + index_in_frame]; + + /* Propogate earliest slice to all previous frames */ + if (start == node->earliest_frame && start > 0) { + for (u32 i = start; i-- > 0;) { + struct sprite_sheet_slice *target = &slice_group->frame_slices[(i * slice_group->per_frame_count) + index_in_frame]; + *target = *slice; + target->original = false; + } + } + + /* Propogate slice to forward frames until original is found */ + for (u32 i = start + 1; i < ase.num_frames; ++i) { + struct sprite_sheet_slice *target = &slice_group->frame_slices[(i * slice_group->per_frame_count) + index_in_frame]; + if (target->original) { + break; + } else { + *target = *slice; + target->original = false; + } + } + } + } + } + + scratch_end(scratch); } return sheet; diff --git a/src/sprite.h b/src/sprite.h index c02e0d82..2467dba0 100644 --- a/src/sprite.h +++ b/src/sprite.h @@ -62,6 +62,19 @@ struct sprite_sheet { struct sprite_sheet_frame *frames; u32 spans_count; struct fixed_dict spans_dict; + u32 slice_groups_count; + struct fixed_dict slice_groups_dict; +}; + +struct sprite_sheet_slice { + b32 original; + struct v2 center; +}; + +struct sprite_slice_group { + struct string name; + u64 per_frame_count; + struct sprite_sheet_slice *frame_slices; /* 2d array of slices with length (num frames) * (num slices per frame). Index with [(frame index * per frame count) + slice index in frame] */ }; struct sprite_sheet_span { diff --git a/src/user.c b/src/user.c index 1089a300..6ec743bd 100644 --- a/src/user.c +++ b/src/user.c @@ -400,7 +400,6 @@ INTERNAL void user_update(void) * Begin frame cache scopes * ========================== */ - /* TODO: Remove texture scope once renderer is rewritten to work with tags */ struct sprite_scope *sprite_frame_scope = sprite_scope_begin(); /* ========================== * @@ -759,6 +758,104 @@ INTERNAL void user_update(void) /* Draw sprite */ if (!sprite_tag_is_nil(ent->sprite)) { +#if 1 + struct sprite_tag sprite = ent->sprite; + + /* 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; + + if (sheet->loaded) { + struct sprite_sheet_frame frame; + { + struct sprite_sheet_span span = sprite_sheet_get_span(sheet, ent->sprite_span_name); + u32 frame_index = span.start + ent->animation_frame; + frame = sprite_sheet_get_frame(sheet, frame_index); + if (entity_has_prop(ent, ENTITY_PROP_ANIMATING)) { + f64 time_in_frame = ent->animation_time_in_frame; + while (time_in_frame > frame.duration) { + time_in_frame -= frame.duration; + ++frame_index; + if (frame_index > span.end) { + /* Loop animation */ + frame_index = span.start; + } + frame = sprite_sheet_get_frame(sheet, frame_index); + } + } + } + + + struct quad quad; + { +#if 1 + struct xform sprite_xf = XFORM_IDENT; + { + struct v2 sprite_size_meters = V2( + sheet->frame_size.x / (f32)PIXELS_PER_UNIT, + sheet->frame_size.y / (f32)PIXELS_PER_UNIT + ); + +#if 1 + struct v2 pivot_pos; + { + struct v2 pivot_pos_norm = V2(0, 0); /* Pivot x & y are each normalized about sprite dimensions. <0, 0> is center, <1, 1> is bottom right corner, etc. */ + struct v2 half_size = v2_mul(sprite_size_meters, 0.5f); + pivot_pos = v2_mul_v2(pivot_pos_norm, half_size); + } + + /* Pivot */ + sprite_xf = xform_translate(sprite_xf, v2_neg(pivot_pos)); + sprite_xf = xform_scale(sprite_xf, sprite_size_meters); +#else + struct sprite_sheet_slice pivot_slice = sprite_sheet_get_slice(STR("pivot"), frame.index); + struct v2 pivot_pos_norm = pivot_slice.center_norm; + f32 pivot_rot = pivot_slice.rot; + + /* Pivot */ + sprite_xf = xform_rotate(sprite_xf, pivot_rot); + sprite_xf = xform_translate(sprite_xf, v2_neg(pivot_pos_norm)); + sprite_xf = xform_scale(sprite_xf, sprite_size_meters); +#endif + + /* Apply entity xform to sprite xform */ + sprite_xf = xform_mul(sprite_xf, ent->sprite_quad_xform); + } +#else + + struct v2 sprite_pos = V2(0, 0); + f32 sprite_rot = 0; + struct v2 sprite_size = V2(0.5f, 0.5f); + // struct v2 sprite_size = sprite_sheet_size_meters(sprite_sheet_tag); + + struct v2 sprite_pivot; + { + struct v2 sprite_pivot_norm = V2(0, 0); /* Pivot x & y are each normalized about sprite dimensions. <0, 0> is center, <1, 1> is bottom right corner, etc. */ + struct v2 half_size = v2_mul(sprite_size, 0.5f); + sprite_pivot = v2_mul_v2(sprite_pivot_norm, half_size); + } + + struct xform sprite_xf = XFORM_POS(sprite_pos); + sprite_xf = xform_rotate(sprite_xf, sprite_rot); + sprite_xf = xform_translate(sprite_xf, v2_neg(sprite_pivot)); + sprite_xf = xform_scale(sprite_xf, sprite_size); + + struct xform xf = { 0 }; + xf = xform_mul(ent->world_xform, sprite_xf); +#endif + + quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, xform_mul(ent->world_xform, sprite_xf)); + } + + /* TODO: Fade in placeholder if texture isn't loaded */ + 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); + } + + + +#else /* Draw texture */ struct sprite_tag sprite = ent->sprite; @@ -794,6 +891,7 @@ INTERNAL void user_update(void) if (sheet->loaded && texture->loaded) { draw_sprite_quad(G.world_canvas, params, quad); } +#endif } /* Debug draw info */ @@ -1053,7 +1151,7 @@ INTERNAL void user_update(void) ++canvases_count; } - renderer_canvas_present(canvases, canvases_count, G.screen_size, RECT_FROM_V2(G.viewport_screen_offset, G.viewport_size), vsync); + 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 diff --git a/src/util.h b/src/util.h index 1d2d25f3..ddfdd47f 100644 --- a/src/util.h +++ b/src/util.h @@ -84,7 +84,7 @@ INLINE void fixed_dict_set(struct arena *arena, struct fixed_dict *dict, struct struct fixed_dict_entry *entry = bucket->entry_head; while (entry) { - if (hash == entry->hash && string_eq(key, entry->key)) { + if (hash == entry->hash) { /* Existing match found, replace its contents */ entry->key = key; entry->value = value; @@ -112,7 +112,7 @@ INLINE void *fixed_dict_get(const struct fixed_dict *dict, struct string key) struct fixed_dict_bucket *bucket = &dict->buckets[index]; for (struct fixed_dict_entry *entry = bucket->entry_head; entry; entry = entry->next) { - if (hash == entry->hash && string_eq(key, entry->key)) { + if (hash == entry->hash) { /* Match found */ return entry->value; }