mirror of
https://github.com/open-goal/jak-project
synced 2026-07-01 20:20:35 -04:00
e6260e48ab
Adds support for exporting animations for foreground models. It's not perfect and doesn't handle the Jak 2/3 animations very well in some cases (scale can often get messed up, especially for the LZO compressed ones, I have no idea what is going on with the data in those art groups sometimes, so that'll have to be revisited later...), but it does a decent job on Jak 1. Additionally, the `build-actor` tool has also been changed to support setting the `master-art-group-name` and `master-art-group-index` fields to allow for custom art groups to link their animations to a different master art group, which lets you add custom animations to vanilla art groups.
643 lines
22 KiB
C++
643 lines
22 KiB
C++
#include "build_actor.h"
|
|
|
|
#include "common/log/log.h"
|
|
#include "common/math/geometry.h"
|
|
|
|
#include "goalc/build_actor/common/MercExtract.h"
|
|
#include "goalc/build_actor/common/animation_processing.h"
|
|
|
|
#include "third-party/tiny_gltf/tiny_gltf.h"
|
|
|
|
using namespace gltf_util;
|
|
namespace jak2 {
|
|
|
|
JointAnimCompressedHDR::JointAnimCompressedHDR(const anim::CompressedAnim& anim) {
|
|
matrix_bits = 0;
|
|
if (anim.matrix_animated[0]) {
|
|
matrix_bits |= 1;
|
|
}
|
|
if (anim.matrix_animated[1]) {
|
|
matrix_bits |= 2;
|
|
}
|
|
|
|
for (auto& word : control_bits) {
|
|
word = 0;
|
|
}
|
|
|
|
for (size_t i = 0; i < anim.joint_metadata.size(); i++) {
|
|
const int word_idx = i / 8;
|
|
if (word_idx >= 14) {
|
|
lg::error("too many joints!!");
|
|
break;
|
|
}
|
|
|
|
u32 val = 0;
|
|
const auto& metadata = anim.joint_metadata[i];
|
|
if (metadata.animated_trans) {
|
|
val |= (0b1);
|
|
}
|
|
if (metadata.animated_quat) {
|
|
val |= (0b10);
|
|
}
|
|
if (metadata.animated_scale) {
|
|
val |= (0b100);
|
|
}
|
|
if (metadata.big_trans_mode) {
|
|
val |= (0b1000);
|
|
}
|
|
val = (val << (4 * (i % 8)));
|
|
control_bits[word_idx] |= val;
|
|
}
|
|
|
|
num_joints = 2 + anim.joint_metadata.size();
|
|
}
|
|
|
|
JointAnimCompressedFixed::JointAnimCompressedFixed(const anim::CompressedAnim& anim) : hdr(anim) {
|
|
u8* dest = (u8*)data;
|
|
const u8* u64_src = (const u8*)anim.fixed.data64.data();
|
|
const u8* u32_src = (const u8*)anim.fixed.data32.data();
|
|
const u8* u16_src = (const u8*)anim.fixed.data16.data();
|
|
|
|
const int u64_size = anim.fixed.data64.size() * sizeof(u64);
|
|
const int u32_size = anim.fixed.data32.size() * sizeof(u32);
|
|
const int u16_size = anim.fixed.data16.size() * sizeof(u16);
|
|
|
|
if (u64_size + u32_size + u16_size > 133 * 4 * 4) {
|
|
lg::die("fixed sizes are {} and {}", 133 * 4 * 4, u64_size + u32_size + u16_size);
|
|
}
|
|
ASSERT(u64_size + u32_size + u16_size <= 133 * 4 * 4);
|
|
|
|
offset_64 = 0;
|
|
memcpy(dest, u64_src, u64_size);
|
|
dest += u64_size;
|
|
|
|
offset_32 = offset_64 + u64_size;
|
|
memcpy(dest, u32_src, u32_size);
|
|
dest += u32_size;
|
|
|
|
offset_16 = offset_32 + u32_size;
|
|
memcpy(dest, u16_src, u16_size);
|
|
reserved = 0;
|
|
|
|
num_data_qw_used = (15 + u64_size + u32_size + u16_size) / 16;
|
|
ASSERT(num_data_qw_used <= 133);
|
|
}
|
|
|
|
JointAnimCompressedFrame::JointAnimCompressedFrame(const anim::CompressedFrame& frame) {
|
|
reserved = 0;
|
|
u8* dest = (u8*)data;
|
|
const u8* u64_src = (const u8*)frame.data64.data();
|
|
const u8* u32_src = (const u8*)frame.data32.data();
|
|
const u8* u16_src = (const u8*)frame.data16.data();
|
|
|
|
const int u64_size = frame.data64.size() * sizeof(u64);
|
|
const int u32_size = frame.data32.size() * sizeof(u32);
|
|
const int u16_size = frame.data16.size() * sizeof(u16);
|
|
|
|
if (u64_size + u32_size + u16_size > 133 * 4 * 4) {
|
|
lg::die("frame sizes are {} and {}", 133 * 4 * 4, u64_size + u32_size + u16_size);
|
|
}
|
|
|
|
offset_64 = 0;
|
|
memcpy(dest, u64_src, u64_size);
|
|
dest += u64_size;
|
|
|
|
offset_32 = offset_64 + u64_size;
|
|
memcpy(dest, u32_src, u32_size);
|
|
dest += u32_size;
|
|
|
|
offset_16 = offset_32 + u32_size;
|
|
memcpy(dest, u16_src, u16_size);
|
|
reserved = 0;
|
|
|
|
num_data_qw_used = (15 + u64_size + u32_size + u16_size) / 16;
|
|
ASSERT(num_data_qw_used <= 133);
|
|
}
|
|
|
|
JointAnimCompressedControl::JointAnimCompressedControl(const anim::CompressedAnim& anim)
|
|
: fixed(anim) {
|
|
num_frames = anim.frames.size();
|
|
for (auto& in_frame : anim.frames) {
|
|
frame.emplace_back(in_frame);
|
|
}
|
|
fixed_qwc = fixed.num_data_qw_used;
|
|
frame_qwc = frame.at(0).num_data_qw_used;
|
|
}
|
|
|
|
ArtJointAnim::ArtJointAnim(const anim::CompressedAnim& anim, const std::vector<Joint>& joints)
|
|
: frames(anim) {
|
|
this->name = anim.name;
|
|
length = joints.size();
|
|
speed = 1.0f;
|
|
artist_base = 0.0f;
|
|
artist_step = 1.0f;
|
|
if (!anim.master_art_group_name.empty()) {
|
|
master_art_group_name = anim.master_art_group_name;
|
|
} else {
|
|
master_art_group_name = name;
|
|
}
|
|
if (anim.master_art_group_index != -1) {
|
|
master_art_group_index = anim.master_art_group_index;
|
|
} else {
|
|
master_art_group_index = 2;
|
|
}
|
|
}
|
|
|
|
size_t JointAnimCompressedFrame::generate(DataObjectGenerator& gen) const {
|
|
gen.align(16); // might have been 8, but 16 doesn't hurt.
|
|
|
|
size_t result = gen.current_offset_bytes();
|
|
gen.add_word(offset_64); // 0
|
|
gen.add_word(offset_32); // 4
|
|
gen.add_word(offset_16); // 8
|
|
gen.add_word(reserved); // 12
|
|
|
|
for (u32 i = 0; i < num_data_qw_used; i++) {
|
|
for (int j = 0; j < 4; j++) {
|
|
gen.add_word_float(data[i][j]);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
size_t JointAnimCompressedHDR::generate(DataObjectGenerator& gen) const {
|
|
size_t result = gen.current_offset_bytes();
|
|
for (auto& bit : control_bits) {
|
|
gen.add_word(bit);
|
|
}
|
|
gen.add_word(num_joints);
|
|
gen.add_word(matrix_bits);
|
|
return result;
|
|
}
|
|
|
|
size_t JointAnimCompressedFixed::generate(DataObjectGenerator& gen) const {
|
|
gen.align(16); // might have been 8, but 16 doesn't hurt.
|
|
size_t result = gen.current_offset_bytes();
|
|
hdr.generate(gen); // 0-64 (inline)
|
|
gen.add_word(offset_64); // 64
|
|
gen.add_word(offset_32); // 68
|
|
gen.add_word(offset_16); // 72
|
|
gen.add_word(reserved); // 76
|
|
// default joint poses (taken from money-idle)
|
|
for (int i = 0; i < num_data_qw_used; i++) {
|
|
gen.add_word_float(data[i].x());
|
|
gen.add_word_float(data[i].y());
|
|
gen.add_word_float(data[i].z());
|
|
gen.add_word_float(data[i].w());
|
|
}
|
|
// -- not sure what this is, part of the dummy data maybe?
|
|
gen.add_word(0);
|
|
gen.add_word(0x7fff0000);
|
|
gen.add_word(0x2250000);
|
|
gen.add_word(0x10001000);
|
|
gen.add_word(0x10000000);
|
|
gen.add_word(0);
|
|
gen.add_word(0);
|
|
gen.add_word(0);
|
|
gen.align(4);
|
|
return result;
|
|
}
|
|
|
|
size_t JointAnimCompressedControl::generate(DataObjectGenerator& gen) const {
|
|
gen.align(16);
|
|
size_t result = gen.current_offset_bytes();
|
|
gen.add_word(num_frames); // 0
|
|
gen.add_word(fixed_qwc); // 4
|
|
gen.add_word(frame_qwc); // 8
|
|
|
|
auto ja_fixed_slot = gen.add_word(0);
|
|
std::vector<int> ja_frame_slots;
|
|
for (u32 i = 0; i < num_frames; i++) {
|
|
ja_frame_slots.push_back(gen.add_word(0));
|
|
}
|
|
gen.link_word_to_byte(ja_fixed_slot, fixed.generate(gen));
|
|
ASSERT(num_frames == frame.size());
|
|
for (size_t i = 0; i < num_frames; i++) {
|
|
gen.link_word_to_byte(ja_frame_slots[i], frame[i].generate(gen));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
size_t CollideMesh::generate(DataObjectGenerator& gen) const {
|
|
gen.align_to_basic();
|
|
gen.add_type_tag("collide-mesh");
|
|
size_t result = gen.current_offset_bytes();
|
|
// gen.add_word(0xffffffff - joint_id); // 4 (joint-id)
|
|
gen.add_word(joint_id); // 4 (joint-id)
|
|
gen.add_word(num_tris); // 8 (num-tris)
|
|
gen.add_word(num_verts); // 12 (num-verts)
|
|
auto vertices_slot = gen.add_word(0); // 16 (vertex-data)
|
|
gen.add_word(0); // 20 (pad)
|
|
gen.add_word(0); // 24 (pad)
|
|
gen.add_word(0); // 28 (pad)
|
|
for (auto& tri : tris) {
|
|
u32 word = (tri.vert_idx[0] & 0xff) + ((tri.vert_idx[1] << 8) & 0xff00) +
|
|
((tri.vert_idx[2] << 16) & 0xff0000);
|
|
gen.add_word(word); // 0 (tris | vertex-index | unused)
|
|
gen.add_word(tri.pat.val); // 4 (pat)
|
|
}
|
|
// vertex data start
|
|
gen.link_word_to_byte(vertices_slot, gen.current_offset_bytes());
|
|
for (auto& vert : vertices) {
|
|
gen.add_word_float(vert.x());
|
|
gen.add_word_float(vert.y());
|
|
gen.add_word_float(vert.z());
|
|
gen.add_word_float(vert.w());
|
|
}
|
|
return result;
|
|
};
|
|
|
|
void ArtJointGeo::add_res() {
|
|
if (!cmeshes.empty()) {
|
|
lump.add_res(
|
|
std::make_unique<ResRef>("collide-mesh-group", "array", mesh_slot, DEFAULT_RES_TIME));
|
|
}
|
|
if (texture_bucket != -1) {
|
|
lump.add_res(std::make_unique<ResUint8>(
|
|
"texture-bucket", std::vector<u8>{static_cast<u8>(texture_bucket)}, DEFAULT_RES_TIME));
|
|
}
|
|
if (texture_level != -1) {
|
|
lump.add_res(std::make_unique<ResInt32>("texture-level", std::vector<s32>{texture_level},
|
|
DEFAULT_RES_TIME));
|
|
}
|
|
if (trans_offset != math::Vector4f{0.f, 0.f, 0.f, 1.f}) {
|
|
lump.add_res(std::make_unique<ResVector>(
|
|
"trans-offset", std::vector<math::Vector4f>{trans_offset}, DEFAULT_RES_TIME));
|
|
}
|
|
if (joint_channel != -1) {
|
|
lump.add_res(std::make_unique<ResInt32>("joint-channel", std::vector<s32>{joint_channel},
|
|
DEFAULT_RES_TIME));
|
|
}
|
|
if (!lod_dist.empty()) {
|
|
lump.add_res(std::make_unique<ResFloat>("lod-dist", lod_dist, DEFAULT_RES_TIME));
|
|
}
|
|
lump.sort_res();
|
|
}
|
|
|
|
size_t ArtJointGeo::generate_mesh(DataObjectGenerator& gen) const {
|
|
std::vector<size_t> data_slots;
|
|
std::vector<size_t> content_slots;
|
|
gen.align_to_basic();
|
|
gen.add_type_tag("array");
|
|
size_t result = gen.current_offset_bytes();
|
|
gen.add_word(cmeshes.size()); // 0 (length)
|
|
gen.add_word(cmeshes.size()); // 4 (allocated-length)
|
|
gen.add_type_tag("collide-mesh"); // 8 (content-type)
|
|
content_slots.reserve(cmeshes.size());
|
|
for (auto& data : cmeshes) {
|
|
(void)data;
|
|
content_slots.push_back(gen.add_word(0)); // 12 (data)
|
|
}
|
|
gen.align(4);
|
|
for (size_t i = 0; i < content_slots.size(); i++) {
|
|
data_slots.push_back(cmeshes.at(i).generate(gen));
|
|
}
|
|
for (size_t i = 0; i < content_slots.size(); i++) {
|
|
gen.link_word_to_byte(content_slots.at(i), data_slots.at(i));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
size_t ArtJointGeo::generate(DataObjectGenerator& gen) {
|
|
gen.align_to_basic();
|
|
gen.add_type_tag("art-joint-geo");
|
|
size_t result = gen.current_offset_bytes();
|
|
gen.add_word(0); // 4
|
|
gen.add_ref_to_string_in_pool(name); // 8
|
|
gen.add_word(length); // 12
|
|
auto res_slot = gen.add_word(0); // 16 (res-lump)
|
|
gen.add_word(0); // 20
|
|
gen.add_word(0);
|
|
gen.add_word(0);
|
|
std::vector<size_t> joint_slots;
|
|
for (int i = 0; i < length; i++) {
|
|
joint_slots.push_back(gen.add_word(0));
|
|
}
|
|
gen.align(4);
|
|
for (int i = 0; i < length; i++) {
|
|
auto joint = data.at(i).generate(gen);
|
|
gen.link_word_to_byte(joint_slots.at(i), joint);
|
|
g_joint_map[data.at(i).number] = joint;
|
|
}
|
|
mesh_slot = generate_mesh(gen);
|
|
add_res();
|
|
auto res_header = lump.generate_header(gen, "res-lump");
|
|
gen.link_word_to_byte(res_slot, res_header);
|
|
lump.generate_tag_list_and_data(gen, res_header);
|
|
return result;
|
|
}
|
|
|
|
size_t ArtJointAnim::generate(DataObjectGenerator& gen) const {
|
|
gen.align_to_basic();
|
|
gen.add_type_tag("art-joint-anim");
|
|
size_t result = gen.current_offset_bytes();
|
|
gen.add_symbol_link("#f"); // 4 (eye block)
|
|
gen.add_ref_to_string_in_pool(name); // 8
|
|
gen.add_word(length); // 12
|
|
gen.add_symbol_link("#f"); // 16 (res-lump)
|
|
gen.add_word_float(speed); // 20
|
|
gen.add_word_float(artist_base); // 24
|
|
gen.add_word_float(artist_step); // 28
|
|
gen.add_ref_to_string_in_pool(master_art_group_name); // 32
|
|
gen.add_word(master_art_group_index); // 36
|
|
gen.add_symbol_link("#f"); // 40 (blerc)
|
|
auto ctrl_slot = gen.add_word(0);
|
|
std::vector<size_t> frame_slots;
|
|
for (int i = 0; i < length; i++) {
|
|
frame_slots.push_back(gen.add_word(0));
|
|
}
|
|
gen.align(4);
|
|
gen.link_word_to_byte(ctrl_slot, frames.generate(gen));
|
|
return result;
|
|
}
|
|
|
|
static size_t gen_dummy_frag_geo(DataObjectGenerator& gen) {
|
|
size_t result = gen.current_offset_bytes();
|
|
// frag geo stolen from money-lod2
|
|
static std::vector<u32> words = {
|
|
0x51180504, 0x1000013, 0x1b150000, 0x604, 0x5225, 0x80808080, 0x80808080,
|
|
0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080,
|
|
0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080,
|
|
0x80808080, 0x80808080, 0x80808080, 0xb4e48086, 0x757d3a0a, 0x1e324000, 0x1e328186,
|
|
0x757d3410, 0x1c4020, 0x432e8186, 0x251313, 0x71a28044, 0x1c8186, 0x757d2e16,
|
|
0x96ce4040, 0x719b8186, 0x151919, 0x71c08064, 0x96ce8186, 0x757d281c, 0xb4e44060,
|
|
0x71d28186, 0x251f1f, 0x435e8080, 0xb4e48186, 0x757d4322, 0x1e324080, 0x71cc7f86,
|
|
0xffdb4025, 0x71ad0064, 0x435c8186, 0xffea582b, 0x71bc0044, 0x43348186, 0xffdb5b31,
|
|
0x43530024, 0x71a48086, 0xffea3755, 0x43440000, 0x71a47f86, 0xffea3d3d, 0x43440080,
|
|
0x432e7f86, 0x254646, 0x71a20044, 0x43657f86, 0x154949, 0x43400024, 0x719b8186,
|
|
0x154c4c, 0x71c00064, 0x71d28186, 0x254f4f, 0x435e0000, 0x71d27f86, 0x250707,
|
|
0x435e8000, 0x43658186, 0x150d0d, 0x43408024, 0x0, 0x0, 0x0,
|
|
0xcb01005a, 0xcb00fffa, 0xcb01005a, 0x2101e01, 0x0, 0x0, 0x306,
|
|
0x4030000, 0x120, 0x0, 0x1cf02c14, 0x66c801d, 0x0, 0x0,
|
|
0x34, 0x0, 0x0, 0x0, 0x8, 0x0, 0x44,
|
|
0x80, 0x42, 0x0,
|
|
};
|
|
for (auto& word : words) {
|
|
gen.add_word(word);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
size_t gen_dummy_frag_ctrl(DataObjectGenerator& gen) {
|
|
size_t result = gen.current_offset_bytes();
|
|
gen.add_word(0x1063918);
|
|
gen.add_word(0x603);
|
|
gen.add_word(0x0);
|
|
gen.add_word(0x0);
|
|
return result;
|
|
}
|
|
|
|
size_t gen_dummy_frag_ctrl_for_uploads(DataObjectGenerator& gen, int n) {
|
|
size_t result = gen.current_offset_bytes();
|
|
|
|
std::vector<u8> packed_frag_ctrls;
|
|
|
|
// this is still a bit of a hack - the dummy_frag_ctrl above has 4 fragments, so we need to
|
|
// provide fragment controls for each. The PC merc renderer will de-duplicate bone uploads over
|
|
// all effects and fragments, so we just need to have a single fragment that asks for all bones,
|
|
// and everything will work out.
|
|
for (int k = 0; k < 4; k++) {
|
|
packed_frag_ctrls.push_back(0);
|
|
packed_frag_ctrls.push_back(0);
|
|
packed_frag_ctrls.push_back(0);
|
|
if (k == 0) { // for the first frag, do all matrix uploads.
|
|
// note that these are bogus destination addresses, but nothing uses it on PC
|
|
packed_frag_ctrls.push_back(n);
|
|
for (int i = 0; i < n; i++) {
|
|
packed_frag_ctrls.push_back(i);
|
|
packed_frag_ctrls.push_back(i);
|
|
}
|
|
} else {
|
|
// remaining frags can have empty matrix upload lists.
|
|
packed_frag_ctrls.push_back(0);
|
|
}
|
|
}
|
|
|
|
std::vector<u32> u32s((packed_frag_ctrls.size() + 3) / 4);
|
|
memcpy(u32s.data(), packed_frag_ctrls.data(), packed_frag_ctrls.size());
|
|
for (auto x : u32s) {
|
|
gen.add_word(x);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
size_t gen_dummy_extra_info(DataObjectGenerator& gen) {
|
|
size_t result = gen.current_offset_bytes();
|
|
gen.add_word(0x1);
|
|
gen.add_word(0x0);
|
|
gen.add_word(0x0);
|
|
gen.add_word(0x0);
|
|
gen.add_word(0xb74ccccd);
|
|
gen.add_word(0x40400000);
|
|
gen.add_word(0x8066a1ff);
|
|
gen.add_word(0x0);
|
|
return result;
|
|
}
|
|
|
|
void generate_merc_effects(DataObjectGenerator& gen, tfrag3::MercModel* mdl, int joints) {
|
|
struct EffectLocs {
|
|
size_t frag_geo;
|
|
size_t frag_ctrl;
|
|
size_t extra_info;
|
|
};
|
|
std::vector<EffectLocs> locs;
|
|
for (auto& e : mdl->effects) {
|
|
EffectLocs loc{};
|
|
auto envmap = (int)e.has_envmap;
|
|
loc.frag_geo = gen.add_word(0); // 112-140 (effect)
|
|
loc.frag_ctrl = gen.add_word(0); // 116 (frag-ctrl)
|
|
gen.add_word(0x0); // 120 (blend-data)
|
|
gen.add_word(0x0); // 124 (blend-ctrl)
|
|
gen.add_word(0x10000); // 128
|
|
gen.add_word(0x140000); // 132
|
|
gen.add_word((envmap << 24) + 0x1d); // 136
|
|
loc.extra_info = gen.add_word(0); // 140 (extra-info)
|
|
locs.push_back(loc);
|
|
}
|
|
for (auto& loc : locs) {
|
|
gen.link_word_to_byte(loc.extra_info, gen_dummy_extra_info(gen));
|
|
gen.link_word_to_byte(loc.frag_ctrl, gen_dummy_frag_ctrl_for_uploads(gen, joints + 3));
|
|
gen.link_word_to_byte(loc.frag_geo, gen_dummy_frag_geo(gen));
|
|
}
|
|
}
|
|
|
|
size_t generate_dummy_merc_ctrl(DataObjectGenerator& gen, const ArtGroup& ag) {
|
|
gen.align_to_basic();
|
|
gen.add_type_tag("merc-ctrl");
|
|
size_t result = gen.current_offset_bytes();
|
|
// excluding align and prejoint
|
|
auto joints = ((ArtJointGeo*)ag.elts.at(0).get())->length - 2;
|
|
auto effect_count = ag.mdl->effects.size();
|
|
gen.add_word(0); // 4
|
|
gen.add_ref_to_string_in_pool(ag.name + "-lod0"); // 8
|
|
gen.add_word(0); // 12
|
|
gen.add_symbol_link("#f"); // 16 (res-lump)
|
|
gen.add_word(joints); // 20 (num-joints)
|
|
gen.add_word(0x0); // 24 (seg-table)
|
|
gen.add_word(0x0); // 28 (pad)
|
|
gen.add_word(0x4185c8ac); // 32-160 (xyz-scale)
|
|
gen.add_word(0xc780ff80); // 36 (st-magic)
|
|
gen.add_word(0x40798000); // 40 (st-out-a)
|
|
gen.add_word(0x40eb4000); // 44 (st-out-b)
|
|
gen.add_word(0x4780ff80); // 48 (st-vif-add)
|
|
gen.add_word(0x50000); // 52 ((st-int-off << 16) + st-int-scale)
|
|
gen.add_word(effect_count); // 56 (effect-count)
|
|
gen.add_word(0x0); // 60 (blend-target-count)
|
|
gen.add_word((0x14 * effect_count << 16) +
|
|
effect_count); // 64 ((fragment-count << 16) + tri-count)
|
|
gen.add_word(0x130101); // 68
|
|
gen.add_word(0x13001d); // 72
|
|
gen.add_word(0x0); // 76
|
|
gen.add_word(0x0); // 80
|
|
gen.add_word(0x10101); // 84
|
|
gen.add_word(0x130000); // 88
|
|
gen.add_word(0x3f319ca9); // 92
|
|
gen.add_word(0x0); // 96
|
|
gen.add_word(0x0); // 100
|
|
gen.add_word(0x0); // 104
|
|
gen.add_word(0x0); // 108
|
|
gen.add_word(0x0); // 112
|
|
gen.add_word(0x0); // 116
|
|
gen.add_word(0x0); // 120
|
|
gen.add_word(0x0); // 124
|
|
gen.add_word(0x0); // 128
|
|
gen.add_word(0x0); // 132
|
|
gen.add_word(0x0); // 136
|
|
gen.add_word(0x0); // 140
|
|
gen.add_word(0x0); // 144
|
|
gen.add_word(0x0); // 148
|
|
gen.add_word(0x0); // 152
|
|
gen.add_word(0x0); // 156
|
|
generate_merc_effects(gen, ag.mdl, joints);
|
|
return result;
|
|
}
|
|
|
|
std::vector<u8> ArtGroup::save_object_file() const {
|
|
DataObjectGenerator gen;
|
|
gen.add_type_tag("art-group");
|
|
auto ag_words = 8 + length;
|
|
while (gen.words() < ag_words) {
|
|
gen.add_word(0);
|
|
}
|
|
auto file_info_slot = info.add_to_object_file(gen);
|
|
gen.link_word_to_byte(1, file_info_slot); // 4 (file-info)
|
|
gen.link_word_to_string_in_pool(name, 8 / 4); // 8 (name)
|
|
gen.set_word(12 / 4, length); // 12 (ag length)
|
|
gen.link_word_to_symbol("#f", 16 / 4); // 16 (res-lump)
|
|
gen.set_word(20 / 4, 0); // 20 (pad)
|
|
gen.set_word(24 / 4, 0);
|
|
gen.set_word(28 / 4, 0);
|
|
if (!elts.empty()) {
|
|
if (elts.at(0)) {
|
|
auto jgeo = (ArtJointGeo*)elts.at(0).get();
|
|
gen.link_word_to_byte(32 / 4, jgeo->generate(gen));
|
|
}
|
|
if (!elts.at(1)) {
|
|
gen.link_word_to_byte(36 / 4, generate_dummy_merc_ctrl(gen, *this));
|
|
}
|
|
|
|
for (size_t i = 2; i < elts.size(); i++) {
|
|
auto ja = (ArtJointAnim*)elts.at(i).get();
|
|
gen.link_word_to_byte((32 + i * 4) / 4, ja->generate(gen));
|
|
}
|
|
}
|
|
|
|
return gen.generate_v4();
|
|
}
|
|
|
|
int ArtGroup::get_joint_idx(const std::string& name) {
|
|
for (auto& elt : this->elts) {
|
|
if (elt && typeid(*elt) == typeid(ArtJointGeo)) {
|
|
auto jgeo = (ArtJointGeo*)elt.get();
|
|
for (auto& joint : jgeo->data) {
|
|
if (joint.name == name) {
|
|
return joint.number;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*!
|
|
* Build GOAL format data for an actor. This doesn't generate the data for the .FR3.
|
|
*/
|
|
bool run_build_actor(const std::string& mdl_name,
|
|
const std::string& ag_out,
|
|
const BuildActorParams2& params) {
|
|
std::string ag_name;
|
|
if (fs::exists(file_util::get_jak_project_dir() / mdl_name)) {
|
|
ag_name = fs::path(mdl_name).stem().string();
|
|
} else {
|
|
ASSERT_MSG(false, "Model file not found: " + mdl_name);
|
|
}
|
|
|
|
// Load GLTF file to check for a skeleton
|
|
tinygltf::Model model = load_gltf_model(file_util::get_jak_project_dir() / mdl_name);
|
|
auto all_nodes = flatten_nodes_from_all_scenes(model);
|
|
auto skin_idx = find_single_skin(model, all_nodes);
|
|
if (skin_idx) {
|
|
lg::info("GLTF file contained a skin, this actor will have a real skeleton");
|
|
}
|
|
std::vector<anim::CompressedAnim> user_anims;
|
|
|
|
ArtGroup ag(ag_name);
|
|
std::vector<Joint> joints;
|
|
MercExtractData extract_data;
|
|
extract("test", extract_data, model, all_nodes, 0, 0, 0);
|
|
ag.mdl = &extract_data.new_model;
|
|
// MercSwapData out;
|
|
// merc_convert(out, extract_data);
|
|
// Set up joints:
|
|
if (skin_idx) {
|
|
// read skeleton data out of GLTF
|
|
auto skeleton_joints = extract_skeleton(model, *skin_idx);
|
|
// convert to game format
|
|
joints = convert_joints(skeleton_joints);
|
|
// get animation from user.
|
|
user_anims = process_anim(model, skeleton_joints, params.master_art_group, params.master_ag_map,
|
|
params.framerate);
|
|
|
|
} else {
|
|
auto identity = math::Matrix4f::identity();
|
|
joints.emplace_back("align", 0, -1, identity);
|
|
joints.emplace_back("prejoint", 1, -1, identity);
|
|
// matrix stolen from "egg" joint from "money" art group
|
|
auto main_pose = math::Matrix4f::identity();
|
|
main_pose(3, 1) = -2194.1628418f;
|
|
Joint main("main", 2, 1, main_pose);
|
|
joints.emplace_back(main);
|
|
}
|
|
|
|
std::vector<CollideMesh> mesh;
|
|
if (params.gen_collide_mesh) {
|
|
mesh = gen_collide_mesh_from_model_jak2(model, all_nodes, 3);
|
|
}
|
|
|
|
std::shared_ptr<ArtJointGeo> jgeo = std::make_shared<ArtJointGeo>(ag.name, mesh, joints, params);
|
|
|
|
ag.elts.emplace_back(jgeo);
|
|
// dummy merc-ctrl
|
|
ag.elts.emplace_back(nullptr);
|
|
|
|
if (!user_anims.empty()) {
|
|
for (auto& anim : user_anims) {
|
|
ag.elts.emplace_back(std::make_shared<ArtJointAnim>(anim, joints));
|
|
}
|
|
} else {
|
|
ag.elts.emplace_back(std::make_shared<ArtJointAnim>(ag.name, joints));
|
|
}
|
|
|
|
ag.length = ag.elts.size();
|
|
|
|
auto ag_file = ag.save_object_file();
|
|
lg::info("ag file size {} bytes", ag_file.size());
|
|
auto save_path = fs::path(ag_out);
|
|
file_util::create_dir_if_needed_for_file(ag_out);
|
|
lg::info("Saving to {}", save_path.string());
|
|
file_util::write_binary_file(file_util::get_jak_project_dir() / save_path, ag_file.data(),
|
|
ag_file.size());
|
|
return true;
|
|
}
|
|
} // namespace jak2
|