mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-10-07 14:28:12 +03:00
SealEngine: Add .3de parser, effect framework, camera, and sound
.3de is the particle/effect format used by SealEngine. Like the ReignEngine's .pae, it is a text format describing an effect as a set of emitters, but the two differ enough in design that .3de lives in its own file (s3de.c) rather than being folded into particle.c. This commit lays the groundwork: - Parse .3de text. - Integrate effect loading, playback, and caching into the existing infrastructure. - Apply the camera object's per-frame override to RE_camera. - Play the companion sound asset. Billboard and polygon-object rendering will be added separately.
This commit is contained in:
@@ -84,6 +84,7 @@ target_sources(xsystem4 PRIVATE
|
||||
src/3d/particle.c
|
||||
src/3d/reign.c
|
||||
src/3d/renderer.c
|
||||
src/3d/s3de.c
|
||||
|
||||
src/dungeon/dgn.c
|
||||
src/dungeon/dtx.c
|
||||
|
||||
+8
-1
@@ -84,6 +84,12 @@ struct RE_camera {
|
||||
vec3 pos;
|
||||
float pitch, roll, yaw; // in degrees
|
||||
float quake_pitch, quake_yaw;
|
||||
// Per-frame full override from an 3de "camera" object. Cleared at the
|
||||
// start of every RE_build_model tick and re-asserted by s3de_effect_update
|
||||
// if any active camera object selects this frame.
|
||||
bool override_active;
|
||||
vec3 override_pos;
|
||||
float override_pitch, override_yaw, override_roll;
|
||||
};
|
||||
|
||||
struct RE_back_cg {
|
||||
@@ -104,7 +110,7 @@ struct RE_plugin {
|
||||
struct RE_instance **instances;
|
||||
struct archive *aar;
|
||||
struct hash_table *model_cache;
|
||||
struct hash_table *pae_cache;
|
||||
struct hash_table *effect_cache;
|
||||
struct RE_renderer *renderer;
|
||||
struct RE_camera camera;
|
||||
mat4 proj_transform;
|
||||
@@ -158,6 +164,7 @@ struct RE_instance {
|
||||
struct RE_plugin *plugin;
|
||||
struct model *model;
|
||||
struct particle_effect *effect;
|
||||
struct s3de_effect *s3de_effect;
|
||||
struct motion *motion;
|
||||
struct motion *next_motion;
|
||||
struct height_detector *height_detector;
|
||||
|
||||
@@ -402,6 +402,178 @@ void particle_effect_free(struct particle_effect *effect);
|
||||
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);
|
||||
|
||||
// s3de.c
|
||||
|
||||
// .3de effect system. The 's' prefix is just to avoid an identifier starting
|
||||
// with a digit; it stands for SealEngine.
|
||||
|
||||
enum s3de_object_type {
|
||||
S3DE_OBJ_BILLBOARD,
|
||||
S3DE_OBJ_POLYGON,
|
||||
S3DE_OBJ_CAMERA,
|
||||
};
|
||||
|
||||
enum s3de_move_type {
|
||||
S3DE_MOVE_LINEAR,
|
||||
S3DE_MOVE_BEZIER,
|
||||
};
|
||||
|
||||
enum s3de_spawn_position_type {
|
||||
S3DE_SPAWN_NORMAL,
|
||||
S3DE_SPAWN_SPHERE,
|
||||
S3DE_SPAWN_HORIZONTAL,
|
||||
};
|
||||
|
||||
enum s3de_blend_type {
|
||||
S3DE_BLEND_NORMAL,
|
||||
S3DE_BLEND_ADDITIVE,
|
||||
};
|
||||
|
||||
enum s3de_emitter_link_type {
|
||||
S3DE_LINK_NONE = 0, // frozen at spawn-time world pos
|
||||
S3DE_LINK_FOLLOW = 1, // particle follows current emitter pos
|
||||
S3DE_LINK_ROTATE_AT_SPAWN = 2, // emitter rotation around spawn pos
|
||||
S3DE_LINK_ROTATE_AT_CURRENT = 3, // emitter rotation around current pos
|
||||
S3DE_LINK_MAX = S3DE_LINK_ROTATE_AT_CURRENT
|
||||
};
|
||||
|
||||
enum s3de_direction_type {
|
||||
S3DE_DIR_RANDOM,
|
||||
S3DE_DIR_SPECIFIED,
|
||||
S3DE_DIR_EMITTER,
|
||||
S3DE_DIR_EMITTER_REVERSE,
|
||||
S3DE_DIR_EMITTER_COORD,
|
||||
S3DE_DIR_ARBITRARY_PLANE,
|
||||
S3DE_DIR_SPAWN,
|
||||
};
|
||||
|
||||
enum s3de_posture_type {
|
||||
S3DE_POSE_NONE,
|
||||
S3DE_POSE_CAMERA,
|
||||
S3DE_POSE_MOVE_DIR,
|
||||
S3DE_POSE_MOVE_AND_FLY,
|
||||
S3DE_POSE_FLY_AND_EMITTER_MOVE,
|
||||
};
|
||||
|
||||
enum s3de_interp_type {
|
||||
S3DE_INTERP_NONE,
|
||||
S3DE_INTERP_LINEAR,
|
||||
S3DE_INTERP_SLERP,
|
||||
};
|
||||
|
||||
struct s3de_range {
|
||||
float base;
|
||||
float random;
|
||||
};
|
||||
|
||||
struct s3de_scalar_entry {
|
||||
int frame;
|
||||
float value;
|
||||
enum s3de_interp_type interp;
|
||||
};
|
||||
|
||||
struct s3de_vec3_entry {
|
||||
int frame;
|
||||
vec3 v;
|
||||
enum s3de_interp_type interp;
|
||||
};
|
||||
|
||||
struct s3de_position_entry {
|
||||
int frame;
|
||||
vec3 pos;
|
||||
enum s3de_interp_type interp;
|
||||
float spawn_radius;
|
||||
float spawn_angle_deg;
|
||||
};
|
||||
|
||||
struct s3de_object {
|
||||
char *name;
|
||||
enum s3de_object_type type;
|
||||
enum s3de_move_type movement_type;
|
||||
enum s3de_spawn_position_type spawn_position_type;
|
||||
float spawn_distance;
|
||||
int particle_count;
|
||||
|
||||
char *texture;
|
||||
|
||||
enum s3de_blend_type blend_type;
|
||||
char *polygon_name;
|
||||
enum s3de_emitter_link_type emitter_link_type;
|
||||
bool soft_fog_edge;
|
||||
|
||||
enum s3de_posture_type posture_type;
|
||||
enum s3de_direction_type direction_type;
|
||||
vec3 direction;
|
||||
float direction_angle;
|
||||
|
||||
struct s3de_range start_size, end_size;
|
||||
struct s3de_range start_x_size, end_x_size;
|
||||
struct s3de_range start_y_size, end_y_size;
|
||||
struct s3de_range offset_x, offset_y, offset_z;
|
||||
struct s3de_range speed, acceleration;
|
||||
struct s3de_range movement_distance, movement_curve;
|
||||
|
||||
bool free_fall;
|
||||
float mass, air_resistance;
|
||||
|
||||
struct s3de_range start_x_rotation, end_x_rotation;
|
||||
struct s3de_range start_y_rotation, end_y_rotation;
|
||||
struct s3de_range start_z_rotation, end_z_rotation;
|
||||
|
||||
float x_revolution_angle[2], y_revolution_angle[2], z_revolution_angle[2];
|
||||
float x_revolution_distance[2], y_revolution_distance[2], z_revolution_distance[2];
|
||||
|
||||
int child_frame;
|
||||
float texture_anime_frame;
|
||||
|
||||
struct s3de_range alpha_fade_in_frame, alpha_fade_out_frame;
|
||||
|
||||
int *damages;
|
||||
int nr_damages;
|
||||
|
||||
struct s3de_position_entry *position_list;
|
||||
int nr_position_entries;
|
||||
struct s3de_scalar_entry *size_list;
|
||||
int nr_size_entries;
|
||||
struct s3de_scalar_entry *x_size_list;
|
||||
int nr_x_size_entries;
|
||||
struct s3de_scalar_entry *y_size_list;
|
||||
int nr_y_size_entries;
|
||||
struct s3de_scalar_entry *alpha_list;
|
||||
int nr_alpha_entries;
|
||||
struct s3de_vec3_entry *rotation_list;
|
||||
int nr_rotation_entries;
|
||||
struct s3de_vec3_entry *multiply_color_list;
|
||||
int nr_multiply_color_entries;
|
||||
struct s3de_vec3_entry *additive_color_list;
|
||||
int nr_additive_color_entries;
|
||||
|
||||
struct model *model;
|
||||
};
|
||||
|
||||
struct s3de {
|
||||
char *path;
|
||||
int loop_start_frame;
|
||||
int loop_end_frame;
|
||||
struct s3de_object *objects;
|
||||
int nr_objects;
|
||||
};
|
||||
|
||||
struct s3de *s3de_load(struct archive *aar, const char *path);
|
||||
void s3de_free(struct s3de *s);
|
||||
|
||||
struct s3de_effect {
|
||||
struct s3de *s3de;
|
||||
int wav_channel; // -1 if no sound
|
||||
bool sound_started;
|
||||
float last_frame;
|
||||
};
|
||||
|
||||
struct s3de_effect *s3de_effect_create(struct s3de *s, struct archive *aar);
|
||||
void s3de_effect_free(struct s3de_effect *eff);
|
||||
void s3de_effect_update(struct RE_instance *inst);
|
||||
void s3de_calc_frame_range(struct s3de *s, struct motion *motion);
|
||||
|
||||
// parser.c
|
||||
|
||||
struct pol {
|
||||
|
||||
+55
-25
@@ -77,6 +77,10 @@ static void unload_instance(struct RE_instance *instance)
|
||||
particle_effect_free(instance->effect);
|
||||
instance->effect = NULL;
|
||||
}
|
||||
if (instance->s3de_effect) {
|
||||
s3de_effect_free(instance->s3de_effect);
|
||||
instance->s3de_effect = NULL;
|
||||
}
|
||||
if (instance->bone_transforms) {
|
||||
glDeleteBuffers(1, &instance->bone_transforms_ubo);
|
||||
free(instance->bone_transforms);
|
||||
@@ -238,7 +242,7 @@ struct RE_plugin *RE_plugin_new(enum RE_plugin_version version)
|
||||
plugin->instances = xcalloc(plugin->nr_instances, sizeof(struct RE_instance *));
|
||||
plugin->aar = aar;
|
||||
plugin->model_cache = ht_create(32);
|
||||
plugin->pae_cache = ht_create(32);
|
||||
plugin->effect_cache = ht_create(32);
|
||||
for (int i = 0; i < RE_NR_BACK_CGS; i++)
|
||||
RE_back_cg_init(&plugin->back_cg[i]);
|
||||
plugin->mag_speed = 1;
|
||||
@@ -260,8 +264,9 @@ void RE_plugin_free(struct RE_plugin *plugin)
|
||||
archive_free(plugin->aar);
|
||||
ht_foreach_value(plugin->model_cache, (void(*)(void*))model_free);
|
||||
ht_free(plugin->model_cache);
|
||||
ht_foreach_value(plugin->pae_cache, (void(*)(void*))pae_free);
|
||||
ht_free(plugin->pae_cache);
|
||||
ht_foreach_value(plugin->effect_cache, re_plugin_version < RE_SEAL_PLUGIN ?
|
||||
(void(*)(void*))pae_free : (void(*)(void*))s3de_free);
|
||||
ht_free(plugin->effect_cache);
|
||||
for (int i = 0; i < RE_NR_BACK_CGS; i++)
|
||||
RE_back_cg_destroy(&plugin->back_cg[i]);
|
||||
xfree_aligned(plugin);
|
||||
@@ -321,6 +326,7 @@ bool RE_build_model(struct RE_plugin *plugin, int elapsed_ms)
|
||||
return false;
|
||||
|
||||
plugin->camera.quake_pitch = plugin->camera.quake_yaw = 0.0;
|
||||
plugin->camera.override_active = false;
|
||||
|
||||
for (int i = 0; i < plugin->nr_instances; i++) {
|
||||
struct RE_instance *inst = plugin->instances[i];
|
||||
@@ -342,7 +348,11 @@ bool RE_build_model(struct RE_plugin *plugin, int elapsed_ms)
|
||||
// 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)
|
||||
if (!inst || inst->type != RE_ITYPE_PARTICLE_EFFECT)
|
||||
continue;
|
||||
if (inst->s3de_effect)
|
||||
s3de_effect_update(inst);
|
||||
else
|
||||
particle_effect_update(inst);
|
||||
}
|
||||
return true;
|
||||
@@ -469,7 +479,6 @@ bool RE_instance_load(struct RE_instance *instance, const char *name)
|
||||
if (name[0] == '\0')
|
||||
return true; // empty name just unloads the instance
|
||||
|
||||
struct pae *pae;
|
||||
switch (instance->type) {
|
||||
case RE_ITYPE_STATIC:
|
||||
case RE_ITYPE_SKINNED:
|
||||
@@ -488,18 +497,34 @@ bool RE_instance_load(struct RE_instance *instance, const char *name)
|
||||
}
|
||||
return !!instance->model;
|
||||
case RE_ITYPE_PARTICLE_EFFECT:
|
||||
pae = ht_get(instance->plugin->pae_cache, name, NULL);
|
||||
if (!pae) {
|
||||
pae = pae_load(instance->plugin->aar, name);
|
||||
if (!pae)
|
||||
return false;
|
||||
ht_put(instance->plugin->pae_cache, name, pae);
|
||||
}
|
||||
instance->effect = particle_effect_create(pae);
|
||||
if (!instance->motion) {
|
||||
instance->motion = xcalloc(1, sizeof(struct motion));
|
||||
instance->motion->instance = instance;
|
||||
pae_calc_frame_range(instance->effect->pae, instance->motion);
|
||||
if (re_plugin_version >= RE_SEAL_PLUGIN) {
|
||||
struct s3de *s3de = ht_get(instance->plugin->effect_cache, name, NULL);
|
||||
if (!s3de) {
|
||||
s3de = s3de_load(instance->plugin->aar, name);
|
||||
if (!s3de)
|
||||
return false;
|
||||
ht_put(instance->plugin->effect_cache, name, s3de);
|
||||
}
|
||||
instance->s3de_effect = s3de_effect_create(s3de, instance->plugin->aar);
|
||||
if (!instance->motion) {
|
||||
instance->motion = xcalloc(1, sizeof(struct motion));
|
||||
instance->motion->instance = instance;
|
||||
s3de_calc_frame_range(instance->s3de_effect->s3de, instance->motion);
|
||||
}
|
||||
} else {
|
||||
struct pae *pae = ht_get(instance->plugin->effect_cache, name, NULL);
|
||||
if (!pae) {
|
||||
pae = pae_load(instance->plugin->aar, name);
|
||||
if (!pae)
|
||||
return false;
|
||||
ht_put(instance->plugin->effect_cache, name, pae);
|
||||
}
|
||||
instance->effect = particle_effect_create(pae);
|
||||
if (!instance->motion) {
|
||||
instance->motion = xcalloc(1, sizeof(struct motion));
|
||||
instance->motion->instance = instance;
|
||||
pae_calc_frame_range(instance->effect->pae, instance->motion);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
@@ -670,7 +695,7 @@ bool RE_instance_find_path(struct RE_instance *inst, vec3 start, vec3 goal)
|
||||
if (!inst || !inst->model->collider)
|
||||
return false;
|
||||
mat4 vp_transform;
|
||||
RE_calc_view_matrix(&inst->plugin->camera, GLM_YUP, vp_transform);
|
||||
RE_calc_view_matrix(&inst->plugin->camera, NULL, vp_transform);
|
||||
glm_mat4_mul(inst->plugin->proj_transform, vp_transform, vp_transform);
|
||||
return collider_find_path(inst->model->collider, start, goal, vp_transform);
|
||||
}
|
||||
@@ -701,7 +726,7 @@ bool RE_instance_calc_path_finder_intersect_eye_vec(struct RE_instance *inst, in
|
||||
vec4 far = { ndc_x, ndc_y, 1.f, 1.f };
|
||||
|
||||
mat4 inv_vp;
|
||||
RE_calc_view_matrix(&inst->plugin->camera, GLM_YUP, inv_vp);
|
||||
RE_calc_view_matrix(&inst->plugin->camera, NULL, inv_vp);
|
||||
glm_mat4_mul(inst->plugin->proj_transform, inv_vp, inv_vp);
|
||||
glm_mat4_inv(inv_vp, inv_vp);
|
||||
|
||||
@@ -724,7 +749,7 @@ bool RE_plugin_transform_pos_to_view_pos(struct RE_plugin *plugin, float x, floa
|
||||
return false;
|
||||
|
||||
mat4 vp;
|
||||
RE_calc_view_matrix(&plugin->camera, GLM_YUP, vp);
|
||||
RE_calc_view_matrix(&plugin->camera, NULL, vp);
|
||||
glm_mat4_mul(plugin->proj_transform, vp, vp);
|
||||
|
||||
vec4 pos = { x, y, z, 1.f };
|
||||
@@ -746,11 +771,16 @@ bool RE_plugin_get_camera_z_vector(struct RE_plugin *plugin, vec3 out)
|
||||
if (!plugin)
|
||||
return false;
|
||||
|
||||
vec3 euler = {
|
||||
glm_rad(plugin->camera.pitch + plugin->camera.quake_pitch),
|
||||
glm_rad(plugin->camera.yaw + plugin->camera.quake_yaw),
|
||||
glm_rad(plugin->camera.roll)
|
||||
};
|
||||
vec3 euler;
|
||||
if (plugin->camera.override_active) {
|
||||
euler[0] = glm_rad(plugin->camera.override_pitch);
|
||||
euler[1] = glm_rad(plugin->camera.override_yaw);
|
||||
euler[2] = glm_rad(plugin->camera.override_roll);
|
||||
} else {
|
||||
euler[0] = glm_rad(plugin->camera.pitch + plugin->camera.quake_pitch);
|
||||
euler[1] = glm_rad(plugin->camera.yaw + plugin->camera.quake_yaw);
|
||||
euler[2] = glm_rad(plugin->camera.roll);
|
||||
}
|
||||
mat4 rot;
|
||||
glm_euler_yxz(euler, rot);
|
||||
glm_mat4_mulv3(rot, GLM_FORWARD, 0.f, out);
|
||||
|
||||
+24
-8
@@ -332,15 +332,30 @@ void RE_renderer_free(struct RE_renderer *r)
|
||||
void RE_calc_view_matrix(struct RE_camera *camera, vec3 up, mat4 out)
|
||||
{
|
||||
vec3 front = { 0.0, 0.0, -1.0 };
|
||||
vec3 euler = {
|
||||
glm_rad(camera->pitch + camera->quake_pitch),
|
||||
glm_rad(camera->yaw + camera->quake_yaw),
|
||||
glm_rad(camera->roll)
|
||||
};
|
||||
vec3 euler;
|
||||
float *pos;
|
||||
if (camera->override_active) {
|
||||
euler[0] = glm_rad(camera->override_pitch);
|
||||
euler[1] = glm_rad(camera->override_yaw);
|
||||
euler[2] = glm_rad(camera->override_roll);
|
||||
pos = camera->override_pos;
|
||||
} else {
|
||||
euler[0] = glm_rad(camera->pitch + camera->quake_pitch);
|
||||
euler[1] = glm_rad(camera->yaw + camera->quake_yaw);
|
||||
euler[2] = glm_rad(camera->roll);
|
||||
pos = camera->pos;
|
||||
}
|
||||
mat4 rot;
|
||||
glm_euler_yxz(euler, rot);
|
||||
glm_mat4_mulv3(rot, front, 0.0, front);
|
||||
glm_look(camera->pos, front, up, out);
|
||||
vec3 up_v;
|
||||
if (up) {
|
||||
glm_vec3_copy(up, up_v); // Use the provided up vector.
|
||||
} else {
|
||||
// Derive the camera's up by rotating world-Y with the same orientation.
|
||||
glm_mat4_mulv3(rot, GLM_YUP, 0.0, up_v);
|
||||
}
|
||||
glm_look(pos, front, up_v, out);
|
||||
}
|
||||
|
||||
static bool should_draw_shadow(struct mesh *mesh, struct material *material)
|
||||
@@ -892,7 +907,8 @@ static void render_back_cg(struct texture *dst, struct RE_back_cg *bcg, struct R
|
||||
static void setup_lights(struct RE_plugin *plugin)
|
||||
{
|
||||
struct RE_renderer *r = plugin->renderer;
|
||||
glUniform3fv(r->camera_pos, 1, plugin->camera.pos);
|
||||
glUniform3fv(r->camera_pos, 1, plugin->camera.override_active
|
||||
? plugin->camera.override_pos : plugin->camera.pos);
|
||||
glUniform3fv(r->global_ambient, 1, plugin->global_ambient);
|
||||
int light_index = 0;
|
||||
for (int i = 0; i < plugin->nr_instances; i++) {
|
||||
@@ -1000,7 +1016,7 @@ void RE_render(struct sact_sprite *sp)
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
mat4 view_transform;
|
||||
RE_calc_view_matrix(&plugin->camera, GLM_YUP, view_transform);
|
||||
RE_calc_view_matrix(&plugin->camera, NULL, 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
|
||||
|
||||
+796
@@ -0,0 +1,796 @@
|
||||
/* 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 <ctype.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <cglm/cglm.h>
|
||||
|
||||
#include "system4.h"
|
||||
#include "system4/aar.h"
|
||||
#include "system4/utfsjis.h"
|
||||
|
||||
#include "3d_internal.h"
|
||||
#include "audio.h"
|
||||
|
||||
static void parse_error(const char *msg, char *input)
|
||||
{
|
||||
char *eol = strchr(input, '\n');
|
||||
if (eol) *eol = '\0';
|
||||
WARNING("Parse error: %s Near: \"%s\"", msg, input);
|
||||
}
|
||||
|
||||
static enum s3de_object_type parse_object_type(const char *s)
|
||||
{
|
||||
if (!strcmp(s, "ビルボード"))
|
||||
return S3DE_OBJ_BILLBOARD;
|
||||
if (!strcmp(s, "ポリゴンオブジェクト"))
|
||||
return S3DE_OBJ_POLYGON;
|
||||
if (!strcmp(s, "カメラ"))
|
||||
return S3DE_OBJ_CAMERA;
|
||||
WARNING("unknown object type \"%s\"", s);
|
||||
return (enum s3de_object_type)-1;
|
||||
}
|
||||
|
||||
static enum s3de_move_type parse_move_type(const char *s)
|
||||
{
|
||||
if (!strcmp(s, "直線"))
|
||||
return S3DE_MOVE_LINEAR;
|
||||
if (!strcmp(s, "ベジェ"))
|
||||
return S3DE_MOVE_BEZIER;
|
||||
WARNING("unknown move type \"%s\"", s);
|
||||
return (enum s3de_move_type)-1;
|
||||
}
|
||||
|
||||
static enum s3de_spawn_position_type parse_spawn_position_type(const char *s)
|
||||
{
|
||||
if (!strcmp(s, "標準"))
|
||||
return S3DE_SPAWN_NORMAL;
|
||||
if (!strcmp(s, "球"))
|
||||
return S3DE_SPAWN_SPHERE;
|
||||
if (!strcmp(s, "水平面"))
|
||||
return S3DE_SPAWN_HORIZONTAL;
|
||||
WARNING("unknown spawn position type \"%s\"", s);
|
||||
return (enum s3de_spawn_position_type)-1;
|
||||
}
|
||||
|
||||
static enum s3de_blend_type parse_blend_type(const char *s)
|
||||
{
|
||||
if (!strcmp(s, "標準"))
|
||||
return S3DE_BLEND_NORMAL;
|
||||
if (!strcmp(s, "加算"))
|
||||
return S3DE_BLEND_ADDITIVE;
|
||||
WARNING("unknown blend type \"%s\"", s);
|
||||
return (enum s3de_blend_type)-1;
|
||||
}
|
||||
|
||||
static enum s3de_direction_type parse_direction_type(const char *s)
|
||||
{
|
||||
if (!strcmp(s, "ランダム"))
|
||||
return S3DE_DIR_RANDOM;
|
||||
if (!strcmp(s, "指定方向"))
|
||||
return S3DE_DIR_SPECIFIED;
|
||||
if (!strcmp(s, "エミッタ方向"))
|
||||
return S3DE_DIR_EMITTER;
|
||||
if (!strcmp(s, "エミッタ逆方向"))
|
||||
return S3DE_DIR_EMITTER_REVERSE;
|
||||
if (!strcmp(s, "エミッタ座標系"))
|
||||
return S3DE_DIR_EMITTER_COORD;
|
||||
if (!strcmp(s, "任意平面"))
|
||||
return S3DE_DIR_ARBITRARY_PLANE;
|
||||
if (!strcmp(s, "生成方向"))
|
||||
return S3DE_DIR_SPAWN;
|
||||
WARNING("unknown direction type \"%s\"", s);
|
||||
return (enum s3de_direction_type)-1;
|
||||
}
|
||||
|
||||
static enum s3de_posture_type parse_posture_type(const char *s)
|
||||
{
|
||||
if (!strcmp(s, "なし"))
|
||||
return S3DE_POSE_NONE;
|
||||
if (!strcmp(s, "カメラ"))
|
||||
return S3DE_POSE_CAMERA;
|
||||
if (!strcmp(s, "移動方向"))
|
||||
return S3DE_POSE_MOVE_DIR;
|
||||
if (!strcmp(s, "移動&飛行方向"))
|
||||
return S3DE_POSE_MOVE_AND_FLY;
|
||||
if (!strcmp(s, "飛行&エミッタ移動方向"))
|
||||
return S3DE_POSE_FLY_AND_EMITTER_MOVE;
|
||||
WARNING("unknown posture type \"%s\"", s);
|
||||
return (enum s3de_posture_type)-1;
|
||||
}
|
||||
|
||||
static enum s3de_interp_type parse_interp_type(const char *s)
|
||||
{
|
||||
if (!strcmp(s, "なし"))
|
||||
return S3DE_INTERP_NONE;
|
||||
if (!strcmp(s, "線形"))
|
||||
return S3DE_INTERP_LINEAR;
|
||||
if (!strcmp(s, "球面線形"))
|
||||
return S3DE_INTERP_SLERP;
|
||||
WARNING("unknown interp type \"%s\"", s);
|
||||
return (enum s3de_interp_type)-1;
|
||||
}
|
||||
|
||||
#define MATCH(fmt, ...) (len = 0, sscanf(text, fmt " %n", ##__VA_ARGS__, &len), text += len, len)
|
||||
#define STRING_BUF_SIZE 256
|
||||
#define KEYWORD " %255[^, \t\r\n}]"
|
||||
#define QUOTED_STRING "\"%255[^\"]\""
|
||||
|
||||
static char *parse_quoted_string(char **ptext)
|
||||
{
|
||||
char *text = *ptext;
|
||||
|
||||
while (isspace((unsigned char)*text)) text++;
|
||||
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);
|
||||
}
|
||||
|
||||
// range := float [',' float]
|
||||
static bool parse_range(char **ptext, struct s3de_range *r)
|
||||
{
|
||||
char *text = *ptext;
|
||||
int len;
|
||||
float a, b;
|
||||
if (!MATCH("%f", &a)) {
|
||||
parse_error("float expected.", text);
|
||||
return false;
|
||||
}
|
||||
r->base = a;
|
||||
r->random = 0.0f;
|
||||
if (MATCH(", %f", &b))
|
||||
r->random = b;
|
||||
*ptext = text;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_two_floats(char **ptext, float out[2])
|
||||
{
|
||||
char *text = *ptext;
|
||||
int len;
|
||||
if (!MATCH("%f , %f", &out[0], &out[1])) {
|
||||
parse_error("two floats expected.", text);
|
||||
return false;
|
||||
}
|
||||
*ptext = text;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_spin_angle(char **ptext, struct s3de_range *start, struct s3de_range *end)
|
||||
{
|
||||
float v[2];
|
||||
if (!parse_two_floats(ptext, v))
|
||||
return false;
|
||||
start->base = v[0];
|
||||
start->random = 0.0f;
|
||||
end->base = v[1];
|
||||
end->random = 0.0f;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_vec3(char **ptext, vec3 out)
|
||||
{
|
||||
char *text = *ptext;
|
||||
int len;
|
||||
if (!MATCH("%f , %f , %f", &out[0], &out[1], &out[2])) {
|
||||
parse_error("three floats expected.", text);
|
||||
return false;
|
||||
}
|
||||
*ptext = text;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int *parse_int_list(char **ptext, int *nr)
|
||||
{
|
||||
char *text = *ptext;
|
||||
int len, n;
|
||||
if (!MATCH("%d", &n)) {
|
||||
parse_error("int expected.", text);
|
||||
return NULL;
|
||||
}
|
||||
int cap = 16;
|
||||
int *v = xmalloc(cap * sizeof(int));
|
||||
v[0] = n;
|
||||
*nr = 1;
|
||||
while (MATCH(", %d", &n)) {
|
||||
if (*nr == cap) {
|
||||
cap *= 2;
|
||||
v = xrealloc(v, cap * sizeof(int));
|
||||
}
|
||||
v[(*nr)++] = n;
|
||||
}
|
||||
*ptext = text;
|
||||
return v;
|
||||
}
|
||||
|
||||
// Parses a comma-separated list of quoted strings, storing the first non-empty
|
||||
// one in *out and skipping the rest. Rance 9 only uses the first name.
|
||||
static bool parse_texture_name(char **ptext, char **out)
|
||||
{
|
||||
char *text = *ptext;
|
||||
int len;
|
||||
char *result = NULL;
|
||||
do {
|
||||
char *s = parse_quoted_string(&text);
|
||||
if (!s) {
|
||||
free(result);
|
||||
return false;
|
||||
}
|
||||
if (result || s[0] == '\0')
|
||||
free(s);
|
||||
else
|
||||
result = s;
|
||||
} while (MATCH(", "));
|
||||
*ptext = text;
|
||||
*out = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parses a list of scalar entries of the form "{ frame, value, interp }, ...".
|
||||
static bool parse_scalar_entries(char **ptext, struct s3de_scalar_entry **out, int *nr_out)
|
||||
{
|
||||
char *text = *ptext;
|
||||
int len;
|
||||
int cap = 8, nr = 0;
|
||||
char buf[STRING_BUF_SIZE];
|
||||
struct s3de_scalar_entry *v = xmalloc(cap * sizeof(*v));
|
||||
for (;;) {
|
||||
if (!MATCH("{ %d , %f , " KEYWORD, &v[nr].frame, &v[nr].value, buf)) {
|
||||
parse_error("scalar list entry expected.", text);
|
||||
goto err;
|
||||
}
|
||||
int interp = parse_interp_type(buf);
|
||||
if (interp < 0) goto err;
|
||||
v[nr].interp = interp;
|
||||
if (!MATCH("}")) { parse_error("'}' expected.", text); goto err; }
|
||||
nr++;
|
||||
if (nr == cap) {
|
||||
cap *= 2;
|
||||
v = xrealloc(v, cap * sizeof(*v));
|
||||
}
|
||||
if (!MATCH(",")) break;
|
||||
}
|
||||
*out = v;
|
||||
*nr_out = nr;
|
||||
*ptext = text;
|
||||
return true;
|
||||
err:
|
||||
free(v);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parses a list of vec3 entries of the form "{ frame, x, y, z, interp }, ...".
|
||||
static bool parse_vec3_entries(char **ptext, struct s3de_vec3_entry **out, int *nr_out)
|
||||
{
|
||||
char *text = *ptext;
|
||||
int len;
|
||||
int cap = 8, nr = 0;
|
||||
char buf[STRING_BUF_SIZE];
|
||||
struct s3de_vec3_entry *v = xmalloc(cap * sizeof(*v));
|
||||
for (;;) {
|
||||
if (!MATCH("{ %d , %f , %f , %f , " KEYWORD,
|
||||
&v[nr].frame, &v[nr].v[0], &v[nr].v[1], &v[nr].v[2], buf)) {
|
||||
parse_error("vec3 list entry expected.", text);
|
||||
goto err;
|
||||
}
|
||||
int interp = parse_interp_type(buf);
|
||||
if (interp < 0) goto err;
|
||||
v[nr].interp = interp;
|
||||
if (!MATCH("}")) { parse_error("'}' expected.", text); goto err; }
|
||||
nr++;
|
||||
if (nr == cap) {
|
||||
cap *= 2;
|
||||
v = xrealloc(v, cap * sizeof(*v));
|
||||
}
|
||||
if (!MATCH(",")) break;
|
||||
}
|
||||
*out = v;
|
||||
*nr_out = nr;
|
||||
*ptext = text;
|
||||
return true;
|
||||
err:
|
||||
free(v);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parses a list of position entries of the form
|
||||
// "{ frame, x, y, z, interp, [spawn_radius, spawn_angle_deg] }, ...".
|
||||
static bool parse_position_entries(char **ptext, struct s3de_position_entry **out, int *nr_out)
|
||||
{
|
||||
char *text = *ptext;
|
||||
int len;
|
||||
int cap = 8, nr = 0;
|
||||
char buf[STRING_BUF_SIZE];
|
||||
struct s3de_position_entry *v = xmalloc(cap * sizeof(*v));
|
||||
for (;;) {
|
||||
memset(&v[nr], 0, sizeof(v[nr]));
|
||||
if (!MATCH("{ %d , %f , %f , %f , " KEYWORD,
|
||||
&v[nr].frame, &v[nr].pos[0], &v[nr].pos[1], &v[nr].pos[2], buf)) {
|
||||
parse_error("position list entry expected.", text);
|
||||
goto err;
|
||||
}
|
||||
// Convert from left-handed coords to right-handed coords.
|
||||
v[nr].pos[2] = -v[nr].pos[2];
|
||||
int interp = parse_interp_type(buf);
|
||||
if (interp < 0) goto err;
|
||||
v[nr].interp = interp;
|
||||
(void)MATCH(", %f , %f", &v[nr].spawn_radius, &v[nr].spawn_angle_deg);
|
||||
if (!MATCH("}")) { parse_error("'}' expected.", text); goto err; }
|
||||
nr++;
|
||||
if (nr == cap) {
|
||||
cap *= 2;
|
||||
v = xrealloc(v, cap * sizeof(*v));
|
||||
}
|
||||
if (!MATCH(",")) break;
|
||||
}
|
||||
*out = v;
|
||||
*nr_out = nr;
|
||||
*ptext = text;
|
||||
return true;
|
||||
err:
|
||||
free(v);
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct s3de *parse_3de(char *text)
|
||||
{
|
||||
struct s3de *s = xcalloc(1, sizeof(struct s3de));
|
||||
s->loop_start_frame = -1;
|
||||
s->loop_end_frame = -1;
|
||||
|
||||
struct s3de_object *current = NULL;
|
||||
|
||||
for (;;) {
|
||||
// Skip whitespace and // comments.
|
||||
for (;;) {
|
||||
while (isspace((unsigned char)*text)) text++;
|
||||
if (text[0] == '/' && text[1] == '/') {
|
||||
char *nl = strchr(text, '\n');
|
||||
text = nl ? nl + 1 : text + strlen(text);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int len;
|
||||
int n;
|
||||
float f;
|
||||
char buf[STRING_BUF_SIZE];
|
||||
|
||||
if (!current) {
|
||||
if (!*text) return s;
|
||||
|
||||
if (MATCH("ループ開始フレーム = %d", &n)) {
|
||||
s->loop_start_frame = n;
|
||||
} else if (MATCH("ループ終了フレーム = %d", &n)) {
|
||||
s->loop_end_frame = n;
|
||||
} else if (MATCH("オブジェクト " QUOTED_STRING " = {", buf)) {
|
||||
s->objects = xrealloc_array(s->objects, s->nr_objects,
|
||||
s->nr_objects + 1, sizeof(struct s3de_object));
|
||||
current = &s->objects[s->nr_objects++];
|
||||
current->name = utf2sjis(buf, 0);
|
||||
current->texture_anime_frame = 1.0f;
|
||||
} else {
|
||||
parse_error("unknown toplevel token.", text);
|
||||
goto err;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!*text) {
|
||||
WARNING("unexpected EOF inside object");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (MATCH("}")) {
|
||||
current = NULL;
|
||||
} else if (MATCH("種類 = " KEYWORD, buf)) {
|
||||
int v = parse_object_type(buf);
|
||||
if (v < 0) goto err;
|
||||
current->type = v;
|
||||
} else if (MATCH("移動タイプ = " KEYWORD, buf)) {
|
||||
int v = parse_move_type(buf);
|
||||
if (v < 0) goto err;
|
||||
current->movement_type = v;
|
||||
} else if (MATCH("生成位置タイプ = " KEYWORD, buf)) {
|
||||
int v = parse_spawn_position_type(buf);
|
||||
if (v < 0) goto err;
|
||||
current->spawn_position_type = v;
|
||||
} else if (MATCH("生成距離 = %f", &f)) {
|
||||
current->spawn_distance = f;
|
||||
} else if (MATCH("個数 = %d", &n)) {
|
||||
current->particle_count = n;
|
||||
} else if (MATCH("テクスチャ = ")) {
|
||||
if (!parse_texture_name(&text, ¤t->texture)) goto err;
|
||||
} else if (MATCH("ブレンドタイプ = " KEYWORD, buf)) {
|
||||
int v = parse_blend_type(buf);
|
||||
if (v < 0) goto err;
|
||||
current->blend_type = v;
|
||||
} else if (MATCH("テクスチャアニメフレーム = %f", &f)) {
|
||||
current->texture_anime_frame = f;
|
||||
} else if (MATCH("ポリゴン = ")) {
|
||||
current->polygon_name = parse_quoted_string(&text);
|
||||
if (!current->polygon_name) goto err;
|
||||
} else if (MATCH("フレーム参照 = " KEYWORD, buf)) {
|
||||
// Every emitter in Rance 9 uses "エフェクト".
|
||||
if (strcmp(buf, "エフェクト") != 0)
|
||||
WARNING("unhandled frame_reference \"%s\"", buf);
|
||||
} else if (MATCH("エミッタ連動タイプ = %d", &n)) {
|
||||
if (n < 0 || n > S3DE_LINK_MAX) {
|
||||
WARNING("unknown emitter_link_type %d", n);
|
||||
n = S3DE_LINK_NONE;
|
||||
}
|
||||
current->emitter_link_type = n;
|
||||
} else if (MATCH("ソフトフォグエッジ = %d", &n)) {
|
||||
current->soft_fog_edge = (n != 0);
|
||||
} else if (MATCH("姿勢タイプ = " KEYWORD, buf)) {
|
||||
int v = parse_posture_type(buf);
|
||||
if (v < 0) goto err;
|
||||
current->posture_type = v;
|
||||
} else if (MATCH("ターゲット名 = ")) {
|
||||
// Unused.
|
||||
char *s = parse_quoted_string(&text);
|
||||
if (!s) goto err;
|
||||
free(s);
|
||||
} else if (MATCH("方向タイプ = " KEYWORD, buf)) {
|
||||
int v = parse_direction_type(buf);
|
||||
if (v < 0) goto err;
|
||||
current->direction_type = v;
|
||||
} else if (MATCH("方向 = ")) {
|
||||
if (!parse_vec3(&text, current->direction))
|
||||
goto err;
|
||||
current->direction[2] = -current->direction[2];
|
||||
} else if (MATCH("方向角度 = %f", &f)) {
|
||||
current->direction_angle = f;
|
||||
}
|
||||
else if (MATCH("開始サイズ = ")) { if (!parse_range(&text, ¤t->start_size)) goto err; }
|
||||
else if (MATCH("終了サイズ = ")) { if (!parse_range(&text, ¤t->end_size)) goto err; }
|
||||
else if (MATCH("開始Xサイズ = ")) { if (!parse_range(&text, ¤t->start_x_size)) goto err; }
|
||||
else if (MATCH("終了Xサイズ = ")) { if (!parse_range(&text, ¤t->end_x_size)) goto err; }
|
||||
else if (MATCH("開始Yサイズ = ")) { if (!parse_range(&text, ¤t->start_y_size)) goto err; }
|
||||
else if (MATCH("終了Yサイズ = ")) { if (!parse_range(&text, ¤t->end_y_size)) goto err; }
|
||||
else if (MATCH("オフセットX = ")) { if (!parse_range(&text, ¤t->offset_x)) goto err; }
|
||||
else if (MATCH("オフセットY = ")) { if (!parse_range(&text, ¤t->offset_y)) goto err; }
|
||||
else if (MATCH("オフセットZ = ")) {
|
||||
if (!parse_range(&text, ¤t->offset_z))
|
||||
goto err;
|
||||
current->offset_z.base = -current->offset_z.base;
|
||||
}
|
||||
else if (MATCH("速度 = ")) { if (!parse_range(&text, ¤t->speed)) goto err; }
|
||||
else if (MATCH("加速度 = ")) { if (!parse_range(&text, ¤t->acceleration)) goto err; }
|
||||
else if (MATCH("移動距離 = ")) { if (!parse_range(&text, ¤t->movement_distance)) goto err; }
|
||||
else if (MATCH("移動カーブ = ")) { if (!parse_range(&text, ¤t->movement_curve)) goto err; }
|
||||
else if (MATCH("自由落下 = %d", &n)) { current->free_fall = (n != 0); }
|
||||
else if (MATCH("質量 = %f", &f)) { current->mass = f; }
|
||||
else if (MATCH("空気抵抗係数 = %f", &f)) { current->air_resistance = f; }
|
||||
else if (MATCH("開始X回転角度 = ")) { if (!parse_range(&text, ¤t->start_x_rotation)) goto err; }
|
||||
else if (MATCH("終了X回転角度 = ")) { if (!parse_range(&text, ¤t->end_x_rotation)) goto err; }
|
||||
else if (MATCH("開始Y回転角度 = ")) { if (!parse_range(&text, ¤t->start_y_rotation)) goto err; }
|
||||
else if (MATCH("終了Y回転角度 = ")) { if (!parse_range(&text, ¤t->end_y_rotation)) goto err; }
|
||||
else if (MATCH("開始Z回転角度 = ")) { if (!parse_range(&text, ¤t->start_z_rotation)) goto err; }
|
||||
else if (MATCH("終了Z回転角度 = ")) { if (!parse_range(&text, ¤t->end_z_rotation)) goto err; }
|
||||
else if (MATCH("X軸自転角度 = ")) { if (!parse_spin_angle(&text, ¤t->start_x_rotation, ¤t->end_x_rotation)) goto err; }
|
||||
else if (MATCH("Y軸自転角度 = ")) { if (!parse_spin_angle(&text, ¤t->start_y_rotation, ¤t->end_y_rotation)) goto err; }
|
||||
else if (MATCH("Z軸自転角度 = ")) { if (!parse_spin_angle(&text, ¤t->start_z_rotation, ¤t->end_z_rotation)) goto err; }
|
||||
else if (MATCH("X軸公転角度 = ")) { if (!parse_two_floats(&text, current->x_revolution_angle)) goto err; }
|
||||
else if (MATCH("Y軸公転角度 = ")) { if (!parse_two_floats(&text, current->y_revolution_angle)) goto err; }
|
||||
else if (MATCH("Z軸公転角度 = ")) { if (!parse_two_floats(&text, current->z_revolution_angle)) goto err; }
|
||||
else if (MATCH("X軸公転距離 = ")) { if (!parse_two_floats(&text, current->x_revolution_distance)) goto err; }
|
||||
else if (MATCH("Y軸公転距離 = ")) { if (!parse_two_floats(&text, current->y_revolution_distance)) goto err; }
|
||||
else if (MATCH("Z軸公転距離 = ")) { if (!parse_two_floats(&text, current->z_revolution_distance)) goto err; }
|
||||
else if (MATCH("カーブX距離 = %f", &f)) { /* unused */ }
|
||||
else if (MATCH("カーブY距離 = %f", &f)) { /* unused */ }
|
||||
else if (MATCH("カーブZ距離 = %f", &f)) { /* unused */ }
|
||||
else if (MATCH("子フレーム = %d", &n)) { current->child_frame = n; }
|
||||
else if (MATCH("子傾き = %f", &f)) { /* unused */ }
|
||||
else if (MATCH("子傾き2 = %f", &f)) { /* unused */ }
|
||||
else if (MATCH("子移動方向タイプ = %d", &n)) { /* unused */ }
|
||||
else if (MATCH("アップベクトルタイプ = %d", &n)) { /* unused */ }
|
||||
else if (MATCH("停止フレーム = %d", &n)) { /* unused */ }
|
||||
else if (MATCH("アルファフェードインフレーム = ")) { if (!parse_range(&text, ¤t->alpha_fade_in_frame)) goto err; }
|
||||
else if (MATCH("アルファフェードアウトフレーム = ")) { if (!parse_range(&text, ¤t->alpha_fade_out_frame)) goto err; }
|
||||
else if (MATCH("ダメージ = ")) {
|
||||
current->damages = parse_int_list(&text, ¤t->nr_damages);
|
||||
if (!current->damages)
|
||||
goto err;
|
||||
}
|
||||
else if (MATCH("位置リスト = ")) {
|
||||
if (!parse_position_entries(&text, ¤t->position_list, ¤t->nr_position_entries))
|
||||
goto err;
|
||||
}
|
||||
else if (MATCH("サイズリスト = ")) {
|
||||
if (!parse_scalar_entries(&text, ¤t->size_list, ¤t->nr_size_entries))
|
||||
goto err;
|
||||
}
|
||||
else if (MATCH("Xサイズリスト = ")) {
|
||||
if (!parse_scalar_entries(&text, ¤t->x_size_list, ¤t->nr_x_size_entries))
|
||||
goto err;
|
||||
}
|
||||
else if (MATCH("Yサイズリスト = ")) {
|
||||
if (!parse_scalar_entries(&text, ¤t->y_size_list, ¤t->nr_y_size_entries))
|
||||
goto err;
|
||||
}
|
||||
else if (MATCH("アルファリスト = ")) {
|
||||
if (!parse_scalar_entries(&text, ¤t->alpha_list, ¤t->nr_alpha_entries))
|
||||
goto err;
|
||||
}
|
||||
else if (MATCH("回転リスト = ")) {
|
||||
if (!parse_vec3_entries(&text, ¤t->rotation_list, ¤t->nr_rotation_entries))
|
||||
goto err;
|
||||
}
|
||||
else if (MATCH("乗算カラーリスト = ")) {
|
||||
if (!parse_vec3_entries(&text, ¤t->multiply_color_list, ¤t->nr_multiply_color_entries))
|
||||
goto err;
|
||||
}
|
||||
else if (MATCH("加算カラーリスト = ")) {
|
||||
if (!parse_vec3_entries(&text, ¤t->additive_color_list, ¤t->nr_additive_color_entries))
|
||||
goto err;
|
||||
}
|
||||
else {
|
||||
parse_error("unknown object field.", text);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
err:
|
||||
s3de_free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct s3de *s3de_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, ".3de");
|
||||
if (!dfile) {
|
||||
WARNING("%s\\%s.3de: not found", path, basename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *sjis_text = xmalloc(dfile->size + 1);
|
||||
memcpy(sjis_text, dfile->data, dfile->size);
|
||||
sjis_text[dfile->size] = '\0';
|
||||
|
||||
char *text = sjis2utf(sjis_text, dfile->size);
|
||||
free(sjis_text);
|
||||
struct s3de *s = parse_3de(text);
|
||||
free(text);
|
||||
if (!s) {
|
||||
WARNING("could not parse %s", dfile->name);
|
||||
archive_free_data(dfile);
|
||||
return NULL;
|
||||
}
|
||||
archive_free_data(dfile);
|
||||
|
||||
s->path = strdup(path);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void s3de_free(struct s3de *s)
|
||||
{
|
||||
if (!s) return;
|
||||
for (int i = 0; i < s->nr_objects; i++) {
|
||||
struct s3de_object *o = &s->objects[i];
|
||||
free(o->name);
|
||||
free(o->texture);
|
||||
free(o->polygon_name);
|
||||
free(o->damages);
|
||||
free(o->position_list);
|
||||
free(o->size_list);
|
||||
free(o->x_size_list);
|
||||
free(o->y_size_list);
|
||||
free(o->alpha_list);
|
||||
free(o->rotation_list);
|
||||
free(o->multiply_color_list);
|
||||
free(o->additive_color_list);
|
||||
}
|
||||
free(s->objects);
|
||||
free(s->path);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void interp_vec3(struct s3de_vec3_entry *list, int n, float frame, vec3 dest, vec3 def)
|
||||
{
|
||||
if (n == 0) {
|
||||
glm_vec3_copy(def, dest);
|
||||
return;
|
||||
}
|
||||
if (frame <= list[0].frame) {
|
||||
glm_vec3_copy(list[0].v, dest);
|
||||
return;
|
||||
}
|
||||
if (frame >= list[n - 1].frame) {
|
||||
glm_vec3_copy(list[n - 1].v, dest);
|
||||
return;
|
||||
}
|
||||
int k = 0;
|
||||
while (k + 1 < n && list[k + 1].frame <= frame)
|
||||
k++;
|
||||
struct s3de_vec3_entry *a = &list[k];
|
||||
struct s3de_vec3_entry *b = &list[k + 1];
|
||||
if (a->interp == S3DE_INTERP_NONE) {
|
||||
glm_vec3_copy(a->v, dest);
|
||||
return;
|
||||
}
|
||||
float t = (frame - a->frame) / (float)(b->frame - a->frame);
|
||||
glm_vec3_lerp(a->v, b->v, t, dest);
|
||||
}
|
||||
|
||||
static void interp_pos(struct s3de_position_entry *list, int n, float frame, vec3 dest)
|
||||
{
|
||||
if (n == 0) {
|
||||
glm_vec3_zero(dest);
|
||||
return;
|
||||
}
|
||||
if (frame <= list[0].frame) {
|
||||
glm_vec3_copy(list[0].pos, dest);
|
||||
return;
|
||||
}
|
||||
if (frame >= list[n - 1].frame) {
|
||||
glm_vec3_copy(list[n - 1].pos, dest);
|
||||
return;
|
||||
}
|
||||
int k = 0;
|
||||
while (k + 1 < n && list[k + 1].frame <= frame)
|
||||
k++;
|
||||
struct s3de_position_entry *a = &list[k];
|
||||
struct s3de_position_entry *b = &list[k + 1];
|
||||
if (a->interp == S3DE_INTERP_NONE) {
|
||||
glm_vec3_copy(a->pos, dest);
|
||||
return;
|
||||
}
|
||||
float t = (frame - a->frame) / (float)(b->frame - a->frame);
|
||||
glm_vec3_lerp(a->pos, b->pos, t, dest);
|
||||
}
|
||||
|
||||
struct s3de_effect *s3de_effect_create(struct s3de *s, struct archive *aar)
|
||||
{
|
||||
struct s3de_effect *eff = xcalloc(1, sizeof(struct s3de_effect));
|
||||
eff->s3de = s;
|
||||
eff->wav_channel = -1;
|
||||
|
||||
const char *basename = strrchr(s->path, '\\');
|
||||
basename = basename ? basename + 1 : s->path;
|
||||
struct archive_data *dfile = RE_get_aar_entry(aar, s->path, basename, ".wav");
|
||||
if (dfile) {
|
||||
int ch = wav_get_unused_channel();
|
||||
if (wav_prepare_from_archive_data(ch, dfile)) {
|
||||
eff->wav_channel = ch;
|
||||
} else {
|
||||
WARNING("wav_prepare_from_archive_data failed for %s\\%s.wav", s->path, basename);
|
||||
}
|
||||
}
|
||||
return eff;
|
||||
}
|
||||
|
||||
void s3de_effect_free(struct s3de_effect *eff)
|
||||
{
|
||||
if (!eff)
|
||||
return;
|
||||
if (eff->wav_channel >= 0) {
|
||||
wav_stop(eff->wav_channel);
|
||||
wav_unprepare(eff->wav_channel);
|
||||
}
|
||||
free(eff);
|
||||
}
|
||||
|
||||
// Pick the camera object whose position_list frame range contains `frame`.
|
||||
// If `frame` is past every range, pick the camera with the latest last_frame.
|
||||
// If `frame` precedes every range, return -1.
|
||||
static int select_active_camera_obj(struct s3de_effect *eff, float frame)
|
||||
{
|
||||
int best = -1;
|
||||
float best_last = -INFINITY, best_first = -INFINITY;
|
||||
for (int i = 0; i < eff->s3de->nr_objects; i++) {
|
||||
struct s3de_object *obj = &eff->s3de->objects[i];
|
||||
if (obj->type != S3DE_OBJ_CAMERA || obj->nr_position_entries == 0)
|
||||
continue;
|
||||
float first = obj->position_list[0].frame;
|
||||
float last = obj->position_list[obj->nr_position_entries - 1].frame;
|
||||
if (first > frame)
|
||||
continue;
|
||||
if (frame < last)
|
||||
return i;
|
||||
if (last > best_last || (last == best_last && first > best_first)) {
|
||||
best = i;
|
||||
best_last = last;
|
||||
best_first = first;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
static void update_sound(struct s3de_effect *eff, struct motion *m)
|
||||
{
|
||||
if (eff->wav_channel < 0)
|
||||
return;
|
||||
bool stopped = (m->state == RE_MOTION_STATE_STOP);
|
||||
float frame = m->current_frame;
|
||||
if (eff->sound_started && (stopped || frame + 0.5f < eff->last_frame)) {
|
||||
wav_stop(eff->wav_channel);
|
||||
eff->sound_started = false;
|
||||
}
|
||||
if (!eff->sound_started && !stopped) {
|
||||
int ms = (int)(frame * 1000.0f / 30.0f);
|
||||
if (ms > 0)
|
||||
wav_seek(eff->wav_channel, ms);
|
||||
wav_play(eff->wav_channel);
|
||||
eff->sound_started = true;
|
||||
}
|
||||
eff->last_frame = frame;
|
||||
}
|
||||
|
||||
static void update_camera_override(struct RE_instance *inst, struct s3de_effect *eff, float frame)
|
||||
{
|
||||
int cam_idx = select_active_camera_obj(eff, frame);
|
||||
if (cam_idx < 0 || !inst->plugin)
|
||||
return;
|
||||
|
||||
struct s3de_object *obj = &eff->s3de->objects[cam_idx];
|
||||
vec3 emitter_pos, emitter_rotation_deg;
|
||||
interp_pos(obj->position_list, obj->nr_position_entries, frame, emitter_pos);
|
||||
interp_vec3(obj->rotation_list, obj->nr_rotation_entries, frame,
|
||||
emitter_rotation_deg, GLM_VEC3_ZERO);
|
||||
|
||||
if (inst->local_transform_needs_update)
|
||||
RE_instance_update_local_transform(inst);
|
||||
vec4 local_pos = { emitter_pos[0], emitter_pos[1], emitter_pos[2], 1.0f };
|
||||
vec4 world_pos;
|
||||
glm_mat4_mulv(inst->local_transform, local_pos, world_pos);
|
||||
struct RE_camera *cam = &inst->plugin->camera;
|
||||
glm_vec3_copy(world_pos, cam->override_pos);
|
||||
// XXX: Compose the instance and camera-object rotations by summing Euler
|
||||
// angles directly. This is not equivalent to true rotation composition in
|
||||
// general, but it is exact here: in Rance 9 non-camera instances are only
|
||||
// ever rotated about yaw. Since yaw is the outermost axis in the renderer's
|
||||
// YXZ convention, the two yaw rotations combine exactly by addition.
|
||||
cam->override_pitch = inst->pitch + emitter_rotation_deg[0];
|
||||
cam->override_yaw = inst->yaw + emitter_rotation_deg[1];
|
||||
cam->override_roll = -(inst->roll + emitter_rotation_deg[2]);
|
||||
cam->override_active = true;
|
||||
}
|
||||
|
||||
void s3de_effect_update(struct RE_instance *inst)
|
||||
{
|
||||
struct s3de_effect *eff = inst->s3de_effect;
|
||||
if (!eff || !inst->motion)
|
||||
return;
|
||||
update_sound(eff, inst->motion);
|
||||
float frame = inst->motion->current_frame;
|
||||
update_camera_override(inst, eff, frame);
|
||||
}
|
||||
|
||||
void s3de_calc_frame_range(struct s3de *s, struct motion *motion)
|
||||
{
|
||||
int begin = s->loop_start_frame >= 0 ? s->loop_start_frame : 0;
|
||||
int end = s->loop_end_frame >= 0 ? s->loop_end_frame : begin;
|
||||
motion->frame_begin = motion->loop_frame_begin = begin;
|
||||
motion->frame_end = motion->loop_frame_end = end;
|
||||
}
|
||||
@@ -45,6 +45,7 @@ xsystem4 = [version_h,
|
||||
'3d/particle.c',
|
||||
'3d/reign.c',
|
||||
'3d/renderer.c',
|
||||
'3d/s3de.c',
|
||||
|
||||
'dungeon/dgn.c',
|
||||
'dungeon/dtx.c',
|
||||
|
||||
Reference in New Issue
Block a user