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..8063dae 100644 --- a/src/parts/render.c +++ b/src/parts/render.c @@ -30,15 +30,6 @@ #include "parts_internal.h" -#ifndef M_PI -#define M_PI (3.14159265358979323846) -#endif - -static inline float deg2rad(float deg) -{ - return deg * (M_PI / 180.0); -} - static struct { struct shader shader; GLint blend_rate; @@ -97,7 +88,7 @@ static void parts_render_texture(struct texture *texture, mat4 mw_transform, Rec // Calculate the inverse of the clipper's world matrix. mat4 clip_mw = GLM_MAT4_IDENTITY_INIT; glm_translate(clip_mw, (vec3) { clipper->global.pos.x, clipper->global.pos.y, 0 }); - glm_rotate_z(clip_mw, clipper->local.rotation.z * (M_PI/180.0), clip_mw); + glm_rotate_z(clip_mw, glm_rad(clipper->local.rotation.z), clip_mw); glm_scale(clip_mw, (vec3){ clipper->global.scale.x, clipper->global.scale.y, 1.0 }); glm_translate(clip_mw, (vec3){ c_common->origin_offset.x, c_common->origin_offset.y, 0 }); glm_scale(clip_mw, (vec3){ c_common->w, c_common->h, 1.0 }); @@ -157,7 +148,7 @@ static void parts_render_cg(struct parts *parts, struct parts_common *common) // FIXME: need perspective for 3D rotate //glm_rotate_x(mw_transform, parts->rotation.x, mw_transform); //glm_rotate_y(mw_transform, parts->rotation.y, mw_transform); - glm_rotate_z(mw_transform, parts->local.rotation.z * (M_PI/180.0), mw_transform); + glm_rotate_z(mw_transform, glm_rad(parts->local.rotation.z), mw_transform); glm_scale(mw_transform, (vec3){ parts->global.scale.x, parts->global.scale.y, 1.0 }); glm_translate(mw_transform, (vec3){ common->origin_offset.x, common->origin_offset.y, 0 }); glm_scale(mw_transform, (vec3){ common->w, common->h, 1.0 }); @@ -185,14 +176,49 @@ static void parts_render_cg(struct parts *parts, struct parts_common *common) static void render_flat_layer(struct parts *parts, struct parts_flat *f, struct flat_layer_state *state, struct flat_timeline *timelines, size_t nr_timelines, - mat4 parent, float parent_alpha); + mat4 parent, float parent_alpha, int parent_draw_filter); + +static void render_flat_cg(struct parts *parts, Texture *tex, + struct flat_key_data_graphic *key, mat4 combined, float alpha, int draw_filter) +{ + if (!tex->handle) + return; + + set_draw_filter_blend_func(draw_filter); + + mat4 render_m; + glm_mat4_copy(combined, render_m); + // Kill the Z-output row to pin clip_z at the near plane, avoiding + // near/far clipping of 3D-rotated sprites. + render_m[0][2] = render_m[1][2] = render_m[2][2] = render_m[3][2] = 0.0f; + // area_x/area_y select a sub-rectangle of the texture atlas, but + // should not shift the on-screen position. This translation cancels + // the offset that the sub-rect's top-left would otherwise introduce. + glm_translate(render_m, (vec3){ -(float)key->area_x, -(float)key->area_y, 0.0f }); + 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, + draw_filter, parts->alpha_clipper_parts_no); + + if (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, - mat4 parent, float parent_alpha) + struct flat_timeline *tl, int local, + mat4 parent, float parent_alpha, int parent_draw_filter) { + 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; @@ -202,56 +228,58 @@ static void render_flat_item(struct parts *parts, struct parts_flat *f, float pos_x = (f->flat->hdr.version > 4) ? key->pos_x.f : (float)key->pos_x.i; float pos_y = (f->flat->hdr.version > 4) ? key->pos_y.f : (float)key->pos_y.i; + // Per-key local matrix: + // M_layer = T(pos) * Rz * Rx * Ry * Scale * T(-origin) * ReverseScale + // The original engine projects 3D-rotated sprites through a proper + // perspective, but we use an orthographic approximation for simplicity. mat4 layer_m = GLM_MAT4_IDENTITY_INIT; glm_translate(layer_m, (vec3){ pos_x, pos_y, 0 }); - glm_rotate_z(layer_m, deg2rad(key->angle_z), layer_m); + if (key->angle_z != 0) + glm_rotate_z(layer_m, glm_rad(key->angle_z), layer_m); + if (key->angle_x != 0) + glm_rotate_x(layer_m, glm_rad(-key->angle_x), layer_m); + if (key->angle_y != 0) + glm_rotate_y(layer_m, glm_rad(key->angle_y), layer_m); glm_scale(layer_m, (vec3){ key->scale_x, key->scale_y, 1.0f }); glm_translate(layer_m, (vec3){ -(float)key->origin_x, -(float)key->origin_y, 0 }); + if (key->reverse_lr || key->reverse_tb) { + glm_scale(layer_m, (vec3){ + key->reverse_lr ? -1.0f : 1.0f, + key->reverse_tb ? -1.0f : 1.0f, + 1.0f }); + } mat4 combined; glm_mat4_mul(parent, layer_m, combined); float alpha = parent_alpha * key->alpha / 255.0f; + // Effective draw filter: this key's own filter overrides for its subtree; + // otherwise the cascading parent filter persists. + int draw_filter = key->draw_filter != PARTS_DRAW_FILTER_NORMAL + ? key->draw_filter : parent_draw_filter; + 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, draw_filter); break; - } case FLAT_LIB_TIMELINE: { struct flat_layer_state *child = state->children[tl_idx]; if (child) { render_flat_layer(parts, f, child, lib->timeline.timelines, lib->timeline.nr_timelines, - combined, alpha); + combined, alpha, draw_filter); } 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, draw_filter); + break; + } + // TODO: support FLAT_LIB_EMITTER default: break; } @@ -260,7 +288,7 @@ static void render_flat_item(struct parts *parts, struct parts_flat *f, static void render_flat_layer(struct parts *parts, struct parts_flat *f, struct flat_layer_state *state, struct flat_timeline *timelines, size_t nr_timelines, - mat4 parent, float parent_alpha) + mat4 parent, float parent_alpha, int parent_draw_filter) { // reverse order for correct z-ordering for (size_t i = nr_timelines; i-- > 0;) { @@ -273,9 +301,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, parent_draw_filter); } } @@ -286,12 +313,12 @@ static void parts_render_flat(struct parts *parts, struct parts_flat *f) mat4 base = GLM_MAT4_IDENTITY_INIT; glm_translate(base, (vec3){ parts->global.pos.x, parts->global.pos.y, 0 }); - glm_rotate_z(base, deg2rad(parts->local.rotation.z), base); + glm_rotate_z(base, glm_rad(parts->local.rotation.z), base); glm_scale(base, (vec3){ parts->global.scale.x, parts->global.scale.y, 1.0f }); render_flat_layer(parts, f, f->root_state, f->flat->timelines, f->flat->nr_timelines, - base, parts->global.alpha / 255.0f); + base, parts->global.alpha / 255.0f, PARTS_DRAW_FILTER_NORMAL); } static void parts_render_flash_shape(struct parts *parts, struct parts_flash *f, struct parts_flash_object *obj, struct swf_tag_define_shape *tag)