PartsEngine: Add initial support for Flat animation

Only nested timelines and CG rendering are implemented. Stop motion and
particle emitters are not yet implemented.
This commit is contained in:
kichikuou
2026-04-05 13:52:27 +09:00
parent f43ca3a31a
commit fe952354c5
10 changed files with 743 additions and 27 deletions
+1
View File
@@ -98,6 +98,7 @@ target_sources(xsystem4 PRIVATE
src/parts/construction.c
src/parts/debug.c
src/parts/flash.c
src/parts/flat.c
src/parts/input.c
src/parts/motion.c
src/parts/parts.c
+14
View File
@@ -238,4 +238,18 @@ bool PE_StartPartsFlash(int parts_no, int state);
bool PE_GoFramePartsFlash(int parts_no, int frame_no, int state);
int PE_GetPartsFlashEndFrame(int parts_no, int state);
// flat.c
bool PE_ExistsFlatFile(struct string *filename);
bool PE_SetPartsFlat(int parts_no, struct string *filename, int state);
bool PE_IsPartsFlatEnd(int parts_no, int state);
int PE_GetPartsFlatCurrentFrameNumber(int parts_no, int state);
bool PE_BackPartsFlatBeginFrame(int parts_no, int state);
bool PE_StepPartsFlatFinalFrame(int parts_no, int state);
bool PE_SetPartsFlatSurfaceArea(int parts_no, int x, int y, int w, int h, int state);
bool PE_SetPartsFlatAndStop(int parts_no, struct string *filename, int state);
bool PE_StopPartsFlat(int parts_no, int state);
bool PE_StartPartsFlat(int parts_no, int state);
bool PE_GoFramePartsFlat(int parts_no, int frame_no, int state);
int PE_GetPartsFlatEndFrame(int parts_no, int state);
#endif /* SYSTEM4_PARTS_H */
+12 -12
View File
@@ -563,18 +563,18 @@ HLL_LIBRARY(PartsEngine,
HLL_TODO_EXPORT(Parts_SetPartsRectangleDetectionSurfaceArea, PartsEngine_Parts_SetPartsRectangleDetectionSurfaceArea),
HLL_EXPORT(Parts_SetPartsCGDetectionSize, PE_SetPartsCGDetectionSize),
HLL_TODO_EXPORT(Parts_SetPartsCGDetectionSurfaceArea, PartsEngine_Parts_SetPartsCGDetectionSurfaceArea),
HLL_TODO_EXPORT(Parts_SetPartsFlat, PartsEngine_Parts_SetPartsFlat),
HLL_TODO_EXPORT(Parts_IsPartsFlatEnd, PartsEngine_Parts_IsPartsFlatEnd),
HLL_TODO_EXPORT(Parts_GetPartsFlatCurrentFrameNumber, PartsEngine_Parts_GetPartsFlatCurrentFrameNumber),
HLL_TODO_EXPORT(Parts_BackPartsFlatBeginFrame, PartsEngine_Parts_BackPartsFlatBeginFrame),
HLL_TODO_EXPORT(Parts_StepPartsFlatFinalFrame, PartsEngine_Parts_StepPartsFlatFinalFrame),
HLL_TODO_EXPORT(Parts_SetPartsFlatSurfaceArea, PartsEngine_Parts_SetPartsFlatSurfaceArea),
HLL_TODO_EXPORT(Parts_SetPartsFlatAndStop, PartsEngine_Parts_SetPartsFlatAndStop),
HLL_TODO_EXPORT(Parts_StopPartsFlat, PartsEngine_Parts_StopPartsFlat),
HLL_TODO_EXPORT(Parts_StartPartsFlat, PartsEngine_Parts_StartPartsFlat),
HLL_TODO_EXPORT(Parts_GoFramePartsFlat, PartsEngine_Parts_GoFramePartsFlat),
HLL_TODO_EXPORT(Parts_GetPartsFlatEndFrame, PartsEngine_Parts_GetPartsFlatEndFrame),
HLL_TODO_EXPORT(Parts_ExistsFlatFile, PartsEngine_Parts_ExistsFlatFile),
HLL_EXPORT(Parts_SetPartsFlat, PE_SetPartsFlat),
HLL_EXPORT(Parts_IsPartsFlatEnd, PE_IsPartsFlatEnd),
HLL_EXPORT(Parts_GetPartsFlatCurrentFrameNumber, PE_GetPartsFlatCurrentFrameNumber),
HLL_EXPORT(Parts_BackPartsFlatBeginFrame, PE_BackPartsFlatBeginFrame),
HLL_EXPORT(Parts_StepPartsFlatFinalFrame, PE_StepPartsFlatFinalFrame),
HLL_EXPORT(Parts_SetPartsFlatSurfaceArea, PE_SetPartsFlatSurfaceArea),
HLL_EXPORT(Parts_SetPartsFlatAndStop, PE_SetPartsFlatAndStop),
HLL_EXPORT(Parts_StopPartsFlat, PE_StopPartsFlat),
HLL_EXPORT(Parts_StartPartsFlat, PE_StartPartsFlat),
HLL_EXPORT(Parts_GoFramePartsFlat, PE_GoFramePartsFlat),
HLL_EXPORT(Parts_GetPartsFlatEndFrame, PE_GetPartsFlatEndFrame),
HLL_EXPORT(Parts_ExistsFlatFile, PE_ExistsFlatFile),
HLL_TODO_EXPORT(Parts_GetPartsFlatDataInfo, PartsEngine_Parts_GetPartsFlatDataInfo),
HLL_TODO_EXPORT(Parts_ChangeFlatCG, PartsEngine_Parts_ChangeFlatCG),
HLL_TODO_EXPORT(Parts_ChangeFlatSound, PartsEngine_Parts_ChangeFlatSound),
+1
View File
@@ -59,6 +59,7 @@ xsystem4 = [version_h,
'parts/construction.c',
'parts/debug.c',
'parts/flash.c',
'parts/flat.c',
'parts/input.c',
'parts/motion.c',
'parts/parts.c',
+17 -1
View File
@@ -200,6 +200,15 @@ static void parts_flash_to_json(struct parts_flash *flash, cJSON *out, bool verb
cJSON_AddNumberToObject(out, "current_frame", flash->current_frame);
}
static void parts_flat_to_json(struct parts_flat *flat, cJSON *out, bool verbose)
{
if (flat->name)
cJSON_AddSjisToObject(out, "name", flat->name->text);
if (flat->root_state)
cJSON_AddNumberToObject(out, "current_frame", flat->root_state->current_frame);
cJSON_AddNumberToObject(out, "end_frame", flat->end_frame);
}
static cJSON *parts_state_to_json(struct parts_state *state, bool verbose)
{
static const char *state_types[PARTS_NR_TYPES] = {
@@ -211,7 +220,8 @@ static cJSON *parts_state_to_json(struct parts_state *state, bool verbose)
[PARTS_HGAUGE] = "hgauge",
[PARTS_VGAUGE] = "vgauge",
[PARTS_CONSTRUCTION_PROCESS] = "construction_process",
[PARTS_FLASH] = "flash"
[PARTS_FLASH] = "flash",
[PARTS_FLAT] = "flat"
};
const char *type = "invalid";
if (state->type >= 0 && state->type < PARTS_NR_TYPES)
@@ -249,6 +259,9 @@ static cJSON *parts_state_to_json(struct parts_state *state, bool verbose)
case PARTS_FLASH:
parts_flash_to_json(&state->flash, obj, verbose);
break;
case PARTS_FLAT:
parts_flat_to_json(&state->flat, obj, verbose);
break;
}
return obj;
@@ -529,6 +542,9 @@ static void parts_list_print(struct parts *parts, int indent)
case PARTS_FLASH:
sys_message("(flash %s)", display_sjis0(state->flash.name->text));
break;
case PARTS_FLAT:
sys_message("(flat %s)", state->flat.name ? display_sjis0(state->flat.name->text) : "(null)");
break;
}
sys_message(" @ z=%d (%d)\n", parts->global.z, parts->local.z);
+475
View File
@@ -0,0 +1,475 @@
/* Copyright (C) 2026 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include "system4.h"
#include "system4/archive.h"
#include "system4/cg.h"
#include "system4/flat.h"
#include "system4/string.h"
#include "asset_manager.h"
#include "gfx/gfx.h"
#include "parts.h"
#include "parts_internal.h"
#include "xsystem4.h"
static struct flat_layer_state *flat_layer_state_new(size_t nr_timelines)
{
struct flat_layer_state *s = xcalloc(1, sizeof(struct flat_layer_state));
s->suppress_advance = true;
s->jump_target = -1;
s->nr_timelines = nr_timelines;
s->last_script_keys = xmalloc(nr_timelines * sizeof(int));
for (size_t i = 0; i < nr_timelines; i++)
s->last_script_keys[i] = -2; // uninitialized
s->children = xcalloc(nr_timelines, sizeof(struct flat_layer_state *));
return s;
}
static void flat_layer_state_free(struct flat_layer_state *s)
{
if (!s)
return;
for (size_t i = 0; i < s->nr_timelines; i++)
flat_layer_state_free(s->children[i]);
free(s->children);
free(s->last_script_keys);
free(s);
}
void parts_flat_free(struct parts_flat *f)
{
if (f->flat)
flat_free(f->flat);
if (f->name)
free_string(f->name);
flat_layer_state_free(f->root_state);
if (f->textures) {
for (size_t i = 0; i < f->nr_textures; i++)
gfx_delete_texture(&f->textures[i]);
free(f->textures);
}
}
int parts_flat_find_library(struct flat *fl, const char *name)
{
for (size_t i = 0; i < fl->nr_libraries; i++) {
if (strcmp(fl->libraries[i].name->text, name) == 0)
return (int)i;
}
return -1;
}
static int flat_compute_max_frame(struct flat_timeline *timelines, size_t nr_timelines)
{
int max_frame = 0;
for (size_t i = 0; i < nr_timelines; i++) {
int end = timelines[i].begin_frame + timelines[i].frame_count;
if (end > max_frame)
max_frame = end;
}
return max_frame;
}
// Process script timelines for the current frame.
static void flat_process_layer_scripts(struct flat_layer_state *state,
struct flat_timeline *timelines, size_t nr_timelines)
{
if (state->stopped)
return;
for (size_t i = 0; i < nr_timelines; i++) {
if (timelines[i].type != FLAT_TIMELINE_SCRIPT)
continue;
// Find last key where frame_index <= current_frame
int matched = -1;
for (size_t j = 0; j < timelines[i].script.count; j++) {
if ((int)timelines[i].script.keys[j].frame_index <= state->current_frame)
matched = (int)j;
else
break;
}
// Only trigger when matched key differs
if (state->last_script_keys[i] == matched)
continue;
state->last_script_keys[i] = matched;
if (matched < 0)
continue;
struct flat_script_key *key = &timelines[i].script.keys[matched];
if (key->has_jump) {
state->jump_target = key->jump_frame;
} else if (key->is_stop) {
state->stopped = true;
}
}
}
// Advance children regardless of this layer's stopped state.
static bool flat_advance_children(struct flat *fl,
struct flat_layer_state *state,
struct flat_timeline *timelines, size_t nr_timelines);
// Advance a layer by one tick. Returns true if any frame changed.
static bool flat_advance_layer(struct flat *fl,
struct flat_layer_state *state,
struct flat_timeline *timelines, size_t nr_timelines)
{
int max_end_frame = flat_compute_max_frame(timelines, nr_timelines) - 1;
int prev_frame = state->current_frame;
do {
int next_frame;
if (state->jump_target != -1) {
next_frame = state->jump_target;
state->jump_target = -1;
} else {
int increment = (state->stopped || state->suppress_advance) ? 0 : 1;
next_frame = state->current_frame + increment;
}
state->suppress_advance = false;
state->current_frame = min(max(0, next_frame), max_end_frame);
flat_process_layer_scripts(state, timelines, nr_timelines);
} while (state->jump_target != -1);
bool child_changed = flat_advance_children(fl, state, timelines, nr_timelines);
return state->current_frame != prev_frame || child_changed;
}
static bool flat_advance_children(struct flat *fl,
struct flat_layer_state *state,
struct flat_timeline *timelines, size_t nr_timelines)
{
bool changed = false;
for (size_t i = 0; i < nr_timelines; i++) {
if (timelines[i].type != FLAT_TIMELINE_GRAPHIC)
continue;
// Destroy child states when the parent frame leaves the active range.
int local = state->current_frame - timelines[i].begin_frame;
if (local < 0 || local >= timelines[i].frame_count) {
if (state->children[i]) {
flat_layer_state_free(state->children[i]);
state->children[i] = NULL;
}
continue;
}
int lib_idx = parts_flat_find_library(fl, timelines[i].library_name->text);
if (lib_idx < 0)
continue;
struct flat_library *lib = &fl->libraries[lib_idx];
if (lib->type != FLAT_LIB_TIMELINE)
continue;
if (!state->children[i])
state->children[i] = flat_layer_state_new(lib->timeline.nr_timelines);
if (flat_advance_layer(fl, state->children[i],
lib->timeline.timelines, lib->timeline.nr_timelines)) {
changed = true;
}
}
return changed;
}
bool parts_flat_load(struct parts *parts, struct parts_flat *f, struct string *filename)
{
if (f->flat) {
parts_flat_free(f);
memset(f, 0, sizeof(struct parts_flat));
}
if (!filename)
return false;
struct archive_data *dfile = asset_get_by_name(ASSET_FLAT, filename->text, NULL);
if (!dfile)
return false;
int error;
f->flat = flat_open(dfile->data, dfile->size, &error);
if (!f->flat) {
archive_free_data(dfile);
return false;
}
f->name = string_dup(filename);
f->stopped = false;
f->elapsed = 0;
f->end_frame = flat_compute_max_frame(
f->flat->timelines, f->flat->nr_timelines) - 1;
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));
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)
continue;
struct cg *cg = cg_load_buffer((uint8_t *)lib->cg.data, lib->cg.size);
if (!cg) {
WARNING("flat: failed to load CG for library '%s'", lib->name->text);
continue;
}
gfx_init_texture_with_cg(&f->textures[i], cg);
cg_free(cg);
}
// TODO: The original engine performs per-frame hit testing against
// the flat's visible sprites (with full transform chain), rather than
// using a static hitbox. For now, estimate dimensions from CG textures.
int w = f->flat->hdr.width;
int h = f->flat->hdr.height;
if (w <= 0 || h <= 0) {
w = 0;
h = 0;
for (size_t i = 0; i < f->nr_textures; i++) {
if (f->textures[i].w > w)
w = f->textures[i].w;
if (f->textures[i].h > h)
h = f->textures[i].h;
}
}
if (w > 0 && h > 0)
parts_set_dims(parts, &f->common, w, h);
// f->flat contains pointers into dfile->data, but it should be safe to
// free now since we've loaded all CG data we need.
archive_free_data(dfile);
return true;
}
bool parts_flat_update(struct parts_flat *f, int passed_time)
{
if (!f->flat || f->flat->hdr.fps <= 0)
return false;
// Consume pending forward seek (from GoFramePartsFlat).
if (f->pending_seek_delta > 0) {
for (int i = 0; i < f->pending_seek_delta; i++) {
flat_advance_layer(f->flat, f->root_state,
f->flat->timelines, f->flat->nr_timelines);
}
f->pending_seek_delta = 0;
f->elapsed = 0;
return true;
}
// One advance after load / seek.
if (f->needs_advance) {
f->needs_advance = false;
bool changed = flat_advance_layer(f->flat, f->root_state,
f->flat->timelines, f->flat->nr_timelines);
f->elapsed = 0;
return changed;
}
if (f->stopped)
return false;
f->elapsed += passed_time;
int delta = (int)((int64_t)f->elapsed * f->flat->hdr.fps / 1000);
if (!delta)
return false;
f->elapsed -= (unsigned)((int64_t)delta * 1000 / f->flat->hdr.fps);
bool any_changed = false;
for (int i = 0; i < delta; i++) {
if (flat_advance_layer(f->flat, f->root_state,
f->flat->timelines, f->flat->nr_timelines))
any_changed = true;
}
return any_changed;
}
bool PE_ExistsFlatFile(struct string *filename)
{
if (!filename)
return false;
return asset_exists_by_name(ASSET_FLAT, filename->text, NULL);
}
bool PE_SetPartsFlat(int parts_no, struct string *filename, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
struct parts_flat *f = parts_get_flat(parts, state);
if (parts_flat_load(parts, f, filename)) {
f->needs_advance = true;
parts_dirty(parts);
return true;
}
return false;
}
bool PE_IsPartsFlatEnd(int parts_no, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
struct parts_flat *f = parts_get_flat(parts, state);
if (!f->flat || !f->root_state)
return true;
return f->root_state->current_frame == f->end_frame;
}
int PE_GetPartsFlatCurrentFrameNumber(int parts_no, int state)
{
if (!parts_state_valid(--state))
return 0;
struct parts *parts = parts_get(parts_no);
struct parts_flat *f = parts_get_flat(parts, state);
if (!f->flat || !f->root_state)
return 0;
return f->root_state->current_frame + f->pending_seek_delta;
}
bool PE_BackPartsFlatBeginFrame(int parts_no, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
struct parts_flat *f = parts_get_flat(parts, state);
if (!f->flat)
return false;
f->pending_seek_delta = 0;
flat_layer_state_free(f->root_state);
f->root_state = flat_layer_state_new(f->flat->nr_timelines);
f->needs_advance = true;
f->elapsed = 0;
parts_dirty(parts);
return true;
}
bool PE_StepPartsFlatFinalFrame(int parts_no, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
struct parts_flat *f = parts_get_flat(parts, state);
if (!f->flat || !f->root_state)
return false;
// Directly set to end frame.
f->root_state->current_frame = f->end_frame;
f->root_state->stopped = false;
f->root_state->suppress_advance = true;
f->pending_seek_delta = 0;
f->needs_advance = true;
f->elapsed = 0;
parts_dirty(parts);
return true;
}
bool PE_SetPartsFlatSurfaceArea(int parts_no, int x, int y, int w, int h, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
struct parts_flat *f = parts_get_flat(parts, state);
parts_set_surface_area(parts, &f->common, x, y, w, h);
return true;
}
bool PE_SetPartsFlatAndStop(int parts_no, struct string *filename, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
struct parts_flat *f = parts_get_flat(parts, state);
bool result = parts_flat_load(parts, f, filename);
f->stopped = true;
return result;
}
bool PE_StopPartsFlat(int parts_no, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
struct parts_flat *f = parts_get_flat(parts, state);
if (!f->flat)
return false;
f->stopped = true;
return true;
}
bool PE_StartPartsFlat(int parts_no, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
struct parts_flat *f = parts_get_flat(parts, state);
if (!f->flat)
return false;
f->stopped = false;
return true;
}
bool PE_GoFramePartsFlat(int parts_no, int frame_no, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
struct parts_flat *f = parts_get_flat(parts, state);
if (!f->flat || !f->root_state)
return false;
int current = f->root_state->current_frame;
if (frame_no < current) {
// Backward seek: directly set current_frame.
f->root_state->current_frame = min(max(0, frame_no), f->end_frame);
f->root_state->stopped = false;
f->root_state->suppress_advance = true;
f->pending_seek_delta = 0;
f->needs_advance = true;
} else {
// Forward seek: defer to next update.
f->pending_seek_delta = frame_no - current;
}
f->elapsed = 0;
parts_dirty(parts);
return true;
}
int PE_GetPartsFlatEndFrame(int parts_no, int state)
{
if (!parts_state_valid(--state))
return 0;
struct parts *parts = parts_get(parts_no);
struct parts_flat *f = parts_get_flat(parts, state);
return f->flat ? f->end_frame : -1;
}
+36 -13
View File
@@ -175,6 +175,9 @@ static void parts_state_free(struct parts_state *state)
case PARTS_FLASH:
parts_flash_free(&state->flash);
break;
case PARTS_FLAT:
parts_flat_free(&state->flat);
break;
}
memset(state, 0, sizeof(struct parts_state));
}
@@ -220,6 +223,7 @@ void parts_state_reset(struct parts_state *state, enum parts_type type)
case PARTS_CG:
case PARTS_ANIMATION:
case PARTS_FLASH:
case PARTS_FLAT:
break;
}
}
@@ -288,6 +292,14 @@ struct parts_flash *parts_get_flash(struct parts *parts, int state)
return &parts->states[state].flash;
}
struct parts_flat *parts_get_flat(struct parts *parts, int state)
{
if (parts->states[state].type != PARTS_FLAT) {
parts_state_reset(&parts->states[state], PARTS_FLAT);
}
return &parts->states[state].flat;
}
static Point calculate_offset(int mode, int w, int h)
{
switch (mode) {
@@ -838,20 +850,10 @@ void parts_set_surface_area(struct parts *parts, struct parts_common *common, in
parts_common_recalculate_hitbox(parts, common);
}
static void parts_update_loop(struct parts *parts, int passed_time)
static bool parts_animation_update(struct parts_animation *anim, int passed_time)
{
if (parts->states[parts->state].type == PARTS_FLASH) {
if (parts_flash_update(&parts->states[parts->state].flash, passed_time))
parts_dirty(parts);
return;
}
if (parts->states[parts->state].type != PARTS_ANIMATION)
return;
struct parts_animation *anim = &parts->states[parts->state].anim;
if (passed_time <= 0 || !anim->nr_frames)
return;
return false;
const unsigned elapsed = anim->elapsed + passed_time;
const unsigned frame_diff = elapsed / anim->frame_time;
@@ -861,12 +863,33 @@ static void parts_update_loop(struct parts *parts, int passed_time)
anim->elapsed = remainder;
anim->current_frame = (anim->current_frame + frame_diff) % anim->nr_frames;
anim->common.texture = anim->frames[anim->current_frame];
parts_dirty(parts);
return true;
} else {
anim->elapsed = elapsed;
return false;
}
}
static void parts_update_loop(struct parts *parts, int passed_time)
{
bool dirty = false;
switch (parts->states[parts->state].type) {
case PARTS_ANIMATION:
dirty = parts_animation_update(&parts->states[parts->state].anim, passed_time);
break;
case PARTS_FLASH:
dirty = parts_flash_update(&parts->states[parts->state].flash, passed_time);
break;
case PARTS_FLAT:
dirty = parts_flat_update(&parts->states[parts->state].flat, passed_time);
break;
default:
break;
}
if (dirty)
parts_dirty(parts);
}
static void parts_update_animation(int passed_time)
{
struct parts *parts;
+37 -1
View File
@@ -103,7 +103,8 @@ enum parts_type {
PARTS_VGAUGE,
PARTS_CONSTRUCTION_PROCESS,
PARTS_FLASH,
#define PARTS_NR_TYPES (PARTS_FLASH+1)
PARTS_FLAT,
#define PARTS_NR_TYPES (PARTS_FLAT+1)
};
struct parts_common {
@@ -285,6 +286,33 @@ struct parts_flash {
TAILQ_HEAD(, parts_flash_object) display_list;
};
struct flat_layer_state {
int current_frame;
bool stopped;
bool suppress_advance;
int jump_target; // pending jump frame, -1 = none
// Per-timeline: last matched script key index for change detection
// (-2 = uninitialized, -1 = no match, >= 0 = key index)
int *last_script_keys;
// Per-timeline: child layer state (for TIMELINE libs), NULL otherwise
struct flat_layer_state **children;
size_t nr_timelines;
};
struct parts_flat {
struct parts_common common;
struct string *name;
struct flat *flat;
bool stopped;
bool needs_advance;
unsigned elapsed;
int end_frame;
int pending_seek_delta;
struct flat_layer_state *root_state;
Texture *textures; // indexed by library index (only CG libs have valid textures)
size_t nr_textures;
};
struct parts_state {
enum parts_type type;
union {
@@ -296,6 +324,7 @@ struct parts_state {
struct parts_gauge gauge;
struct parts_construction_process cproc;
struct parts_flash flash;
struct parts_flat flat;
};
};
@@ -355,6 +384,7 @@ struct parts_gauge *parts_get_hgauge(struct parts *parts, int state);
struct parts_gauge *parts_get_vgauge(struct parts *parts, int state);
struct parts_construction_process *parts_get_construction_process(struct parts *parts, int state);
struct parts_flash *parts_get_flash(struct parts *parts, int state);
struct parts_flat *parts_get_flat(struct parts *parts, int state);
void parts_set_pos(struct parts *parts, Point pos);
void parts_set_global_pos(Point pos);
void parts_set_dims(struct parts *parts, struct parts_common *common, int w, int h);
@@ -426,6 +456,12 @@ bool parts_flash_load(struct parts *parts, struct parts_flash *f, struct string
bool parts_flash_update(struct parts_flash *f, int passed_time);
bool parts_flash_seek(struct parts_flash *f, int frame);
// flat.c
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);
// debug.c
struct sprite;
void parts_debug_init(void);
+130
View File
@@ -15,11 +15,14 @@
*/
#include <limits.h>
#include <math.h>
#include <assert.h>
#include <cglm/cglm.h>
#include "system4.h"
#include "system4/hashtable.h"
#include "system4/string.h"
#include "system4/flat.h"
#include "gfx/gfx.h"
#include "scene.h"
@@ -166,6 +169,130 @@ static void parts_render_cg(struct parts *parts, struct parts_common *common)
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
}
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);
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)
{
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;
struct flat_library *lib = &f->flat->libraries[lib_idx];
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;
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);
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 });
mat4 combined;
glm_mat4_mul(parent, layer_m, combined);
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];
switch (key->draw_filter) {
case 1: // additive
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE);
break;
case 2: // multiply
glBlendFuncSeparate(GL_DST_COLOR, GL_ZERO, GL_ZERO, GL_ONE);
break;
case 3: // screen
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE);
break;
default:
break;
}
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,
parts->alpha_clipper_parts_no);
if (key->draw_filter != 0)
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
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);
}
break;
}
// TODO: support FLAT_LIB_STOP_MOTION and FLAT_LIB_EMITTER
default:
break;
}
}
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)
{
// reverse order for correct z-ordering
for (size_t i = nr_timelines; i-- > 0;) {
struct flat_timeline *tl = &timelines[i];
if (tl->type != FLAT_TIMELINE_GRAPHIC)
continue;
int local = state->current_frame - tl->begin_frame;
if (local < 0 || local >= tl->frame_count)
continue;
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);
}
}
static void parts_render_flat(struct parts *parts, struct parts_flat *f)
{
if (!f->flat || !f->root_state)
return;
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_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);
}
static void parts_render_flash_shape(struct parts *parts, struct parts_flash *f, struct parts_flash_object *obj, struct swf_tag_define_shape *tag)
{
struct texture *src = ht_get_int(f->bitmaps, tag->fill_style.bitmap_id, NULL);
@@ -301,6 +428,9 @@ void parts_render(struct parts *parts)
case PARTS_FLASH:
parts_render_flash(parts, &state->flash);
break;
case PARTS_FLAT:
parts_render_flat(parts, &state->flat);
break;
}
}
+20
View File
@@ -336,6 +336,20 @@ static void load_parts_flash(struct iarray_reader *r, struct parts *parts,
parts_flash_seek(flash, iarray_read(r));
}
static void save_parts_flat(struct iarray_writer *w, struct parts_flat *flat)
{
iarray_write_string_or_null(w, flat->name);
}
static void load_parts_flat(struct iarray_reader *r, struct parts *parts,
struct parts_flat *flat)
{
struct string *name = iarray_read_string_or_null(r);
parts_flat_load(parts, flat, name);
free_string(name);
flat->needs_advance = true;
}
static void save_parts_state(struct iarray_writer *w, struct parts_state *state)
{
iarray_write(w, state->type);
@@ -369,6 +383,9 @@ static void save_parts_state(struct iarray_writer *w, struct parts_state *state)
case PARTS_FLASH:
save_parts_flash(w, &state->flash);
break;
case PARTS_FLAT:
save_parts_flat(w, &state->flat);
break;
}
}
@@ -408,6 +425,9 @@ static void load_parts_state(struct iarray_reader *r, struct parts *parts,
case PARTS_FLASH:
load_parts_flash(r, parts, &state->flash);
break;
case PARTS_FLAT:
load_parts_flat(r, parts, &state->flat);
break;
}
}