mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-09-22 23:18:01 +03:00
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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user