From 909168acdbf880836c311980f22f38a9127d62d6 Mon Sep 17 00:00:00 2001 From: kichikuou Date: Sun, 25 Sep 2022 10:19:44 +0900 Subject: [PATCH 1/2] ReignEngine: Implement particle rendering This completes the implementation of the particle effect system, except for the camera quake effect. --- include/reign.h | 7 + shaders/reign.f.glsl | 29 ++- src/3d/3d_internal.h | 71 +++++- src/3d/particle.c | 554 ++++++++++++++++++++++++++++++++++++++---- src/3d/reign.c | 88 ++++--- src/3d/renderer.c | 167 ++++++++++++- src/hll/ReignEngine.c | 111 +++++++-- 7 files changed, 900 insertions(+), 127 deletions(-) diff --git a/include/reign.h b/include/reign.h index 8c14490..184b9fd 100644 --- a/include/reign.h +++ b/include/reign.h @@ -18,6 +18,7 @@ #define SYSTEM4_REIGN_H #define RE_NR_BACK_CGS 16 +#define RE_NR_INSTANCE_TARGETS 2 #include @@ -124,6 +125,7 @@ struct RE_instance { struct height_detector *height_detector; enum RE_instance_type type; + int target[RE_NR_INSTANCE_TARGETS]; vec3 pos; float pitch, roll, yaw; // in degrees vec3 scale; @@ -137,6 +139,10 @@ struct RE_instance { bool motion_blend; float motion_blend_rate; vec3 ambient; + vec3 column_pos; + float column_height; + float column_radius; + float column_angle; // around the Y axis, in degrees // Lights vec3 vec; @@ -237,6 +243,7 @@ int RE_particle_get_num_damage(struct particle_object *po); int RE_particle_get_damage(struct particle_object *po, int frame); float RE_particle_get_offset_x(struct particle_object *po); float RE_particle_get_offset_y(struct particle_object *po); +bool RE_effect_get_frame_range(struct RE_instance *instance, int *begin_frame, int *end_frame); bool RE_back_cg_set(struct RE_back_cg *bcg, int no); bool RE_back_cg_set_name(struct RE_back_cg *bcg, struct string *name, struct archive *aar); diff --git a/shaders/reign.f.glsl b/shaders/reign.f.glsl index 4b6210c..7f4eefa 100644 --- a/shaders/reign.f.glsl +++ b/shaders/reign.f.glsl @@ -15,6 +15,11 @@ */ #define NR_DIR_LIGHTS 3 + +#define DIFFUSE_EMISSIVE 0 +#define DIFFUSE_NORMAL 1 +#define DIFFUSE_LIGHT_MAP 2 + #define FOG_LINEAR 1 #define FOG_LIGHT_SCATTERING 2 @@ -40,7 +45,7 @@ uniform float specular_shininess; uniform bool use_specular_map; uniform float rim_exponent; uniform vec3 rim_color; -uniform bool use_light_map; +uniform int diffuse_type; uniform bool use_shadow_map; uniform float shadow_bias; uniform int fog_type; @@ -74,16 +79,20 @@ void main() { vec3 frag_rgb = ambient; // Diffuse lighting - vec3 diffuse = vec3(0.0); - for (int i = 0; i < NR_DIR_LIGHTS; i++) { - float half_lambert = dot(norm, -light_dir[i]) * 0.5 + 0.5; - diffuse += mix(dir_lights[i].globe_diffuse, dir_lights[i].diffuse, half_lambert); - } - if (use_light_map) { - diffuse *= texture(light_texture, light_tex_coord).rgb; - } vec4 texel = texture(tex, tex_coord); - frag_rgb += texel.rgb * diffuse; + if (diffuse_type == DIFFUSE_EMISSIVE) { + frag_rgb = texel.rgb; + } else { + vec3 diffuse = vec3(0.0); + for (int i = 0; i < NR_DIR_LIGHTS; i++) { + float half_lambert = dot(norm, -light_dir[i]) * 0.5 + 0.5; + diffuse += mix(dir_lights[i].globe_diffuse, dir_lights[i].diffuse, half_lambert); + } + if (diffuse_type == DIFFUSE_LIGHT_MAP) { + diffuse *= texture(light_texture, light_tex_coord).rgb; + } + frag_rgb += texel.rgb * diffuse; + } // Specular lighting if (specular_strength > 0.0) { diff --git a/src/3d/3d_internal.h b/src/3d/3d_internal.h index 9cfa999..cb2325a 100644 --- a/src/3d/3d_internal.h +++ b/src/3d/3d_internal.h @@ -23,10 +23,16 @@ #include "gfx/gl.h" #include "gfx/gfx.h" +#include "reign.h" #define NR_DIR_LIGHTS 3 #define MAX_BONES 211 // Maximum in TT3 +typedef struct vec3_range { + vec3 begin; + vec3 end; +} vec3_range; + struct model { char *path; int nr_meshes; @@ -127,7 +133,7 @@ struct RE_renderer { GLint rim_exponent; GLint rim_color; GLint camera_pos; - GLint use_light_map; + GLint diffuse_type; GLint light_texture; GLint use_normal_map; GLint normal_texture; @@ -151,6 +157,7 @@ struct RE_renderer { struct billboard_texture { GLuint texture; + bool has_alpha; }; // reign.c @@ -175,6 +182,7 @@ bool RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no); struct height_detector *RE_renderer_create_height_detector(struct RE_renderer *r, struct model *model); void RE_renderer_free_height_detector(struct height_detector *hd); float RE_renderer_detect_height(struct height_detector *hd, float x, float z); +void RE_calc_view_matrix(struct RE_camera *camera, vec3 up, mat4 out); // particle.c @@ -191,6 +199,13 @@ enum particle_move_type { PARTICLE_MOVE_EMITTER = 2, }; +enum particle_up_vector_type { + PARTICLE_UP_VECTOR_Y_AXIS = 0, + PARTICLE_UP_VECTOR_OBJECT_MOVE = 1, + PARTICLE_UP_VECTOR_INSTANCE_MOVE = 2, + PARTICLE_UP_VECTOR_TYPE_MAX = PARTICLE_UP_VECTOR_INSTANCE_MOVE +}; + enum particle_blend_type { PARTICLE_BLEND_NORMAL = 0, PARTICLE_BLEND_ADDITIVE = 1, @@ -201,6 +216,18 @@ enum particle_frame_ref_type { PARTICLE_FRAME_REF_TARGET = 1, }; +enum particle_child_move_dir_type { + PARTICLE_CHILD_MOVE_DIR_NORMAL = 0, + PARTICLE_CHILD_MOVE_DIR_REVERSE = 1, + PARTICLE_CHILD_MOVE_DIR_TYPE_MAX = PARTICLE_CHILD_MOVE_DIR_REVERSE +}; + +enum particle_dir_type { + PARTICLE_DIR_TYPE_NORMAL = 0, + PARTICLE_DIR_TYPE_XY_PLANE = 1, + PARTICLE_DIR_TYPE_MAX = PARTICLE_DIR_TYPE_XY_PLANE +}; + struct particle_position_unit { enum { PARTICLE_POS_UNIT_NONE = 0, @@ -212,13 +239,13 @@ struct particle_position_unit { } type; union { struct { - int n; - vec3 pos; + int index; + vec3 offset; } target; struct { - int n; - // char *name; - vec3 pos; + int target_index; + char *name; + vec3 offset; } bone; struct { float f; @@ -237,7 +264,7 @@ struct particle_object { char *name; enum particle_type type; enum particle_move_type move_type; - int up_vector_type; + enum particle_up_vector_type up_vector_type; float move_curve; struct particle_position *position[NR_PARTICLE_POSITIONS]; float size[2]; @@ -269,9 +296,9 @@ struct particle_object { int alpha_fadein_time; float alpha_fadeout_frame; int alpha_fadeout_time; - vec3 rotation[2]; - vec3 revolution_angle[2]; - vec3 revolution_distance[2]; + vec3_range rotation; + vec3_range revolution_angle; + vec3_range revolution_distance; vec3 curve_length; int child_frame; float child_length; @@ -279,22 +306,42 @@ struct particle_object { float child_end_slope; float child_create_begin_frame; float child_create_end_frame; - int child_move_dir_type; - int dir_type; + enum particle_child_move_dir_type child_move_dir_type; + enum particle_dir_type dir_type; int nr_damages; int *damages; float offset_x; float offset_y; + + // Runtime data + struct model *model; + struct particle_instance *instances; + vec3_range pos; + vec3 move_vec, move_ortho; +}; + +struct particle_instance { + float begin_frame; + float end_frame; + vec3_range random_offset; + float roll_angle; + float pitch_angle; }; struct particle_effect { char *path; + struct hash_table *textures; // name -> struct billboard_texture* + int nr_objects; struct particle_object *objects; }; struct particle_effect *particle_effect_load(struct archive *aar, const char *path); void particle_effect_free(struct particle_effect *effect); +void particle_effect_calc_frame_range(struct particle_effect *effect, struct motion *motion); +void particle_effect_update(struct RE_instance *inst); +void particle_object_calc_local_transform(struct RE_instance *inst, struct particle_object *po, struct particle_instance *pi, float frame, mat4 dest); +float particle_object_calc_alpha(struct particle_object *po, struct particle_instance *pi, float frame); // parser.c diff --git a/src/3d/particle.c b/src/3d/particle.c index 69ff53a..7c3d625 100644 --- a/src/3d/particle.c +++ b/src/3d/particle.c @@ -14,12 +14,22 @@ * along with this program; if not, see . */ +#include +#include + #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'); @@ -69,7 +79,7 @@ static enum particle_frame_ref_type parse_particle_frame_ref_type(const char *s, return PARTICLE_FRAME_REF_EFFECT; else if (sscanf(s, "ターゲット[%d]", param) == 1) return PARTICLE_FRAME_REF_TARGET; - WARNING("unknown blend type \"%s\"", s); + WARNING("unknown frame ref type \"%s\"", s); return (enum particle_frame_ref_type)-1; } @@ -148,7 +158,7 @@ static int *parse_int_list(char **ptext, int *nr) return ints; } -static char **parse_qstr_list(char **ptext, int *nr) +static char **parse_qstr_list(char **ptext, int *nr, bool ignore_empty) { char *text = *ptext; int len; @@ -161,14 +171,17 @@ static char **parse_qstr_list(char **ptext, int *nr) cap *= 2; strs = xrealloc(strs, cap * sizeof(char *)); } - strs[*nr] = parse_quoted_string(&text); - if (!strs[*nr]) { + char *s = parse_quoted_string(&text); + if (!s) { for (int i = 0; i < *nr; i++) free(strs[i]); free(strs); return NULL; } - (*nr)++; + if (s[0] == '\0' && ignore_empty) + free(s); + else + strs[(*nr)++] = s; } while (MATCH(" , ")); *ptext = text; return strs; @@ -196,17 +209,17 @@ static struct particle_position *parse_position(char **ptext) } 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.n = n; - // unit->bone.name = utf2sjis(string_buf, 0); - unit->bone.pos[0] = x; - unit->bone.pos[1] = y; - unit->bone.pos[2] = -z; + 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.n = n; - unit->target.pos[0] = x; - unit->target.pos[1] = y; - unit->target.pos[2] = -z; + 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; @@ -261,6 +274,8 @@ static struct particle_effect *parse_pae(char *text) 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; @@ -281,6 +296,10 @@ static struct particle_effect *parse_pae(char *text) 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; @@ -324,7 +343,7 @@ static struct particle_effect *parse_pae(char *text) if (!current_object->size_y_types) goto err; } else if (MATCH("テクスチャ = ")) { - current_object->textures = parse_qstr_list(&text, ¤t_object->nr_textures); + current_object->textures = parse_qstr_list(&text, ¤t_object->nr_textures, true); if (!current_object->textures) goto err; } else if (MATCH("ブレンドタイプ = " KEYWORD, string_buf)) { @@ -359,38 +378,38 @@ static struct particle_effect *parse_pae(char *text) } else if (MATCH("アルファフェードアウト時間 = %d", &n)) { current_object->alpha_fadeout_time = n; } else if (MATCH("X軸自転角度 = %f , %f", &f, &f2)) { - current_object->rotation[0][0] = f; - current_object->rotation[1][0] = f2; + current_object->rotation.begin[0] = f; + current_object->rotation.end[0] = f2; } else if (MATCH("Y軸自転角度 = %f , %f", &f, &f2)) { - current_object->rotation[0][1] = f; - current_object->rotation[1][1] = f2; + current_object->rotation.begin[1] = f; + current_object->rotation.end[1] = f2; } else if (MATCH("Z軸自転角度 = %f , %f", &f, &f2)) { - current_object->rotation[0][2] = f; - current_object->rotation[1][2] = 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[0][0] = f; - current_object->revolution_angle[1][0] = 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[0][1] = f; - current_object->revolution_angle[1][1] = 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[0][2] = f; - current_object->revolution_angle[1][2] = 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[0][0] = f; - current_object->revolution_distance[1][0] = 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[0][1] = f; - current_object->revolution_distance[1][1] = 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[0][2] = f; - current_object->revolution_distance[1][2] = 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; + current_object->curve_length[2] = -f; } else if (MATCH("子フレーム = %d", &n)) { current_object->child_frame = n; } else if (MATCH("子距離 = %f", &f)) { @@ -404,8 +423,16 @@ static struct particle_effect *parse_pae(char *text) } 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, ¤t_object->nr_damages); @@ -426,6 +453,182 @@ static struct particle_effect *parse_pae(char *text) return NULL; } +static void load_textures(struct particle_effect *effect, struct archive *aar) +{ + effect->textures = ht_create(16); + for (int i = 0; i < effect->nr_objects; i++) { + struct particle_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 particle_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) +{ + po->instances = xcalloc(po->nr_particles, sizeof(struct particle_instance)); + + for (int i = 0; i < po->nr_particles; i++) { + struct particle_instance *pi = &po->instances[i]; + switch (po->move_type) { + case PARTICLE_MOVE_FIXED: + case PARTICLE_MOVE_LINEAR: + pi->begin_frame = po->begin_frame; + pi->end_frame = po->end_frame; + break; + case PARTICLE_MOVE_EMITTER: + pi->begin_frame = glm_lerp(po->child_create_begin_frame, po->child_create_end_frame, (float)i / po->nr_particles); + pi->end_frame = pi->begin_frame + po->child_frame; + pi->roll_angle = eval_child_slope(po); + pi->pitch_angle = randf() * (2.0f * GLM_PIf); + break; + } + eval_pos_for_instance(po->position[0], pi->random_offset.begin); + eval_pos_for_instance(po->position[1], pi->random_offset.end); + } +} + struct particle_effect *particle_effect_load(struct archive *aar, const char *path) { const char *basename = strrchr(path, '\\'); @@ -460,30 +663,283 @@ struct particle_effect *particle_effect_load(struct archive *aar, const char *pa archive_free_data(dfile); effect->path = strdup(path); + load_textures(effect, aar); + for (int i = 0; i < effect->nr_objects; i++) { + struct particle_object *po = &effect->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); + } + + 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 particle_effect_free(struct particle_effect *effect) { for (int i = 0; i < effect->nr_objects; i++) { - struct particle_object *obj = &effect->objects[i]; - free(obj->name); - free(obj->position[0]); - free(obj->position[1]); - free(obj->sizes2); - free(obj->sizes_x); - free(obj->sizes_y); - free(obj->size_types); - free(obj->size_x_types); - free(obj->size_y_types); - for (int j = 0; j < obj->nr_textures; j++) - free(obj->textures[j]); - free(obj->textures); - free(obj->polygon_name); - free(obj->damages); + struct particle_object *po = &effect->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); + free(po->instances); } + ht_foreach_value(effect->textures, free_billboard_texture); + ht_free(effect->textures); free(effect->objects); free(effect->path); free(effect); } + +void particle_effect_calc_frame_range(struct particle_effect *effect, struct motion *motion) +{ + int begin_frame = INT_MAX; + int end_frame = INT_MIN; + for (int i = 0; i < effect->nr_objects; i++) { + struct particle_object *po = &effect->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 = begin_frame; + motion->frame_end = end_frame; +} + +void particle_effect_update(struct RE_instance *inst) +{ + struct particle_effect *effect = inst->effect; + if (!effect) + return; + for (int i = 0; i < effect->nr_objects; i++) { + struct particle_object *po = &effect->objects[i]; + // These cannot be precomputed, because they may depend on the target's + // *current* bone matrix. + eval_pos_for_object(inst, po->position[0], po->pos.begin); + eval_pos_for_object(inst, po->position[1], po->pos.end); + glm_vec3_sub(po->pos.end, po->pos.begin, po->move_vec); + switch (po->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) +{ + 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, po->move_curve, t, dest); + if (move_vec) + glm_vec3_sub(end, begin, move_vec); + + // Orbital rotation. + vec3 angle, dist; + glm_vec3_lerp(po->revolution_angle.begin, po->revolution_angle.end, t, angle); + glm_vec3_lerp(po->revolution_distance.begin, po->revolution_distance.end, t, dist); + glm_vec3_scale(angle, GLM_PIf / 180.0f, angle); // deg -> rad + if (po->revolution_angle.begin[0] != 0.0f || po->revolution_angle.end[0] != 0.0f) { + dest[1] += dist[0] * sinf(angle[0]); + dest[2] -= dist[0] * cosf(angle[0]); + } + if (po->revolution_angle.begin[1] != 0.0f || po->revolution_angle.end[1] != 0.0f) { + dest[0] += dist[1] * sinf(angle[1]); + dest[2] -= dist[1] * cosf(angle[1]); + } + if (po->revolution_angle.begin[2] != 0.0f || po->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(po->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) +{ + switch (po->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 (po->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(po->begin_frame, po->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 (po->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 (po->child_move_dir_type) { + case PARTICLE_CHILD_MOVE_DIR_NORMAL: + glm_vec3_muladds(inst_move, po->child_length, inst_pos.end); + break; + case PARTICLE_CHILD_MOVE_DIR_REVERSE: + glm_vec3_muladds(inst_move, po->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 (po->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 (po->child_length != 0.0f) + glm_vec3_copy(inst_move, up_vector); + else + glm_vec3_copy(GLM_XUP, up_vector); + break; + } + if (po->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 particle_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) +{ + 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 (po->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(po->rotation.begin, po->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 (po->nr_sizes2 > 0) { + vec3 scale, scale2; + calc_scale(po, cur_step, scale); + calc_scale(po, next_step, scale2); + glm_vec3_lerp(scale, scale2, step_frac, scale); + glm_scale(dest, scale); + } else { + glm_scale_uni(dest, glm_lerp(po->size[0], po->size[1], t)); + } + + glm_translate_x(dest, -po->offset_x); + glm_translate_y(dest, -po->offset_y); +} + +float particle_object_calc_alpha(struct particle_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; +} diff --git a/src/3d/reign.c b/src/3d/reign.c index 30eae28..ba6aeb8 100644 --- a/src/3d/reign.c +++ b/src/3d/reign.c @@ -37,12 +37,16 @@ static struct RE_instance *create_instance(struct RE_plugin *plugin) { struct RE_instance *instance = xcalloc(1, sizeof(struct RE_instance)); instance->plugin = plugin; + for (int i = 0; i < RE_NR_INSTANCE_TARGETS; i++) + instance->target[i] = -1; instance->scale[0] = 1.0; instance->scale[1] = 1.0; instance->scale[2] = 1.0; instance->alpha = 1.0; instance->draw_bump = true; instance->fps = 30.0; + instance->column_height = 1.0; + instance->column_radius = 1.0; glm_mat4_identity(instance->local_transform); glm_mat3_identity(instance->normal_transform); return instance; @@ -106,6 +110,8 @@ static void update_motion(struct motion *m, float delta_frame) if (!m || m->state == RE_MOTION_STATE_STOP) return; m->current_frame += delta_frame; + if (m->current_frame < m->frame_begin) + m->current_frame = m->frame_begin; switch (m->state) { case RE_MOTION_STATE_NOLOOP: if (m->current_frame > m->frame_end) { @@ -131,13 +137,9 @@ static void calc_motion_frame(struct motion *m, int bone, struct mot_frame *out) interpolate_motion_frame(&frames[cur_frame], &frames[next_frame], t, out); } -static void animate(struct RE_instance *inst, int elapsed_ms) +static void update_bones(struct RE_instance *inst) { - float delta_frame = inst->fps * elapsed_ms / 1000.0; - update_motion(inst->motion, delta_frame); - update_motion(inst->next_motion, delta_frame); - - if (inst->type != RE_ITYPE_SKINNED || !inst->motion) + if (!inst->motion) return; mat4 parent_transforms[MAX_BONES]; @@ -235,8 +237,24 @@ bool RE_build_model(struct RE_plugin *plugin, int elapsed_ms) return false; for (int i = 0; i < plugin->nr_instances; i++) { - if (plugin->instances[i]) - animate(plugin->instances[i], elapsed_ms); + struct RE_instance *inst = plugin->instances[i]; + if (!inst) + continue; + + float delta_frame = inst->fps * elapsed_ms / 1000.0; + update_motion(inst->motion, delta_frame); + update_motion(inst->next_motion, delta_frame); + + if (inst->type == RE_ITYPE_SKINNED) + update_bones(inst); + } + + // Update particle effects in a second pass, because it uses the results of + // update_bones(). + for (int i = 0; i < plugin->nr_instances; i++) { + struct RE_instance *inst = plugin->instances[i]; + if (inst && inst->type == RE_ITYPE_PARTICLE_EFFECT) + particle_effect_update(inst); } return true; } @@ -329,6 +347,11 @@ bool RE_instance_load(struct RE_instance *instance, const char *name) return !!instance->model; case RE_ITYPE_PARTICLE_EFFECT: instance->effect = particle_effect_load(instance->plugin->aar, name); + if (!instance->motion) { + instance->motion = xcalloc(1, sizeof(struct motion)); + instance->motion->instance = instance; + particle_effect_calc_frame_range(instance->effect, instance->motion); + } return !!instance->effect; default: WARNING("Invalid instance type %d", instance->type); @@ -573,9 +596,9 @@ int RE_particle_get_pos_unit_index(struct particle_object *po, int pos, int unit return 0; switch (u->type) { case PARTICLE_POS_UNIT_BONE: - return u->bone.n; + return u->bone.target_index; case PARTICLE_POS_UNIT_TARGET: - return u->target.n; + return u->target.index; default: return 0; } @@ -739,72 +762,72 @@ void RE_particle_get_x_rotation_angle(struct particle_object *po, float *begin_a { if (!po) return; - *begin_angle = po->rotation[0][0]; - *end_angle = po->rotation[1][0]; + *begin_angle = po->rotation.begin[0]; + *end_angle = po->rotation.end[0]; } void RE_particle_get_y_rotation_angle(struct particle_object *po, float *begin_angle, float *end_angle) { if (!po) return; - *begin_angle = po->rotation[0][1]; - *end_angle = po->rotation[1][1]; + *begin_angle = po->rotation.begin[1]; + *end_angle = po->rotation.end[1]; } void RE_particle_get_z_rotation_angle(struct particle_object *po, float *begin_angle, float *end_angle) { if (!po) return; - *begin_angle = po->rotation[0][2]; - *end_angle = po->rotation[1][2]; + *begin_angle = po->rotation.begin[2]; + *end_angle = po->rotation.end[2]; } void RE_particle_get_x_revolution_angle(struct particle_object *po, float *begin_angle, float *end_angle) { if (!po) return; - *begin_angle = po->revolution_angle[0][0]; - *end_angle = po->revolution_angle[1][0]; + *begin_angle = po->revolution_angle.begin[0]; + *end_angle = po->revolution_angle.end[0]; } void RE_particle_get_x_revolution_distance(struct particle_object *po, float *begin_distance, float *end_distance) { if (!po) return; - *begin_distance = po->revolution_distance[0][0]; - *end_distance = po->revolution_distance[1][0]; + *begin_distance = po->revolution_distance.begin[0]; + *end_distance = po->revolution_distance.end[0]; } void RE_particle_get_y_revolution_angle(struct particle_object *po, float *begin_angle, float *end_angle) { if (!po) return; - *begin_angle = po->revolution_angle[0][1]; - *end_angle = po->revolution_angle[1][1]; + *begin_angle = po->revolution_angle.begin[1]; + *end_angle = po->revolution_angle.end[1]; } void RE_particle_get_y_revolution_distance(struct particle_object *po, float *begin_distance, float *end_distance) { if (!po) return; - *begin_distance = po->revolution_distance[0][1]; - *end_distance = po->revolution_distance[1][1]; + *begin_distance = po->revolution_distance.begin[1]; + *end_distance = po->revolution_distance.end[1]; } void RE_particle_get_z_revolution_angle(struct particle_object *po, float *begin_angle, float *end_angle) { if (!po) return; - *begin_angle = po->revolution_angle[0][2]; - *end_angle = po->revolution_angle[1][2]; + *begin_angle = po->revolution_angle.begin[2]; + *end_angle = po->revolution_angle.end[2]; } void RE_particle_get_z_revolution_distance(struct particle_object *po, float *begin_distance, float *end_distance) { if (!po) return; - *begin_distance = po->revolution_distance[0][2]; - *end_distance = po->revolution_distance[1][2]; + *begin_distance = po->revolution_distance.begin[2]; + *end_distance = po->revolution_distance.end[2]; } bool RE_particle_get_curve_length(struct particle_object *po, vec3 out) @@ -877,6 +900,15 @@ float RE_particle_get_offset_y(struct particle_object *po) return po ? po->offset_y : 0.0; } +bool RE_effect_get_frame_range(struct RE_instance *instance, int *begin_frame, int *end_frame) +{ + if (!instance || !instance->effect || !instance->motion) + return false; + *begin_frame = instance->motion->frame_begin; + *end_frame = instance->motion->frame_end; + return true; +} + bool RE_back_cg_set(struct RE_back_cg *bcg, int no) { if (!bcg) diff --git a/src/3d/renderer.c b/src/3d/renderer.c index 2d668a0..96f73f8 100644 --- a/src/3d/renderer.c +++ b/src/3d/renderer.c @@ -37,6 +37,12 @@ enum { SHADOW_TEXTURE_UNIT, }; +enum { + DIFFUSE_EMISSIVE = 0, + DIFFUSE_NORMAL = 1, + DIFFUSE_LIGHT_MAP = 2, +}; + static GLuint load_shader(const char *vertex_shader_path, const char *fragment_shader_path) { GLuint program = glCreateProgram(); @@ -108,10 +114,10 @@ static void init_billboard_mesh(struct RE_renderer *r) { static const GLfloat vertices[] = { // x, y, z, u, v - -1.0, 2.0, 0.0, 0.0, 0.0, - -1.0, 0.0, 0.0, 0.0, 1.0, - 1.0, 2.0, 0.0, 1.0, 0.0, - 1.0, 0.0, 0.0, 1.0, 1.0, + -1.0, 1.0, 0.0, 0.0, 0.0, + -1.0, -1.0, 0.0, 0.0, 1.0, + 1.0, 1.0, 0.0, 1.0, 0.0, + 1.0, -1.0, 0.0, 1.0, 1.0, }; glGenVertexArrays(1, &r->billboard_vao); glBindVertexArray(r->billboard_vao); @@ -177,7 +183,7 @@ struct RE_renderer *RE_renderer_new(struct texture *texture) r->rim_exponent = glGetUniformLocation(r->program, "rim_exponent"); r->rim_color = glGetUniformLocation(r->program, "rim_color"); r->camera_pos = glGetUniformLocation(r->program, "camera_pos"); - r->use_light_map = glGetUniformLocation(r->program, "use_light_map"); + r->diffuse_type = glGetUniformLocation(r->program, "diffuse_type"); r->light_texture = glGetUniformLocation(r->program, "light_texture"); r->use_normal_map = glGetUniformLocation(r->program, "use_normal_map"); r->normal_texture = glGetUniformLocation(r->program, "normal_texture"); @@ -224,6 +230,7 @@ bool RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); + bt->has_alpha = cg->metrics.has_alpha; cg_free(cg); ht_put_int(r->billboard_textures, cg_no, bt); @@ -248,7 +255,7 @@ void RE_renderer_free(struct RE_renderer *r) free(r); } -static void calc_view_matrix(struct RE_camera *camera, mat4 out) +void RE_calc_view_matrix(struct RE_camera *camera, vec3 up, mat4 out) { vec3 front = { 0.0, 0.0, -1.0 }; vec3 euler = { @@ -259,7 +266,6 @@ static void calc_view_matrix(struct RE_camera *camera, mat4 out) mat4 rot; glm_euler(euler, rot); glm_mat4_mulv3(rot, front, 0.0, front); - vec3 up = {0.0, 1.0, 0.0}; glm_look(camera->pos, front, up, out); } @@ -316,12 +322,12 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r) glUniform1i(r->texture, COLOR_TEXTURE_UNIT); if (material->light_map && inst->plugin->light_map_mode) { - glUniform1i(r->use_light_map, GL_TRUE); + glUniform1i(r->diffuse_type, DIFFUSE_LIGHT_MAP); glActiveTexture(GL_TEXTURE0 + LIGHT_TEXTURE_UNIT); glBindTexture(GL_TEXTURE_2D, material->light_map); glUniform1i(r->light_texture, LIGHT_TEXTURE_UNIT); } else { - glUniform1i(r->use_light_map, GL_FALSE); + glUniform1i(r->diffuse_type, DIFFUSE_NORMAL); } if (material->normal_map && inst->draw_bump && inst->plugin->bump_mode) { @@ -342,12 +348,16 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r) static void render_static_model(struct RE_instance *inst, struct RE_renderer *r) { + if (!inst->draw) + return; glUniform1i(r->has_bones, GL_FALSE); render_model(inst, r); } static void render_skinned_model(struct RE_instance *inst, struct RE_renderer *r) { + if (!inst->draw) + return; struct model *model = inst->model; if (!model) return; @@ -363,6 +373,8 @@ static void render_skinned_model(struct RE_instance *inst, struct RE_renderer *r static void render_billboard(struct RE_instance *inst, struct RE_renderer *r, mat4 view_mat) { + if (!inst->draw) + return; int cg_no = inst->motion->current_frame; struct billboard_texture *bt = ht_get_int(r->billboard_textures, cg_no, NULL); if (!bt) @@ -374,6 +386,8 @@ static void render_billboard(struct RE_instance *inst, struct RE_renderer *r, ma glm_mat4_pick3t(view_mat, rot); glm_mat4_ins3(rot, local_transform); glm_scale(local_transform, inst->scale); + // Billboard instances are bottomed at y=0. + glm_translate_y(local_transform, 1.0); mat3 normal_transform; // This should be safe because billboards do not have non-uniform scaling. glm_mat4_pick3(local_transform, normal_transform); @@ -390,8 +404,9 @@ static void render_billboard(struct RE_instance *inst, struct RE_renderer *r, ma glUniform1f(r->specular_shininess, 0.0); glUniform1i(r->use_specular_map, GL_FALSE); glUniform1f(r->rim_exponent, 0.0); - glUniform1i(r->use_light_map, GL_FALSE); + glUniform1i(r->diffuse_type, DIFFUSE_NORMAL); glUniform1i(r->use_normal_map, GL_FALSE); + glUniform1i(r->use_shadow_map, GL_FALSE); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, bt->texture); @@ -404,6 +419,131 @@ static void render_billboard(struct RE_instance *inst, struct RE_renderer *r, ma glBindTexture(GL_TEXTURE_2D, 0); } +static void render_billboard_particles(struct RE_renderer *r, struct RE_instance *inst, struct particle_object *po, float frame) +{ + if (po->nr_textures == 0) + return; + + glUniform1i(r->diffuse_type, DIFFUSE_EMISSIVE); + glUniform1i(r->fog_type, 0); + + glActiveTexture(GL_TEXTURE0); + glUniform1i(r->texture, 0); + glBindVertexArray(r->billboard_vao); + + for (int index = 0; index < po->nr_particles; index++) { + struct particle_instance *pi = &po->instances[index]; + if (frame < pi->begin_frame || pi->end_frame < frame) + continue; + + int step = (int)((frame - pi->begin_frame) / po->texture_anime_frame); + struct billboard_texture *bt = ht_get(inst->effect->textures, po->textures[step % po->nr_textures], NULL); + if (!bt) + continue; + glBindTexture(GL_TEXTURE_2D, bt->texture); + + float alpha = particle_object_calc_alpha(po, pi, frame); + if (po->blend_type == PARTICLE_BLEND_ADDITIVE && !bt->has_alpha) + alpha *= alpha; // why... + glUniform1f(r->alpha_mod, alpha); + + mat4 local_transform; + particle_object_calc_local_transform(inst, po, pi, frame, local_transform); + glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, local_transform[0]); + glUniformMatrix3fv(r->normal_transform, 1, GL_FALSE, local_transform[0]); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + glBindVertexArray(0); + glBindTexture(GL_TEXTURE_2D, 0); + + glUniform1i(r->fog_type, inst->plugin->fog_type); +} + +static void render_polygon_particles(struct RE_renderer *r, struct RE_instance *inst, struct particle_object *po, float frame) +{ + struct model *model = po->model; + if (!model) + return; + + glUniform1i(r->diffuse_type, DIFFUSE_NORMAL); + + for (int index = 0; index < po->nr_particles; index++) { + struct particle_instance *pi = &po->instances[index]; + + float alpha = particle_object_calc_alpha(po, pi, frame); + glUniform1f(r->alpha_mod, alpha); + + mat4 local_transform; + particle_object_calc_local_transform(inst, po, pi, frame, local_transform); + glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, local_transform[0]); + glUniformMatrix3fv(r->normal_transform, 1, GL_FALSE, local_transform[0]); + + for (int i = 0; i < model->nr_meshes; i++) { + struct mesh *mesh = &model->meshes[i]; + struct material *material = &model->materials[mesh->material]; + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, material->color_map); + glUniform1i(r->texture, 0); + + glBindVertexArray(mesh->vao); + glDrawArrays(GL_TRIANGLES, 0, mesh->nr_vertices); + + glBindVertexArray(0); + glBindTexture(GL_TEXTURE_2D, 0); + } + } +} + +static void render_particle_effect(struct RE_instance *inst, struct RE_renderer *r) +{ + // NOTE: inst->draw flag has no effect for particle effects. + if (!inst->effect) + return; + + vec3 ambient; + glm_vec3_add(inst->plugin->global_ambient, inst->ambient, ambient); + glUniform3fv(r->ambient, 1, ambient); + + glUniform1i(r->has_bones, GL_FALSE); + glUniform1f(r->specular_strength, 0.0); + glUniform1f(r->specular_shininess, 0.0); + glUniform1i(r->use_specular_map, GL_FALSE); + glUniform1f(r->rim_exponent, 0.0); + glUniform1i(r->use_normal_map, GL_FALSE); + glUniform1i(r->use_shadow_map, GL_FALSE); + + glDepthMask(GL_FALSE); + + for (int i = 0; i < inst->effect->nr_objects; i++) { + struct particle_object *po = &inst->effect->objects[i]; + + switch (po->blend_type) { + case PARTICLE_BLEND_NORMAL: + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + break; + case PARTICLE_BLEND_ADDITIVE: + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE); + break; + } + + switch (po->type) { + case PARTICLE_TYPE_BILLBOARD: + render_billboard_particles(r, inst, po, inst->motion->current_frame); + break; + case PARTICLE_TYPE_POLYGON_OBJECT: + render_polygon_particles(r, inst, po, inst->motion->current_frame); + break; + case PARTICLE_TYPE_SWORD_BLUR: // unused + break; + } + } + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(GL_TRUE); +} + static bool calc_shadow_light_transform(struct RE_plugin *plugin, mat4 dest) { // Compute AABB of shadow casters. @@ -579,7 +719,7 @@ void RE_render(struct sact_sprite *sp) glEnable(GL_CULL_FACE); mat4 view_transform; - calc_view_matrix(&plugin->camera, view_transform); + RE_calc_view_matrix(&plugin->camera, GLM_YUP, view_transform); // Tweak the projection transform so that the rendering result is vertically // flipped. If we render the scene normally, the resulting image will be @@ -621,7 +761,7 @@ void RE_render(struct sact_sprite *sp) for (int i = 0; i < plugin->nr_instances; i++) { struct RE_instance *inst = plugin->instances[i]; - if (!inst || !inst->draw) + if (!inst) continue; switch (inst->type) { case RE_ITYPE_STATIC: @@ -633,6 +773,9 @@ void RE_render(struct sact_sprite *sp) case RE_ITYPE_BILLBOARD: render_billboard(inst, r, view_transform); break; + case RE_ITYPE_PARTICLE_EFFECT: + render_particle_effect(inst, r); + break; default: break; } diff --git a/src/hll/ReignEngine.c b/src/hll/ReignEngine.c index 9954bed..4bf8a93 100644 --- a/src/hll/ReignEngine.c +++ b/src/hll/ReignEngine.c @@ -482,22 +482,97 @@ static bool ReignEngine_FreeInstanceNextMotion(int plugin, int instance) //bool ReignEngine_SaveInstanceAddMaterialData(int plugin, int instance); //bool ReignEngine_SetInstancePointPos(int plugin, int instance, int index, float x, float y, float z); //bool ReignEngine_GetInstancePointPos(int plugin, int instance, int index, float *pfX, float *pfY, float *pfZ); -//bool ReignEngine_GetInstanceColumnPos(int plugin, int instance, float *fX, float *fY, float *fZ); -//float ReignEngine_GetInstanceColumnHeight(int plugin, int instance); -//float ReignEngine_GetInstanceColumnRadius(int plugin, int instance); -//float ReignEngine_GetInstanceColumnAngle(int plugin, int instance); +static bool ReignEngine_GetInstanceColumnPos(int plugin, int instance, float *x, float *y, float *z) +{ + struct RE_instance *ri = get_instance(plugin, instance); + if (!ri) + return false; + *x = ri->column_pos[0]; + *y = ri->column_pos[1]; + *z = -ri->column_pos[2]; + return true; +} + +static float ReignEngine_GetInstanceColumnHeight(int plugin, int instance) +{ + struct RE_instance *ri = get_instance(plugin, instance); + return ri ? ri->column_height : 0.0; +} + +static float ReignEngine_GetInstanceColumnRadius(int plugin, int instance) +{ + struct RE_instance *ri = get_instance(plugin, instance); + return ri ? ri->column_radius : 0.0; +} + +static float ReignEngine_GetInstanceColumnAngle(int plugin, int instance) +{ + struct RE_instance *ri = get_instance(plugin, instance); + return ri ? ri->column_angle : 0.0; +} + //float ReignEngine_GetInstanceColumnAngleP(int plugin, int instance); //float ReignEngine_GetInstanceColumnAngleB(int plugin, int instance); -HLL_WARN_UNIMPLEMENTED(false, bool, ReignEngine, SetInstanceColumnPos, int plugin, int instance, float x, float y, float z); -HLL_WARN_UNIMPLEMENTED(false, bool, ReignEngine, SetInstanceColumnHeight, int plugin, int instance, float height); -HLL_WARN_UNIMPLEMENTED(false, bool, ReignEngine, SetInstanceColumnRadius, int plugin, int instance, float radius); -HLL_WARN_UNIMPLEMENTED(false, bool, ReignEngine, SetInstanceColumnAngle, int plugin, int instance, float angle); + +static bool ReignEngine_SetInstanceColumnPos(int plugin, int instance, float x, float y, float z) +{ + struct RE_instance *ri = get_instance(plugin, instance); + if (!ri) + return false; + ri->column_pos[0] = x; + ri->column_pos[1] = y; + ri->column_pos[2] = -z; + return true; +} + +static bool ReignEngine_SetInstanceColumnHeight(int plugin, int instance, float height) +{ + struct RE_instance *ri = get_instance(plugin, instance); + if (!ri) + return false; + ri->column_height = height; + return true; +} + +static bool ReignEngine_SetInstanceColumnRadius(int plugin, int instance, float radius) +{ + struct RE_instance *ri = get_instance(plugin, instance); + if (!ri) + return false; + ri->column_radius = radius; + return true; +} + +static bool ReignEngine_SetInstanceColumnAngle(int plugin, int instance, float angle) +{ + struct RE_instance *ri = get_instance(plugin, instance); + if (!ri) + return false; + ri->column_angle = angle; + return true; +} + //bool ReignEngine_SetInstanceColumnAngleP(int plugin, int instance, float fAngleP); //bool ReignEngine_SetInstanceColumnAngleB(int plugin, int instance, float fAngleB); //bool ReignEngine_GetInstanceDrawColumn(int plugin, int instance); //bool ReignEngine_SetInstanceDrawColumn(int plugin, int instance, bool bDraw); -HLL_WARN_UNIMPLEMENTED(false, bool, ReignEngine, SetInstanceTarget, int plugin, int instance, int index, int target); -//int ReignEngine_GetInstanceTarget(int plugin, int instance, int index); + +static bool ReignEngine_SetInstanceTarget(int plugin, int instance, int index, int target) +{ + struct RE_instance *ri = get_instance(plugin, instance); + if (!ri || index < 0 || index >= RE_NR_INSTANCE_TARGETS) + return false; + ri->target[index] = target; + return true; +} + +static int ReignEngine_GetInstanceTarget(int plugin, int instance, int index) +{ + struct RE_instance *ri = get_instance(plugin, instance); + if (!ri || index < 0 || index >= RE_NR_INSTANCE_TARGETS) + return -1; + return ri->target[index]; +} static float ReignEngine_GetInstanceFPS(int plugin, int instance) { @@ -561,7 +636,11 @@ static bool ReignEngine_SetInstanceShadowVolumeBoneRadius(int plugin, int instan //bool ReignEngine_GetInstanceDebugDrawShadowVolume(int plugin, int instance); //bool ReignEngine_SetInstanceDebugDrawShadowVolume(int plugin, int instance, bool flag); //bool ReignEngine_SaveEffect(int plugin, int instance); -HLL_WARN_UNIMPLEMENTED(false, bool, ReignEngine, GetEffectFrameRange, int plugin, int instance, int *begin_frame, int *end_frame); + +static bool ReignEngine_GetEffectFrameRange(int plugin, int instance, int *begin_frame, int *end_frame) +{ + return RE_effect_get_frame_range(get_instance(plugin, instance), begin_frame, end_frame); +} static int ReignEngine_GetEffectObjectType(int plugin, int instance, int object) { @@ -1765,10 +1844,10 @@ HLL_LIBRARY(ReignEngine, HLL_TODO_EXPORT(SaveInstanceAddMaterialData, ReignEngine_SaveInstanceAddMaterialData), HLL_TODO_EXPORT(SetInstancePointPos, ReignEngine_SetInstancePointPos), HLL_TODO_EXPORT(GetInstancePointPos, ReignEngine_GetInstancePointPos), - HLL_TODO_EXPORT(GetInstanceColumnPos, ReignEngine_GetInstanceColumnPos), - HLL_TODO_EXPORT(GetInstanceColumnHeight, ReignEngine_GetInstanceColumnHeight), - HLL_TODO_EXPORT(GetInstanceColumnRadius, ReignEngine_GetInstanceColumnRadius), - HLL_TODO_EXPORT(GetInstanceColumnAngle, ReignEngine_GetInstanceColumnAngle), + HLL_EXPORT(GetInstanceColumnPos, ReignEngine_GetInstanceColumnPos), + HLL_EXPORT(GetInstanceColumnHeight, ReignEngine_GetInstanceColumnHeight), + HLL_EXPORT(GetInstanceColumnRadius, ReignEngine_GetInstanceColumnRadius), + HLL_EXPORT(GetInstanceColumnAngle, ReignEngine_GetInstanceColumnAngle), HLL_TODO_EXPORT(GetInstanceColumnAngleP, ReignEngine_GetInstanceColumnAngleP), HLL_TODO_EXPORT(GetInstanceColumnAngleB, ReignEngine_GetInstanceColumnAngleB), HLL_EXPORT(SetInstanceColumnPos, ReignEngine_SetInstanceColumnPos), @@ -1780,7 +1859,7 @@ HLL_LIBRARY(ReignEngine, HLL_TODO_EXPORT(GetInstanceDrawColumn, ReignEngine_GetInstanceDrawColumn), HLL_TODO_EXPORT(SetInstanceDrawColumn, ReignEngine_SetInstanceDrawColumn), HLL_EXPORT(SetInstanceTarget, ReignEngine_SetInstanceTarget), - HLL_TODO_EXPORT(GetInstanceTarget, ReignEngine_GetInstanceTarget), + HLL_EXPORT(GetInstanceTarget, ReignEngine_GetInstanceTarget), HLL_EXPORT(GetInstanceFPS, ReignEngine_GetInstanceFPS), HLL_EXPORT(SetInstanceFPS, ReignEngine_SetInstanceFPS), HLL_EXPORT(GetInstanceBoneIndex, ReignEngine_GetInstanceBoneIndex), From 97c65a33db5b18b499b8d72774a6b4e05b98353e Mon Sep 17 00:00:00 2001 From: kichikuou Date: Mon, 26 Sep 2022 15:46:12 +0900 Subject: [PATCH 2/2] Add frame bounds check in render_polygon_particles() --- src/3d/renderer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/3d/renderer.c b/src/3d/renderer.c index 96f73f8..88885d3 100644 --- a/src/3d/renderer.c +++ b/src/3d/renderer.c @@ -471,6 +471,8 @@ static void render_polygon_particles(struct RE_renderer *r, struct RE_instance * for (int index = 0; index < po->nr_particles; index++) { struct particle_instance *pi = &po->instances[index]; + if (frame < pi->begin_frame || pi->end_frame < frame) + continue; float alpha = particle_object_calc_alpha(po, pi, frame); glUniform1f(r->alpha_mod, alpha);