From a2f087b7f96aedebb6c0f30bd584bdeacbe0c42e Mon Sep 17 00:00:00 2001 From: kichikuou Date: Thu, 30 Apr 2026 12:42:39 +0900 Subject: [PATCH] Flat: Add support for FLAT_LIB_STOP_MOTION FLAT_LIB_STOP_MOTION represents a sprite animation as a sequence of CG frames. A stop_motion library specifies a base CG file (e.g. "foo.png"); at load time, parts_flat_load() discovers numbered frame variants ("foo_01.png", "foo_02.png", ...) and builds a frame list. At render time, the active frame is resolved from the local timeline clock using three parameters: - span: number of timeline ticks each frame is displayed - loop_type: 0 = play once and stop at last frame, 1 = loop forward, 2 = ping-pong (forward then backward) - local: the timeline's current local time --- src/parts/flat.c | 88 ++++++++++++++++++++++++++++++++++++-- src/parts/parts_internal.h | 12 +++++- src/parts/render.c | 71 ++++++++++++++++-------------- 3 files changed, 135 insertions(+), 36 deletions(-) diff --git a/src/parts/flat.c b/src/parts/flat.c index 3609c9a..dfa88ae 100644 --- a/src/parts/flat.c +++ b/src/parts/flat.c @@ -62,10 +62,15 @@ void parts_flat_free(struct parts_flat *f) free_string(f->name); flat_layer_state_free(f->root_state); if (f->textures) { - for (size_t i = 0; i < f->nr_textures; i++) + for (size_t i = 0; i < f->nr_libraries; i++) gfx_delete_texture(&f->textures[i]); free(f->textures); } + if (f->stop_motion_frames) { + for (size_t i = 0; i < f->nr_libraries; i++) + free(f->stop_motion_frames[i].lib_indices); + free(f->stop_motion_frames); + } } int parts_flat_find_library(struct flat *fl, const char *name) @@ -192,6 +197,75 @@ static bool flat_advance_children(struct flat *fl, return changed; } +// Build the CG list for a STOP_MOTION library: frame 0 is the base +// library named in `sm->library_name`; subsequent frames are numbered +// variants `${stem}_NN.${ext}`, terminated by the first missing entry. +static void build_stop_motion_frames(struct parts_flat *f, int sm_lib_idx) +{ + struct flat_stop_motion *sm = &f->flat->libraries[sm_lib_idx].stop_motion; + const char *base = sm->library_name->text; + + int base_idx = parts_flat_find_library(f->flat, base); + if (base_idx < 0) + return; + + const char *dot = strrchr(base, '.'); + if (!dot) { + WARNING("flat: invalid STOP_MOTION library name '%s'", base); + return; + } + + int *indices = xmalloc(sizeof(int)); + indices[0] = base_idx; + int count = 1; + + char name_buf[512]; + for (int i = 1; ; i++) { + snprintf(name_buf, sizeof(name_buf), "%.*s_%02d.%s", + (int)(dot - base), base, i, dot + 1); + int idx = parts_flat_find_library(f->flat, name_buf); + if (idx < 0) + break; + indices = xrealloc(indices, (count + 1) * sizeof(int)); + indices[count++] = idx; + } + + f->stop_motion_frames[sm_lib_idx].lib_indices = indices; + f->stop_motion_frames[sm_lib_idx].count = count; +} + +static int stop_motion_frame_index(int loop_type, int span, int local, int total) +{ + if (total <= 1) + return 0; + int idx = (span > 0) ? local / span : 0; + switch (loop_type) { + case 0: // stop at last frame + return min(idx, total - 1); + case 1: // loop + return idx % total; + case 2: { // ping-pong + int period = total * 2 - 2; + idx = idx % period; + return idx < total ? idx : period - idx; + } + default: + return 0; + } +} + +int parts_flat_stop_motion_get_cg_lib(struct parts_flat *f, int sm_lib_idx, int local) +{ + if (sm_lib_idx < 0 || (size_t)sm_lib_idx >= f->nr_libraries) + return -1; + struct flat_stop_motion_frames *frames = &f->stop_motion_frames[sm_lib_idx]; + if (frames->count <= 0) + return -1; + struct flat_stop_motion *sm = &f->flat->libraries[sm_lib_idx].stop_motion; + int frame = stop_motion_frame_index(sm->loop_type, sm->span, local, frames->count); + return frames->lib_indices[frame]; +} + bool parts_flat_load(struct parts *parts, struct parts_flat *f, struct string *filename) { if (f->flat) { @@ -221,8 +295,10 @@ bool parts_flat_load(struct parts *parts, struct parts_flat *f, struct string *f f->root_state = flat_layer_state_new(f->flat->nr_timelines); // Load textures for CG libraries. - f->nr_textures = f->flat->nr_libraries; - f->textures = xcalloc(f->nr_textures, sizeof(Texture)); + f->nr_libraries = f->flat->nr_libraries; + f->textures = xcalloc(f->nr_libraries, sizeof(Texture)); + f->stop_motion_frames = xcalloc(f->nr_libraries, + sizeof(struct flat_stop_motion_frames)); for (size_t i = 0; i < f->flat->nr_libraries; i++) { struct flat_library *lib = &f->flat->libraries[i]; if (lib->type != FLAT_LIB_CG) @@ -235,6 +311,10 @@ bool parts_flat_load(struct parts *parts, struct parts_flat *f, struct string *f gfx_init_texture_with_cg(&f->textures[i], cg); cg_free(cg); } + for (size_t i = 0; i < f->flat->nr_libraries; i++) { + if (f->flat->libraries[i].type == FLAT_LIB_STOP_MOTION) + build_stop_motion_frames(f, (int)i); + } // TODO: The original engine performs per-frame hit testing against // the flat's visible sprites (with full transform chain), rather than @@ -244,7 +324,7 @@ bool parts_flat_load(struct parts *parts, struct parts_flat *f, struct string *f if (w <= 0 || h <= 0) { w = 0; h = 0; - for (size_t i = 0; i < f->nr_textures; i++) { + for (size_t i = 0; i < f->nr_libraries; i++) { if (f->textures[i].w > w) w = f->textures[i].w; if (f->textures[i].h > h) diff --git a/src/parts/parts_internal.h b/src/parts/parts_internal.h index 7b4c48c..9fb09fc 100644 --- a/src/parts/parts_internal.h +++ b/src/parts/parts_internal.h @@ -310,6 +310,13 @@ struct flat_layer_state { size_t nr_timelines; }; +// Per-frame CG list for a STOP_MOTION library. lib_indices[k] is +// the library index of the CG to display at frame k. +struct flat_stop_motion_frames { + int *lib_indices; + int count; +}; + struct parts_flat { struct parts_common common; struct string *name; @@ -320,8 +327,10 @@ struct parts_flat { int end_frame; int pending_seek_delta; struct flat_layer_state *root_state; + size_t nr_libraries; Texture *textures; // indexed by library index (only CG libs have valid textures) - size_t nr_textures; + // Indexed by library index. Only entries for STOP_MOTION libraries have lib_indices populated. + struct flat_stop_motion_frames *stop_motion_frames; }; struct parts_movie { @@ -559,6 +568,7 @@ void parts_flat_free(struct parts_flat *f); bool parts_flat_load(struct parts *parts, struct parts_flat *f, struct string *filename); bool parts_flat_update(struct parts_flat *f, int passed_time); int parts_flat_find_library(struct flat *fl, const char *name); +int parts_flat_stop_motion_get_cg_lib(struct parts_flat *f, int sm_lib_idx, int local); // layoutbox.c void parts_do_layout(struct parts *parts); diff --git a/src/parts/render.c b/src/parts/render.c index 3a49d2d..a7685ab 100644 --- a/src/parts/render.c +++ b/src/parts/render.c @@ -187,12 +187,40 @@ static void render_flat_layer(struct parts *parts, struct parts_flat *f, struct flat_timeline *timelines, size_t nr_timelines, mat4 parent, float parent_alpha); +static void render_flat_cg(struct parts *parts, Texture *tex, + struct flat_key_data_graphic *key, mat4 combined, float alpha) +{ + if (!tex->handle) + return; + + set_draw_filter_blend_func(key->draw_filter); + + mat4 render_m; + glm_mat4_copy(combined, render_m); + glm_scale(render_m, (vec3){ tex->w, tex->h, 1.0f }); + + Rectangle rect; + if (key->area_width && key->area_height) { + rect = (Rectangle){ key->area_x, key->area_y, key->area_width, key->area_height }; + } else { + rect = (Rectangle){ 0, 0, tex->w, tex->h }; + } + + vec3 add_color = { key->add_r / 255.0f, key->add_g / 255.0f, key->add_b / 255.0f }; + vec3 mul_color = { key->mul_r / 255.0f, key->mul_g / 255.0f, key->mul_b / 255.0f }; + parts_render_texture(tex, render_m, &rect, alpha, add_color, mul_color, + key->draw_filter, parts->alpha_clipper_parts_no); + + if (key->draw_filter != PARTS_DRAW_FILTER_NORMAL) + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); +} + static void render_flat_item(struct parts *parts, struct parts_flat *f, struct flat_layer_state *state, size_t tl_idx, - struct flat_timeline *tl, - struct flat_key_data_graphic *key, + struct flat_timeline *tl, int local, mat4 parent, float parent_alpha) { + struct flat_key_data_graphic *key = &tl->graphic.keys[local]; int lib_idx = parts_flat_find_library(f->flat, tl->library_name->text); if (lib_idx < 0 || (size_t)lib_idx >= f->flat->nr_libraries) return; @@ -214,33 +242,9 @@ static void render_flat_item(struct parts *parts, struct parts_flat *f, float alpha = parent_alpha * key->alpha / 255.0f; switch (lib->type) { - case FLAT_LIB_CG: { - if ((size_t)lib_idx >= f->nr_textures || !f->textures[lib_idx].handle) - return; - Texture *tex = &f->textures[lib_idx]; - - set_draw_filter_blend_func(key->draw_filter); - - mat4 render_m; - glm_mat4_copy(combined, render_m); - glm_scale(render_m, (vec3){ tex->w, tex->h, 1.0f }); - - Rectangle rect; - if (key->area_width && key->area_height) { - rect = (Rectangle){ key->area_x, key->area_y, key->area_width, key->area_height }; - } else { - rect = (Rectangle){ 0, 0, tex->w, tex->h }; - } - - vec3 add_color = { key->add_r / 255.0f, key->add_g / 255.0f, key->add_b / 255.0f }; - vec3 mul_color = { key->mul_r / 255.0f, key->mul_g / 255.0f, key->mul_b / 255.0f }; - parts_render_texture(tex, render_m, &rect, alpha, add_color, mul_color, - key->draw_filter, parts->alpha_clipper_parts_no); - - if (key->draw_filter != PARTS_DRAW_FILTER_NORMAL) - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); + case FLAT_LIB_CG: + render_flat_cg(parts, &f->textures[lib_idx], key, combined, alpha); break; - } case FLAT_LIB_TIMELINE: { struct flat_layer_state *child = state->children[tl_idx]; if (child) { @@ -251,7 +255,13 @@ static void render_flat_item(struct parts *parts, struct parts_flat *f, } break; } - // TODO: support FLAT_LIB_STOP_MOTION and FLAT_LIB_EMITTER + case FLAT_LIB_STOP_MOTION: { + int cg_idx = parts_flat_stop_motion_get_cg_lib(f, lib_idx, local); + if (cg_idx >= 0 && (size_t)cg_idx < f->nr_libraries) + render_flat_cg(parts, &f->textures[cg_idx], key, combined, alpha); + break; + } + // TODO: support FLAT_LIB_EMITTER default: break; } @@ -273,9 +283,8 @@ static void render_flat_layer(struct parts *parts, struct parts_flat *f, if (local >= (int)tl->graphic.count) continue; - struct flat_key_data_graphic *key = &tl->graphic.keys[local]; - render_flat_item(parts, f, state, i, tl, key, parent, parent_alpha); + render_flat_item(parts, f, state, i, tl, local, parent, parent_alpha); } }