Files
nunuhara_xsystem4/src/3d/particle.c
T
kichikuou 10710e9908 ReignEngine: Cache parsed particle effects
Now parsed particle-effect definitions are cached in a hash map in the
plugin object. To make it possible, static part of `struct
particle_effect` and `struct particle_object` are factored out to
`struct pae` and `struct pae_object` respectively.
2022-12-28 13:18:41 +09:00

996 lines
31 KiB
C

/* Copyright (C) 2022 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 <cglm/cglm.h>
#include "system4.h"
#include "system4/aar.h"
#include "system4/cg.h"
#include "system4/hashtable.h"
#include "system4/utfsjis.h"
#include "3d_internal.h"
static inline float randf(void)
{
return rand() / (float)RAND_MAX;
}
static void parse_error(const char *msg, char *input)
{
char *eol = strchr(input, '\n');
*eol = '\0';
WARNING("Parse error: %s Near: \"%s\"", msg, input);
}
static enum particle_type parse_particle_type(const char *s)
{
if (!strcmp(s, "ビルボード"))
return PARTICLE_TYPE_BILLBOARD;
else if (!strcmp(s, "ポリゴンオブジェクト"))
return PARTICLE_TYPE_POLYGON_OBJECT;
else if (!strcmp(s, "剣ブラー"))
return PARTICLE_TYPE_SWORD_BLUR;
else if (!strcmp(s, "カメラ振動"))
return PARTICLE_TYPE_CAMERA_QUAKE;
WARNING("unknown object type \"%s\"", s);
return (enum particle_type)-1;
}
static enum particle_move_type parse_particle_move_type(const char *s)
{
if (!strcmp(s, "固定"))
return PARTICLE_MOVE_FIXED;
else if (!strcmp(s, "直線"))
return PARTICLE_MOVE_LINEAR;
else if (!strcmp(s, "放射"))
return PARTICLE_MOVE_EMITTER;
WARNING("unknown move type \"%s\"", s);
return (enum particle_move_type)-1;
}
static enum particle_blend_type parse_particle_blend_type(const char *s)
{
if (!strcmp(s, "標準"))
return PARTICLE_BLEND_NORMAL;
else if (!strcmp(s, "加算"))
return PARTICLE_BLEND_ADDITIVE;
WARNING("unknown blend type \"%s\"", s);
return (enum particle_blend_type)-1;
}
static enum particle_frame_ref_type parse_particle_frame_ref_type(const char *s, int *param)
{
if (!strcmp(s, "エフェクト"))
return PARTICLE_FRAME_REF_EFFECT;
else if (sscanf(s, "ターゲット[%d]", param) == 1)
return PARTICLE_FRAME_REF_TARGET;
WARNING("unknown frame ref type \"%s\"", s);
return (enum particle_frame_ref_type)-1;
}
static char *parse_quoted_string(char **ptext)
{
char *text = *ptext;
if (*text++ != '"') {
parse_error("quoted string expected.", text);
return NULL;
}
char *p = strchr(text, '"');
if (!p) {
parse_error("unfinished string.", text);
return NULL;
}
*p = '\0';
*ptext = p + 1;
return utf2sjis(text, 0);
}
#define MATCH(fmt, ...) (len = 0, sscanf(text, fmt " %n", ##__VA_ARGS__, &len), text += len, len)
#define STRING_BUF_SIZE 64
#define KEYWORD "%63s"
#define QUOTED_STRING "\"%63[^\"]\""
static float *parse_float_list(char **ptext, int *nr)
{
char *text = *ptext;
int len;
float f;
if (!MATCH("%f", &f)) {
parse_error("float expected.", text);
return NULL;
}
int cap = 16;
float *floats = xmalloc(cap * sizeof(float));
floats[0] = f;
*nr = 1;
while (MATCH(" , %f", &f)) {
if (*nr == cap) {
cap *= 2;
floats = xrealloc(floats, cap * sizeof(float));
}
floats[(*nr)++] = f;
}
*ptext = text;
return floats;
}
static int *parse_int_list(char **ptext, int *nr)
{
char *text = *ptext;
int len;
int n;
if (!MATCH("%d", &n)) {
parse_error("int expected.", text);
return NULL;
}
int cap = 16;
int *ints = xmalloc(cap * sizeof(int));
ints[0] = n;
*nr = 1;
while (MATCH(" , %d", &n)) {
if (*nr == cap) {
cap *= 2;
ints = xrealloc(ints, cap * sizeof(int));
}
ints[(*nr)++] = n;
}
*ptext = text;
return ints;
}
static char **parse_qstr_list(char **ptext, int *nr, bool ignore_empty)
{
char *text = *ptext;
int len;
int cap = 16;
char **strs = xmalloc(cap * sizeof(char *));
*nr = 0;
do {
if (*nr == cap) {
cap *= 2;
strs = xrealloc(strs, cap * sizeof(char *));
}
char *s = parse_quoted_string(&text);
if (!s) {
for (int i = 0; i < *nr; i++)
free(strs[i]);
free(strs);
return NULL;
}
if (s[0] == '\0' && ignore_empty)
free(s);
else
strs[(*nr)++] = s;
} while (MATCH(" , "));
*ptext = text;
return strs;
}
static struct particle_position *parse_position(char **ptext)
{
char *text = *ptext;
int len;
struct particle_position *epos = xcalloc(1, sizeof(struct particle_position));
int n;
float x, y, z;
char string_buf[STRING_BUF_SIZE];
for (int i = 0; i < NR_PARTICLE_POSITION_UNITS; i++) {
struct particle_position_unit *unit = &epos->units[i];
if (MATCH("なし")) {
unit->type = PARTICLE_POS_UNIT_NONE;
} else if (MATCH("絶対位置( %f , %f , %f )", &x, &y, &z)) {
unit->type = PARTICLE_POS_UNIT_ABSOLUTE;
unit->absolute[0] = x;
unit->absolute[1] = y;
unit->absolute[2] = -z;
} else if (MATCH("ボーン[ %d , "QUOTED_STRING" , %f , %f , %f ]", &n, string_buf, &x, &y, &z) ||
MATCH("ボーン[ %d , \"\" , %f , %f , %f ]", &n, &x, &y, &z)) {
unit->type = PARTICLE_POS_UNIT_BONE;
unit->bone.target_index = n;
unit->bone.name = utf2sjis(string_buf, 0);
unit->bone.offset[0] = x;
unit->bone.offset[1] = y;
unit->bone.offset[2] = -z;
} else if (MATCH("ターゲット[ %d , %f , %f , %f ]", &n, &x, &y, &z)) {
unit->type = PARTICLE_POS_UNIT_TARGET;
unit->target.index = n;
unit->target.offset[0] = x;
unit->target.offset[1] = y;
unit->target.offset[2] = -z;
} else if (MATCH("ランダム方向( %f )", &x)) {
unit->type = PARTICLE_POS_UNIT_RAND;
unit->rand.f = x;
} else if (MATCH("ランダム方向Y+( %f )", &x)) {
unit->type = PARTICLE_POS_UNIT_RAND_POSITIVE_Y;
unit->rand.f = x;
} else {
parse_error("unknown position unit.", text);
free(epos);
return NULL;
}
if (!MATCH(" + "))
break;
}
*ptext = text;
return epos;
}
static struct pae *parse_pae(char *text)
{
struct pae *pae = xcalloc(1, sizeof(struct pae));
struct pae_object *current_object = NULL;
for (;;) {
// Skip whitespaces and comments
for (;;) {
while (isspace(*text))
text++;
if (text[0] == '/' && text[1] == '/') {
char *newline = strchr(text, '\n');
if (newline)
text = newline + 1;
else
text += strlen(text);
} else {
break;
}
}
int len;
int n, n2;
float f, f2;
char string_buf[STRING_BUF_SIZE];
if (!current_object) {
// Toplevel
if (!*text) { // EOF
return pae;
} else if (MATCH("オブジェクト " QUOTED_STRING " = {", string_buf)) {
pae->objects = xrealloc_array(pae->objects, pae->nr_objects, pae->nr_objects + 1, sizeof(struct pae_object));
current_object = &pae->objects[pae->nr_objects++];
current_object->name = utf2sjis(string_buf, 0);
// Default values.
current_object->move_type = PARTICLE_MOVE_LINEAR;
current_object->texture_anime_frame = 1.0;
current_object->child_end_slope = 1.0;
} else {
parse_error("unknown toplevel.", text);
goto err;
}
} else {
// Object context
if (!*text) {
WARNING("unexpected EOF");
return false;
} else if (MATCH("}")) {
current_object = NULL;
} else if (MATCH("種類 = " KEYWORD, string_buf)) {
current_object->type = parse_particle_type(string_buf);
if (current_object->type < 0)
goto err;
} else if (MATCH("移動タイプ = " KEYWORD, string_buf)) {
current_object->move_type = parse_particle_move_type(string_buf);
if (current_object->move_type < 0)
goto err;
} else if (MATCH("アップベクトルタイプ = %d", &n)) {
if (n > PARTICLE_UP_VECTOR_TYPE_MAX) {
WARNING("unknown up vector type %d", n);
n = PARTICLE_UP_VECTOR_TYPE_MAX;
}
current_object->up_vector_type = n;
} else if (MATCH("移動カーブ = %f", &f)) {
current_object->move_curve = f;
} else if (MATCH("位置 = ")) {
current_object->position[0] = parse_position(&text);
if (!current_object->position[0])
goto err;
if (MATCH(" , ")) {
current_object->position[1] = parse_position(&text);
if (!current_object->position[1])
goto err;
}
} else if (MATCH("サイズ = %f", &f)) {
current_object->size[0] = f;
if (MATCH(" , %f", &f2))
current_object->size[1] = f2;
else
current_object->size[1] = f;
} else if (MATCH("サイズ2 = ")) {
current_object->sizes2 = parse_float_list(&text, &current_object->nr_sizes2);
if (!current_object->sizes2)
goto err;
} else if (MATCH("サイズX = ")) {
current_object->sizes_x = parse_float_list(&text, &current_object->nr_sizes_x);
if (!current_object->sizes_x)
goto err;
} else if (MATCH("サイズY = ")) {
current_object->sizes_y = parse_float_list(&text, &current_object->nr_sizes_y);
if (!current_object->sizes_y)
goto err;
} else if (MATCH("サイズタイプ = ")) {
current_object->size_types = parse_int_list(&text, &current_object->nr_size_types);
if (!current_object->size_types)
goto err;
} else if (MATCH("サイズXタイプ = ")) {
current_object->size_x_types = parse_int_list(&text, &current_object->nr_size_x_types);
if (!current_object->size_x_types)
goto err;
} else if (MATCH("サイズYタイプ = ")) {
current_object->size_y_types = parse_int_list(&text, &current_object->nr_size_y_types);
if (!current_object->size_y_types)
goto err;
} else if (MATCH("テクスチャ = ")) {
current_object->textures = parse_qstr_list(&text, &current_object->nr_textures, true);
if (!current_object->textures)
goto err;
} else if (MATCH("ブレンドタイプ = " KEYWORD, string_buf)) {
current_object->blend_type = parse_particle_blend_type(string_buf);
if (current_object->blend_type < 0)
goto err;
} else if (MATCH("テクスチャアニメフレーム = %f", &f)) {
current_object->texture_anime_frame = f;
} else if (MATCH("テクスチャアニメ時間 = %d", &n)) {
current_object->texture_anime_time = n;
} else if (MATCH("ポリゴン = ")) {
current_object->polygon_name = parse_quoted_string(&text);
if (!current_object->polygon_name)
goto err;
} else if (MATCH("フレーム = %d , %d", &n, &n2)) {
current_object->begin_frame = n;
current_object->end_frame = n2;
} else if (MATCH("停止フレーム = %d", &n)) {
current_object->stop_frame = n;
} else if (MATCH("フレーム参照 = " KEYWORD, string_buf)) {
current_object->frame_ref_type = parse_particle_frame_ref_type(string_buf, &current_object->frame_ref_param);
if (current_object->frame_ref_type < 0)
goto err;
} else if (MATCH("個数 = %d", &n)) {
current_object->nr_particles = n;
} else if (MATCH("アルファフェードインフレーム = %f", &f)) {
current_object->alpha_fadein_frame = f;
} else if (MATCH("アルファフェードイン時間 = %d", &n)) {
current_object->alpha_fadein_time = n;
} else if (MATCH("アルファフェードアウトフレーム = %f", &f)) {
current_object->alpha_fadeout_frame = f;
} else if (MATCH("アルファフェードアウト時間 = %d", &n)) {
current_object->alpha_fadeout_time = n;
} else if (MATCH("X軸自転角度 = %f , %f", &f, &f2)) {
current_object->rotation.begin[0] = f;
current_object->rotation.end[0] = f2;
} else if (MATCH("Y軸自転角度 = %f , %f", &f, &f2)) {
current_object->rotation.begin[1] = f;
current_object->rotation.end[1] = f2;
} else if (MATCH("Z軸自転角度 = %f , %f", &f, &f2)) {
current_object->rotation.begin[2] = f;
current_object->rotation.end[2] = f2;
} else if (MATCH("X軸公転角度 = %f , %f", &f, &f2)) {
current_object->revolution_angle.begin[0] = f;
current_object->revolution_angle.end[0] = f2;
} else if (MATCH("Y軸公転角度 = %f , %f", &f, &f2)) {
current_object->revolution_angle.begin[1] = f;
current_object->revolution_angle.end[1] = f2;
} else if (MATCH("Z軸公転角度 = %f , %f", &f, &f2)) {
current_object->revolution_angle.begin[2] = f;
current_object->revolution_angle.end[2] = f2;
} else if (MATCH("X軸公転距離 = %f , %f", &f, &f2)) {
current_object->revolution_distance.begin[0] = f;
current_object->revolution_distance.end[0] = f2;
} else if (MATCH("Y軸公転距離 = %f , %f", &f, &f2)) {
current_object->revolution_distance.begin[1] = f;
current_object->revolution_distance.end[1] = f2;
} else if (MATCH("Z軸公転距離 = %f , %f", &f, &f2)) {
current_object->revolution_distance.begin[2] = f;
current_object->revolution_distance.end[2] = f2;
} else if (MATCH("カーブX距離 = %f", &f)) {
current_object->curve_length[0] = f;
} else if (MATCH("カーブY距離 = %f", &f)) {
current_object->curve_length[1] = f;
} else if (MATCH("カーブZ距離 = %f", &f)) {
current_object->curve_length[2] = -f;
} else if (MATCH("子フレーム = %d", &n)) {
current_object->child_frame = n;
} else if (MATCH("子距離 = %f", &f)) {
current_object->child_length = f;
} else if (MATCH("子傾き = %f", &f)) {
current_object->child_begin_slope = f;
} else if (MATCH("子傾き2 = %f", &f)) {
current_object->child_end_slope = f;
} else if (MATCH("子生成開始フレーム = %f", &f)) {
current_object->child_create_begin_frame = f;
} else if (MATCH("子生成終了フレーム = %f", &f)) {
current_object->child_create_end_frame = f;
} else if (MATCH("子移動方向タイプ = %d", &n)) {
if (n > PARTICLE_CHILD_MOVE_DIR_TYPE_MAX) {
WARNING("unknown child move dir type %d", n);
n = PARTICLE_CHILD_MOVE_DIR_TYPE_MAX;
}
current_object->child_move_dir_type = n;
} else if (MATCH("方向タイプ = %d", &n)) {
if (n > PARTICLE_DIR_TYPE_MAX) {
WARNING("unknown dir type %d", n);
n = PARTICLE_DIR_TYPE_MAX;
}
current_object->dir_type = n;
} else if (MATCH("ダメージ = ")) {
current_object->damages = parse_int_list(&text, &current_object->nr_damages);
if (!current_object->damages)
goto err;
} else if (MATCH("オフセットX = %f", &f)) {
current_object->offset_x = f;
} else if (MATCH("オフセットY = %f", &f)) {
current_object->offset_y = f;
} else {
parse_error("unknown object field.", text);
goto err;
}
}
}
err:
pae_free(pae);
return NULL;
}
static void load_textures(struct pae *effect, struct archive *aar)
{
effect->textures = ht_create(16);
for (int i = 0; i < effect->nr_objects; i++) {
struct pae_object *po = &effect->objects[i];
for (int j = 0; j < po->nr_textures; j++) {
const char *name = po->textures[j];
if (ht_get(effect->textures, name, NULL))
continue; // already loaded.
struct archive_data *dfile = RE_get_aar_entry(aar, effect->path, name, ".bmp");
if (!dfile)
dfile = RE_get_aar_entry(aar, effect->path, name, ".tga");
if (!dfile) {
WARNING("cannot load texture %s\\%s", effect->path, name);
continue;
}
struct cg *cg = cg_load_data(dfile);
if (!cg) {
WARNING("cg_load_data failed: %s", dfile->name);
archive_free_data(dfile);
continue;
}
archive_free_data(dfile);
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cg->metrics.w, cg->metrics.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, cg->pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
struct billboard_texture *bt = xcalloc(1, sizeof(struct billboard_texture));
bt->texture = texture;
bt->has_alpha = cg->metrics.has_alpha;
ht_put(effect->textures, name, bt);
cg_free(cg);
}
}
}
static struct RE_instance *get_target(struct RE_instance *inst, int index)
{
int target_id = inst->target[index];
if (target_id < 0 || target_id >= inst->plugin->nr_instances)
return NULL;
return inst->plugin->instances[target_id];
}
static void eval_pos_for_object(struct RE_instance *inst, struct particle_position *pp, vec3 dest)
{
glm_vec3_zero(dest);
if (!pp)
return;
for (int i = 0; i < NR_PARTICLE_POSITION_UNITS; i++) {
struct particle_position_unit *unit = &pp->units[i];
switch (unit->type) {
case PARTICLE_POS_UNIT_NONE:
break;
case PARTICLE_POS_UNIT_RAND:
case PARTICLE_POS_UNIT_RAND_POSITIVE_Y:
// These are computed per instance, in eval_pos_for_instance().
break;
case PARTICLE_POS_UNIT_ABSOLUTE:
glm_vec3_add(dest, unit->absolute, dest);
break;
case PARTICLE_POS_UNIT_BONE:
{
struct RE_instance *target = get_target(inst, unit->bone.target_index);
int bone = RE_instance_get_bone_index(target, unit->bone.name);
vec3 target_pos;
if (RE_instance_trans_local_pos_to_world_pos_by_bone(target, bone, unit->bone.offset, target_pos))
glm_vec3_add(dest, target_pos, dest);
else
glm_vec3_add(dest, unit->bone.offset, dest);
}
break;
case PARTICLE_POS_UNIT_TARGET:
{
struct RE_instance *target = get_target(inst, unit->target.index);
if (!target)
break;
vec3 scale = {
target->column_radius,
target->column_height,
target->column_radius
};
mat4 transform;
glm_scale_make(transform, scale);
glm_rotate_y(transform, -glm_rad(target->column_angle), transform);
vec3 target_pos;
glm_mat4_mulv3(transform, unit->target.offset, 1.0f, target_pos);
glm_vec3_addadd(target->pos, target->column_pos, target_pos);
glm_vec3_add(dest, target_pos, dest);
}
break;
}
}
}
static void eval_pos_for_instance(struct particle_position *pp, vec3 dest)
{
glm_vec3_zero(dest);
if (!pp)
return;
for (int i = 0; i < NR_PARTICLE_POSITION_UNITS; i++) {
struct particle_position_unit *unit = &pp->units[i];
switch (unit->type) {
case PARTICLE_POS_UNIT_NONE:
break;
case PARTICLE_POS_UNIT_ABSOLUTE:
case PARTICLE_POS_UNIT_BONE:
case PARTICLE_POS_UNIT_TARGET:
// These are computed per object, in eval_pos_for_object().
break;
case PARTICLE_POS_UNIT_RAND:
dest[0] += (randf() * 2.0 - 1.0) * unit->rand.f;
dest[1] += (randf() * 2.0 - 1.0) * unit->rand.f;
dest[2] += (randf() * 2.0 - 1.0) * unit->rand.f;
break;
case PARTICLE_POS_UNIT_RAND_POSITIVE_Y:
dest[0] += (randf() * 2.0 - 1.0) * unit->rand.f;
dest[1] += randf() * unit->rand.f;
dest[2] += (randf() * 2.0 - 1.0) * unit->rand.f;
break;
}
}
}
static float eval_child_slope(struct pae_object *po)
{
float slope = glm_lerp(po->child_begin_slope, po->child_end_slope, randf());
if (slope > 1.0)
slope = 0.5 + 0.5 / slope; // ???
return (1.0 + slope) * GLM_PI_2;
}
static void interpolate_pos(vec3 begin, vec3 end, float move_curve, float t, vec3 dest)
{
if (move_curve == 0.0f) {
glm_vec3_copy(end, dest);
return;
}
float x = move_curve > 0.0f ? powf(t, move_curve) : 1.0f - powf(1.0f - t, -move_curve);
glm_vec3_lerp(begin, end, x, dest);
}
static void init_particle_instances(struct particle_object *po)
{
struct pae_object *pae_obj = po->pae_obj;
po->instances = xcalloc(pae_obj->nr_particles, sizeof(struct particle_instance));
for (int i = 0; i < pae_obj->nr_particles; i++) {
struct particle_instance *pi = &po->instances[i];
switch (pae_obj->move_type) {
case PARTICLE_MOVE_FIXED:
case PARTICLE_MOVE_LINEAR:
pi->begin_frame = pae_obj->begin_frame;
pi->end_frame = pae_obj->end_frame;
break;
case PARTICLE_MOVE_EMITTER:
pi->begin_frame = glm_lerp(pae_obj->child_create_begin_frame, pae_obj->child_create_end_frame, (float)i / pae_obj->nr_particles);
pi->end_frame = pi->begin_frame + pae_obj->child_frame;
pi->roll_angle = eval_child_slope(pae_obj);
pi->pitch_angle = randf() * (2.0f * GLM_PIf);
break;
}
eval_pos_for_instance(pae_obj->position[0], pi->random_offset.begin);
eval_pos_for_instance(pae_obj->position[1], pi->random_offset.end);
}
}
struct pae *pae_load(struct archive *aar, const char *path)
{
const char *basename = strrchr(path, '\\');
basename = basename ? basename + 1 : path;
struct archive_data *dfile = RE_get_aar_entry(aar, path, basename, ".pae");
if (!dfile) {
WARNING("%s\\%s.pae: not found", path, basename);
return NULL;
}
char *sjis_text = xmalloc(dfile->size + 1);
memcpy(sjis_text, dfile->data, dfile->size);
// Replace null characters (written by buggy .pae formatter) with spaces.
for (size_t i = 0; i < dfile->size; i++) {
if (sjis_text[i] == '\0')
sjis_text[i] = ' ';
}
sjis_text[dfile->size] = '\0';
// We could parse the SJIS text directly, but convert it to UTF-8 for code
// clarity.
char *text = sjis2utf(sjis_text, dfile->size);
free(sjis_text);
struct pae *pae = parse_pae(text);
free(text);
if (!pae) {
WARNING("could not parse %s", dfile->name);
archive_free_data(dfile);
return NULL;
}
archive_free_data(dfile);
pae->path = strdup(path);
load_textures(pae, aar);
for (int i = 0; i < pae->nr_objects; i++) {
struct pae_object *po = &pae->objects[i];
if (po->polygon_name && *po->polygon_name) {
char *pol_path = xmalloc(strlen(path) + strlen(po->polygon_name) + 2);
sprintf(pol_path, "%s\\%s", path, po->polygon_name);
po->model = model_load(aar, pol_path);
free(pol_path);
}
}
return pae;
}
struct particle_effect *particle_effect_create(struct pae *pae)
{
struct particle_effect *effect = xcalloc(1, sizeof(struct particle_effect));
effect->pae = pae;
effect->objects = xcalloc(pae->nr_objects, sizeof(struct particle_object));
effect->camera_quake_enabled = true;
for (int i = 0; i < pae->nr_objects; i++) {
struct particle_object *po = &effect->objects[i];
po->pae_obj = &pae->objects[i];
init_particle_instances(po);
}
return effect;
}
static void free_billboard_texture(void *value)
{
struct billboard_texture *bt = value;
glDeleteTextures(1, &bt->texture);
free(bt);
}
static void particle_position_free(struct particle_position *pos)
{
if (!pos)
return;
for (int i = 0; i < NR_PARTICLE_POSITION_UNITS; i++) {
if (pos->units[i].type == PARTICLE_POS_UNIT_BONE)
free(pos->units[i].bone.name);
}
free(pos);
}
void pae_free(struct pae *pae)
{
for (int i = 0; i < pae->nr_objects; i++) {
struct pae_object *po = &pae->objects[i];
free(po->name);
for (int j = 0; j < NR_PARTICLE_POSITIONS; j++)
particle_position_free(po->position[j]);
free(po->sizes2);
free(po->sizes_x);
free(po->sizes_y);
free(po->size_types);
free(po->size_x_types);
free(po->size_y_types);
for (int j = 0; j < po->nr_textures; j++)
free(po->textures[j]);
free(po->textures);
free(po->polygon_name);
free(po->damages);
if (po->model)
model_free(po->model);
}
ht_foreach_value(pae->textures, free_billboard_texture);
ht_free(pae->textures);
free(pae->objects);
free(pae->path);
free(pae);
}
void particle_effect_free(struct particle_effect *effect)
{
for (int i = 0; i < effect->pae->nr_objects; i++) {
struct particle_object *obj = &effect->objects[i];
free(obj->instances);
}
free(effect->objects);
free(effect);
}
void pae_calc_frame_range(struct pae *pae, struct motion *motion)
{
int begin_frame = INT_MAX;
int end_frame = INT_MIN;
for (int i = 0; i < pae->nr_objects; i++) {
struct pae_object *po = &pae->objects[i];
int obj_begin = po->begin_frame;
int obj_end = max(po->end_frame, po->child_create_end_frame + po->child_frame);
if (begin_frame > obj_begin)
begin_frame = obj_begin;
if (end_frame < obj_end)
end_frame = obj_end;
}
motion->frame_begin = motion->loop_frame_begin = begin_frame;
motion->frame_end = motion->loop_frame_end = end_frame;
}
static void update_camera_quake(struct RE_instance *inst, struct pae_object *po)
{
float frame = inst->motion->current_frame - po->begin_frame;
float total_frames = po->end_frame - po->begin_frame;
if (frame < 0 || frame >= total_frames)
return;
const float ease_in_duration = 4.0f;
const float ease_out_duration = 4.0f;
float amplitude = 1.0f;
if (total_frames - frame < ease_out_duration)
amplitude *= (total_frames - frame) / ease_out_duration;
else if (frame < ease_in_duration)
amplitude *= frame / ease_in_duration;
inst->plugin->camera.quake_pitch = sinf(frame * 1.1) * amplitude * -8.0f;
inst->plugin->camera.quake_yaw = sinf(frame * 1.7) * amplitude * 12.0f;
}
void particle_effect_update(struct RE_instance *inst)
{
struct particle_effect *effect = inst->effect;
if (!effect)
return;
for (int i = 0; i < effect->pae->nr_objects; i++) {
struct particle_object *po = &effect->objects[i];
struct pae_object *pae_obj = po->pae_obj;
if (pae_obj->type == PARTICLE_TYPE_CAMERA_QUAKE) {
if (effect->camera_quake_enabled)
update_camera_quake(inst, pae_obj);
continue;
}
// These cannot be precomputed, because they may depend on the target's
// *current* bone matrix.
eval_pos_for_object(inst, pae_obj->position[0], po->pos.begin);
eval_pos_for_object(inst, pae_obj->position[1], po->pos.end);
glm_vec3_sub(po->pos.end, po->pos.begin, po->move_vec);
switch (pae_obj->dir_type) {
case PARTICLE_DIR_TYPE_NORMAL:
glm_vec3_normalize(po->move_vec);
glm_vec3_ortho(po->move_vec, po->move_ortho);
glm_vec3_normalize(po->move_ortho);
break;
case PARTICLE_DIR_TYPE_XY_PLANE:
po->move_vec[2] = 0.0f;
glm_vec3_normalize(po->move_vec);
glm_vec3_copy(GLM_ZUP, po->move_ortho);
break;
}
}
}
static void calc_emitter_pos(struct particle_object *po, struct particle_instance *pi, float t, vec3 dest, vec3 move_vec)
{
struct pae_object *pae_obj = po->pae_obj;
vec3 begin, end;
glm_vec3_add(po->pos.begin, pi->random_offset.begin, begin);
glm_vec3_add(po->pos.end, pi->random_offset.end, end);
interpolate_pos(begin, end, pae_obj->move_curve, t, dest);
if (move_vec)
glm_vec3_sub(end, begin, move_vec);
// Orbital rotation.
vec3 angle, dist;
glm_vec3_lerp(pae_obj->revolution_angle.begin, pae_obj->revolution_angle.end, t, angle);
glm_vec3_lerp(pae_obj->revolution_distance.begin, pae_obj->revolution_distance.end, t, dist);
glm_vec3_scale(angle, GLM_PIf / 180.0f, angle); // deg -> rad
if (pae_obj->revolution_angle.begin[0] != 0.0f || pae_obj->revolution_angle.end[0] != 0.0f) {
dest[1] += dist[0] * sinf(angle[0]);
dest[2] -= dist[0] * cosf(angle[0]);
}
if (pae_obj->revolution_angle.begin[1] != 0.0f || pae_obj->revolution_angle.end[1] != 0.0f) {
dest[0] += dist[1] * sinf(angle[1]);
dest[2] -= dist[1] * cosf(angle[1]);
}
if (pae_obj->revolution_angle.begin[2] != 0.0f || pae_obj->revolution_angle.end[2] != 0.0f) {
dest[1] += dist[2] * sinf(angle[2]);
dest[0] += dist[2] * cosf(angle[2]);
}
// Apply the curve.
glm_vec3_muladds(pae_obj->curve_length, 4.9f * t * (1.0f - t), dest);
}
static void calc_pos_and_up_vector(struct particle_object *po, struct particle_instance *pi, float t, vec3 pos, vec3 up_vector)
{
struct pae_object *pae_obj = po->pae_obj;
switch (pae_obj->move_type) {
case PARTICLE_MOVE_FIXED:
glm_vec3_add(po->pos.begin, pi->random_offset.begin, pos);
glm_vec3_copy(GLM_YUP, up_vector);
break;
case PARTICLE_MOVE_LINEAR:
calc_emitter_pos(po, pi, t, pos, up_vector);
switch (pae_obj->up_vector_type) {
case PARTICLE_UP_VECTOR_Y_AXIS:
case PARTICLE_UP_VECTOR_INSTANCE_MOVE:
glm_vec3_copy(GLM_YUP, up_vector);
break;
case PARTICLE_UP_VECTOR_OBJECT_MOVE:
if (glm_vec3_norm2(up_vector) == 0.0f)
glm_vec3_copy(GLM_XUP, up_vector);
else
glm_vec3_normalize(up_vector);
break;
}
break;
case PARTICLE_MOVE_EMITTER:
{
// Calculate the instance's initial and final position.
float t0 = glm_clamp_zo(glm_percent(pae_obj->begin_frame, pae_obj->end_frame, pi->begin_frame));
vec3_range inst_pos;
calc_emitter_pos(po, pi, t0, inst_pos.begin, NULL);
vec3 inst_move;
glm_vec3_copy(po->move_vec, inst_move);
switch (pae_obj->dir_type) {
case PARTICLE_DIR_TYPE_NORMAL:
glm_vec3_rotate(inst_move, pi->roll_angle, po->move_ortho);
glm_vec3_rotate(inst_move, pi->pitch_angle, po->move_vec);
break;
case PARTICLE_DIR_TYPE_XY_PLANE:
glm_vec3_rotate(inst_move, (pi->pitch_angle < GLM_PIf ? -1.0f : 1.0f) * pi->roll_angle, po->move_ortho);
break;
}
glm_vec3_copy(inst_pos.begin, inst_pos.end);
switch (pae_obj->child_move_dir_type) {
case PARTICLE_CHILD_MOVE_DIR_NORMAL:
glm_vec3_muladds(inst_move, pae_obj->child_length, inst_pos.end);
break;
case PARTICLE_CHILD_MOVE_DIR_REVERSE:
glm_vec3_muladds(inst_move, pae_obj->child_length, inst_pos.begin);
glm_vec3_negate(inst_move);
break;
}
// Current position of the instance.
glm_vec3_lerp(inst_pos.begin, inst_pos.end, t, pos);
// Calculate the up vector.
switch (pae_obj->up_vector_type) {
case PARTICLE_UP_VECTOR_Y_AXIS:
glm_vec3_copy(GLM_YUP, up_vector);
break;
case PARTICLE_UP_VECTOR_OBJECT_MOVE:
glm_vec3_copy(po->move_vec, up_vector);
break;
case PARTICLE_UP_VECTOR_INSTANCE_MOVE:
if (pae_obj->child_length != 0.0f)
glm_vec3_copy(inst_move, up_vector);
else
glm_vec3_copy(GLM_XUP, up_vector);
break;
}
if (pae_obj->dir_type == PARTICLE_DIR_TYPE_XY_PLANE) {
mat3 rot = {{ inst_move[1], -inst_move[0], 0.0f },
{ inst_move[0], inst_move[1], 0.0f },
{ 0.0f, 0.0f, 1.0f }};
glm_mat3_mulv(rot, up_vector, up_vector);
}
}
break;
}
}
static void calc_scale(struct pae_object *po, int frame, vec3 dest)
{
glm_vec3_broadcast(po->sizes2[min(frame, po->nr_sizes2 - 1)], dest);
if (po->nr_sizes_x)
dest[0] *= po->sizes_x[min(frame, po->nr_sizes_x - 1)];
if (po->nr_sizes_y)
dest[1] *= po->sizes_y[min(frame, po->nr_sizes_y - 1)];
}
void particle_object_calc_local_transform(struct RE_instance *inst, struct particle_object *po, struct particle_instance *pi, float frame, mat4 dest)
{
struct pae_object *pae_obj = po->pae_obj;
float t = glm_percent(pi->begin_frame, pi->end_frame, frame);
vec3 pos, up_vector;
calc_pos_and_up_vector(po, pi, t, pos, up_vector);
float rel_frame = frame - pi->begin_frame;
int cur_step = rel_frame;
int next_step = ceilf(rel_frame);
float step_frac = rel_frame - cur_step;
glm_translate_make(dest, pos);
if (pae_obj->type == PARTICLE_TYPE_BILLBOARD) {
mat4 view_mat;
RE_calc_view_matrix(&inst->plugin->camera, up_vector, view_mat);
mat3 rot;
glm_mat4_pick3t(view_mat, rot);
glm_mat4_ins3(rot, dest);
}
vec3 angles;
glm_vec3_lerp(pae_obj->rotation.begin, pae_obj->rotation.end, t, angles);
angles[0] *= -GLM_PIf / 180.0f;
angles[1] *= -GLM_PIf / 180.0f;
angles[2] *= GLM_PIf / 180.0f;
mat4 rotation_mat;
glm_euler_zxy(angles, rotation_mat);
glm_mat4_mul(dest, rotation_mat, dest);
if (pae_obj->nr_sizes2 > 0) {
vec3 scale, scale2;
calc_scale(pae_obj, cur_step, scale);
calc_scale(pae_obj, next_step, scale2);
glm_vec3_lerp(scale, scale2, step_frac, scale);
glm_scale(dest, scale);
} else {
glm_scale_uni(dest, glm_lerp(pae_obj->size[0], pae_obj->size[1], t));
}
glm_translate_x(dest, -pae_obj->offset_x);
glm_translate_y(dest, -pae_obj->offset_y);
}
float pae_object_calc_alpha(struct pae_object *po, struct particle_instance *pi, float frame)
{
frame -= pi->begin_frame;
if (frame < po->alpha_fadein_frame)
return glm_clamp_zo(frame / po->alpha_fadein_frame);
float total_frames = pi->end_frame - pi->begin_frame;
if (total_frames - po->alpha_fadeout_frame < frame)
return glm_clamp_zo((total_frames - frame) / po->alpha_fadeout_frame);
return 1.0;
}