decompiler: support animation export and support master art groups in build-actor tool (#4260)

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.
This commit is contained in:
Hat Kid
2026-05-04 17:19:41 +02:00
committed by GitHub
parent a7844c9b5d
commit e6260e48ab
25 changed files with 923 additions and 75 deletions
+2 -1
View File
@@ -60,6 +60,7 @@ add_library(
level_extractor/extract_common.cpp
level_extractor/extract_hfrag.cpp
level_extractor/extract_joint_group.cpp
level_extractor/extract_anim.cpp
level_extractor/extract_level.cpp
level_extractor/extract_merc.cpp
level_extractor/extract_tfrag.cpp
@@ -100,7 +101,7 @@ target_link_libraries(decomp
stb_image
xdelta3
tiny_gltf
)
)
add_executable(decompiler
main.cpp)
+131
View File
@@ -16,6 +16,136 @@ struct Joint {
math::Matrix4f bind_pose_T_w;
};
struct UncompressedSingleJointAnim {
std::vector<math::Vector3f> trans_frames;
std::vector<math::Vector3f> scale_frames;
std::vector<math::Vector4f> quat_frames;
};
struct UncompressedJointAnim {
std::string name;
std::vector<UncompressedSingleJointAnim> joints;
float framerate = 30;
int frames = 0;
};
struct CompressedMatrixMetadata {
bool is_animated[2];
};
struct CompressedFrame {
std::vector<u16> data16;
std::vector<u32> data32;
std::vector<u64> data64;
int size_bytes() const { return data16.size() * 2 + data32.size() * 4 + data64.size() * 8; }
};
struct CompressedJointMetadata {
bool animated_trans = false;
bool animated_quat = false;
bool animated_scale = false;
bool big_trans_mode = false;
};
struct CompressedAnim {
std::string name;
CompressedFrame fixed;
std::vector<CompressedFrame> frames;
bool matrix_animated[2] = {false, false};
std::vector<CompressedJointMetadata> joint_metadata;
float framerate = 60;
};
struct JointAnimCompressedHDR {
u32 control_bits[14];
u32 num_joints;
u32 matrix_bits;
JointAnimCompressedHDR() {
for (auto& bit : control_bits) {
bit = 0;
}
num_joints = 1;
matrix_bits = 0;
}
};
struct JointAnimCompressedFixed {
JointAnimCompressedHDR hdr;
u32 offset_64;
u32 offset_32;
u32 offset_16;
u32 reserved;
math::Vector4f data[133];
int num_data_qw_used = 0;
bool mat[2] = {false, false};
math::Matrix4f mats[2] = {math::Matrix4f::zero(), math::Matrix4f::zero()};
u64 data64_size;
u32 data32_size;
u16 data16_size;
std::vector<u64> data64;
std::vector<u32> data32;
std::vector<u16> data16;
JointAnimCompressedFixed() {
offset_64 = 0;
offset_32 = 0;
offset_16 = 0;
reserved = 0;
data[0] = math::Vector4f(1.0f, 0.0f, 0.0f, 0.0f);
data[1] = math::Vector4f(0.0f, 1.0f, 0.0f, 0.0f);
data[2] = math::Vector4f(0.0f, 0.0f, 1.0f, 0.0f);
data[3] = math::Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
data[4] = math::Vector4f(1.0f, 0.0f, 0.0f, 0.0f);
data[5] = math::Vector4f(0.0f, 1.0f, 0.0f, 0.0f);
data[6] = math::Vector4f(0.0f, 0.0f, 1.0f, 0.0f);
data[7] = math::Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
num_data_qw_used = 8;
}
};
struct JointAnimCompressedFrame {
u32 offset_64;
u32 offset_32;
u32 offset_16;
u32 reserved;
// math::Vector4f data[133];
u32 num_data_qw_used = 0;
bool mat[2] = {false, false};
math::Matrix4f mats[2] = {math::Matrix4f::zero(), math::Matrix4f::zero()};
u64 data64_size;
u32 data32_size;
u16 data16_size;
std::vector<u64> data64;
std::vector<u32> data32;
std::vector<u16> data16;
JointAnimCompressedFrame() {
offset_64 = 0;
offset_32 = 0;
offset_16 = 0;
reserved = 0;
}
};
struct JointAnimCompressedControl {
u32 num_frames;
u32 fixed_qwc;
u32 frame_qwc;
JointAnimCompressedFixed fixed;
std::vector<JointAnimCompressedFrame> frame;
};
struct ArtJointAnim {
std::string name;
float speed;
float artist_base;
float artist_step;
s16 length;
JointAnimCompressedControl frames;
};
/*!
* Data extracted from art groups that is not needed for .FR3, but is potentially needed for other
* stuff (skeleton export).
@@ -24,6 +154,7 @@ struct ArtData {
std::string art_group_name;
std::string art_name;
std::vector<Joint> joint_group;
std::vector<ArtJointAnim> anims;
};
} // namespace level_tools
+267
View File
@@ -0,0 +1,267 @@
#include "extract_anim.h"
#include "common_formats.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "decompiler/util/goal_data_reader.h"
#include "third-party/lzokay/lzokay.hpp"
namespace decompiler {
static std::vector<u8> get_plain_data_bytes_up_to_label(const Ref& ref) {
const auto& words = ref.data->words_by_seg.at(ref.seg);
int start_word = ref.byte_offset / 4;
std::vector<u8> result;
for (int w = start_word; w < (int)words.size(); w++) {
if (words[w].kind() != LinkedWord::PLAIN_DATA)
break;
for (int b = 0; b < 4; b++)
result.push_back(words[w].get_byte(b));
}
return result;
}
static u32 read_u32(const u8* p) {
u32 v;
memcpy(&v, p, 4);
return v;
}
static void parse_fixed_from_buf(const u8* p,
level_tools::JointAnimCompressedFixed& fixed,
u32 fixed_qwc) {
memcpy(fixed.hdr.control_bits, p, sizeof(u32) * 14);
fixed.hdr.num_joints = read_u32(p + 56);
fixed.hdr.matrix_bits = read_u32(p + 60);
fixed.offset_64 = read_u32(p + 64);
fixed.offset_32 = read_u32(p + 68);
fixed.offset_16 = read_u32(p + 72);
fixed.reserved = read_u32(p + 76);
fixed.data64_size = fixed.offset_32 - fixed.offset_64;
fixed.data32_size = fixed.offset_16 - fixed.offset_32;
int data16 = (int)((fixed_qwc - 5) * 16) - (int)fixed.data64_size - (int)fixed.data32_size;
ASSERT(data16 >= 0);
fixed.data16_size = (u16)data16;
fixed.mat[0] = (fixed.hdr.matrix_bits & 1) == 0;
fixed.mat[1] = (fixed.hdr.matrix_bits & 2) == 0;
const u8* data = p + 80;
int d64 = (int)fixed.data64_size;
int d32 = (int)fixed.data32_size;
int d16 = (int)fixed.data16_size;
fixed.data64.resize((d64 + 7) / 8);
if (d64 > 0)
memcpy(fixed.data64.data(), data + fixed.offset_64, d64);
fixed.data32.resize((d32 + 3) / 4);
if (d32 > 0)
memcpy(fixed.data32.data(), data + fixed.offset_32, d32);
fixed.data16.resize((d16 + 1) / 2);
if (d16 > 0)
memcpy(fixed.data16.data(), data + fixed.offset_16, d16);
}
static void parse_frame_from_buf(const u8* p,
level_tools::JointAnimCompressedFrame& frame,
u32 frame_qwc) {
frame.offset_64 = read_u32(p + 0);
frame.offset_32 = read_u32(p + 4);
frame.offset_16 = read_u32(p + 8);
frame.reserved = read_u32(p + 12);
frame.data64_size = frame.offset_32 - frame.offset_64;
frame.data32_size = frame.offset_16 - frame.offset_32;
int data16 = (int)((frame_qwc - 1) * 16) - (int)frame.data64_size - (int)frame.data32_size;
ASSERT(data16 >= 0);
frame.data16_size = (u16)data16;
const u8* data = p + 16;
int fd64 = (int)frame.data64_size;
int fd32 = (int)frame.data32_size;
int fd16 = (int)frame.data16_size;
frame.data64.resize((fd64 + 7) / 8);
if (fd64 > 0)
memcpy(frame.data64.data(), data + frame.offset_64, fd64);
frame.data32.resize((fd32 + 3) / 4);
if (fd32 > 0)
memcpy(frame.data32.data(), data + frame.offset_32, fd32);
frame.data16.resize((fd16 + 1) / 2);
if (fd16 > 0)
memcpy(frame.data16.data(), data + frame.offset_16, fd16);
}
void extract_animations(const ObjectFileData& ag_data,
const DecompilerTypeSystem& dts,
GameVersion version,
std::map<std::string, level_tools::ArtData>& out) {
auto ja_locs = find_objects_with_type(ag_data.linked_data, "art-joint-anim");
if (ja_locs.empty()) {
lg::warn("extract_animations: art group {} has no anims, skipping", ag_data.name_in_dgo);
return;
}
// jak 2/3 split the first word into num-frames + flags
const bool has_flags = version != GameVersion::Jak1;
for (auto loc : ja_locs) {
TypedRef ref(Ref{&ag_data.linked_data, 0, loc * 4}, dts.ts.lookup_type("art-joint-anim"));
auto master_art_name = read_string_field(ref, "master-art-group-name", dts, false);
level_tools::ArtJointAnim ja;
ja.name = read_string_field(ref, "name", dts, false);
ja.speed = read_plain_data_field<float>(ref, "speed", dts);
ja.artist_base = read_plain_data_field<float>(ref, "artist-base", dts);
ja.artist_step = read_plain_data_field<float>(ref, "artist-step", dts);
Ref jacc = deref_label(get_field_ref(ref, "frames", dts));
int jacc_word_off = 0;
u32 first_word = deref_u32(jacc, jacc_word_off++);
ja.frames.num_frames = has_flags ? (first_word & 0xFFFF) : first_word;
ja.frames.fixed_qwc = deref_u32(jacc, jacc_word_off++);
ja.frames.frame_qwc = deref_u32(jacc, jacc_word_off++);
// jak 2/3 may lzo compress the animation, check the flag bit
const bool lzo_compressed = has_flags && ((first_word >> 16) & 1) != 0;
// lg::info("{}: extracting anim {} (compressed {})", ag_data.name_in_dgo, ja.name,
// lzo_compressed);
Ref fixed_ptr = jacc;
fixed_ptr.byte_offset += jacc_word_off * 4;
Ref fixed_ref = deref_label(fixed_ptr);
if (lzo_compressed) {
size_t decompressed_size =
((size_t)ja.frames.fixed_qwc + (size_t)ja.frames.num_frames * ja.frames.frame_qwc) * 16;
auto compressed = get_plain_data_bytes_up_to_label(fixed_ref);
ASSERT(!compressed.empty());
std::vector<u8> decompressed(decompressed_size);
size_t out_size = 0;
auto lzo_result = lzokay::decompress(compressed.data(), compressed.size(),
decompressed.data(), decompressed.size(), out_size);
ASSERT(lzo_result == lzokay::EResult::Success ||
lzo_result == lzokay::EResult::InputNotConsumed);
// if (out_size != decompressed_size) {
// lg::warn("lzo decomp size mismatch for '{}' in '{}': got {} bytes, expected {}", ja.name,
// ag_data.name_in_dgo, out_size, decompressed_size);
// }
parse_fixed_from_buf(decompressed.data(), ja.frames.fixed, ja.frames.fixed_qwc);
size_t frame_base = (size_t)ja.frames.fixed_qwc * 16;
for (int i = 0; i < (int)ja.frames.num_frames; i++) {
auto& frame = ja.frames.frame.emplace_back();
parse_frame_from_buf(
decompressed.data() + frame_base + (size_t)i * ja.frames.frame_qwc * 16, frame,
ja.frames.frame_qwc);
}
} else {
int fixed_word_off = 0;
// fixed hdr
memcpy_from_plain_data((u8*)ja.frames.fixed.hdr.control_bits, fixed_ref, 4 * 14);
fixed_word_off += 14;
ja.frames.fixed.hdr.num_joints = deref_u32(fixed_ref, fixed_word_off++);
ja.frames.fixed.hdr.matrix_bits = deref_u32(fixed_ref, fixed_word_off++);
ja.frames.fixed.offset_64 = deref_u32(fixed_ref, fixed_word_off++);
ja.frames.fixed.offset_32 = deref_u32(fixed_ref, fixed_word_off++);
ja.frames.fixed.offset_16 = deref_u32(fixed_ref, fixed_word_off++);
ja.frames.fixed.reserved = deref_u32(fixed_ref, fixed_word_off++);
ja.frames.fixed.data64_size = ja.frames.fixed.offset_32 - ja.frames.fixed.offset_64;
ja.frames.fixed.data32_size = ja.frames.fixed.offset_16 - ja.frames.fixed.offset_32;
{
int data16 = (int)((ja.frames.fixed_qwc - 5) * 16) - (int)ja.frames.fixed.data64_size -
(int)ja.frames.fixed.data32_size;
ASSERT(data16 >= 0);
ja.frames.fixed.data16_size = (u16)data16;
}
// matrix flags
ja.frames.fixed.mat[0] = (ja.frames.fixed.hdr.matrix_bits & 1) == 0;
ja.frames.fixed.mat[1] = (ja.frames.fixed.hdr.matrix_bits & 2) == 0;
fixed_ref.byte_offset += fixed_word_off * 4;
int d64_bytes = (int)ja.frames.fixed.data64_size;
int d32_bytes = (int)ja.frames.fixed.data32_size;
int d16_bytes = (int)ja.frames.fixed.data16_size;
ja.frames.fixed.data64.resize((d64_bytes + 7) / 8);
if (d64_bytes > 0) {
Ref d64 = fixed_ref;
d64.byte_offset += ja.frames.fixed.offset_64;
memcpy_from_plain_data((u8*)ja.frames.fixed.data64.data(), d64, d64_bytes);
}
ja.frames.fixed.data32.resize((d32_bytes + 3) / 4);
if (d32_bytes > 0) {
Ref d32 = fixed_ref;
d32.byte_offset += ja.frames.fixed.offset_32;
memcpy_from_plain_data((u8*)ja.frames.fixed.data32.data(), d32, d32_bytes);
}
ja.frames.fixed.data16.resize((d16_bytes + 1) / 2);
if (d16_bytes > 0) {
Ref d16 = fixed_ref;
d16.byte_offset += ja.frames.fixed.offset_16;
memcpy_from_plain_data((u8*)ja.frames.fixed.data16.data(), d16, d16_bytes);
}
Ref frames_ref = jacc;
frames_ref.byte_offset += 16;
for (int i = 0; i < (int)ja.frames.num_frames; i++) {
Ref frame_ref = deref_label(frames_ref);
int frame_off = 0;
auto& frame = ja.frames.frame.emplace_back();
frame.offset_64 = deref_u32(frame_ref, frame_off++);
frame.offset_32 = deref_u32(frame_ref, frame_off++);
frame.offset_16 = deref_u32(frame_ref, frame_off++);
frame.reserved = deref_u32(frame_ref, frame_off++);
frame.data64_size = frame.offset_32 - frame.offset_64;
frame.data32_size = frame.offset_16 - frame.offset_32;
{
int data16 = (int)((ja.frames.frame_qwc - 1) * 16) - (int)frame.data64_size -
(int)frame.data32_size;
ASSERT(data16 >= 0);
frame.data16_size = (u16)data16;
}
Ref frame_data = frame_ref;
frame_data.byte_offset += frame_off * 4;
int fd64_bytes = (int)frame.data64_size;
int fd32_bytes = (int)frame.data32_size;
int fd16_bytes = (int)frame.data16_size;
frame.data64.resize((fd64_bytes + 7) / 8);
if (fd64_bytes > 0) {
Ref fd64 = frame_data;
fd64.byte_offset += frame.offset_64;
memcpy_from_plain_data((u8*)frame.data64.data(), fd64, fd64_bytes);
}
frame.data32.resize((fd32_bytes + 3) / 4);
if (fd32_bytes > 0) {
Ref fd32 = frame_data;
fd32.byte_offset += frame.offset_32;
memcpy_from_plain_data((u8*)frame.data32.data(), fd32, fd32_bytes);
}
frame.data16.resize((fd16_bytes + 1) / 2);
if (fd16_bytes > 0) {
Ref fd16 = frame_data;
fd16.byte_offset += frame.offset_16;
memcpy_from_plain_data((u8*)frame.data16.data(), fd16, fd16_bytes);
}
frames_ref.byte_offset += 4;
}
}
// this should catch 99% of cases, but there could be mismatches between
// master art names and model names
out[master_art_name + "-lod0"].anims.push_back(ja);
// out[master_art_name + "-lod1"].anims.push_back(ja);
// out[master_art_name + "-lod2"].anims.push_back(ja);
}
}
} // namespace decompiler
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "common/custom_data/Tfrag3Data.h"
#include "decompiler/ObjectFile/ObjectFileDB.h"
#include "decompiler/level_extractor/common_formats.h"
#include "goalc/build_actor/common/build_actor.h"
namespace decompiler {
void extract_animations(const ObjectFileData& ag_data,
const DecompilerTypeSystem& dts,
GameVersion version,
std::map<std::string, level_tools::ArtData>& out);
} // namespace decompiler
@@ -3,6 +3,8 @@
#include <set>
#include <thread>
#include "extract_anim.h"
#include "common/log/log.h"
#include "common/util/FileUtil.h"
#include "common/util/SimpleThreadGroup.h"
@@ -129,6 +131,7 @@ void extract_art_groups_from_level(const ObjectFileDB& db,
extract_merc(ag_file, tex_db, db.dts, tex_remap, level_data, false, db.version(),
swapped_info);
extract_joint_group(ag_file, db.dts, db.version(), art_group_data);
extract_animations(ag_file, db.dts, db.version(), art_group_data);
}
}
}
+280
View File
@@ -1,5 +1,6 @@
#include "fr3_to_gltf.h"
#include <algorithm>
#include <unordered_map>
#include "common/custom_data/Tfrag3Data.h"
@@ -879,6 +880,276 @@ int make_inv_matrix_bind_poses(const std::vector<level_tools::Joint>& joints,
return accessor_idx;
}
level_tools::UncompressedJointAnim decompress_anim(const level_tools::ArtJointAnim& art_anim) {
constexpr float kQuatScale = 0.000030517578125f;
constexpr float kScaleScale = 0.000244140625f;
constexpr float kTransScale = 4.f / 4096.f;
auto read_f32 = [](const u8*& ptr) -> float {
float v;
memcpy(&v, ptr, 4);
ptr += 4;
return v;
};
auto read_s16 = [](const u8*& ptr) -> float {
s16 v;
memcpy(&v, ptr, 2);
ptr += 2;
return v;
};
const auto& ctrl = art_anim.frames;
const auto& fixed = ctrl.fixed;
const auto& hdr = fixed.hdr;
int num_joints = (int)hdr.num_joints;
int total_frames = (int)ctrl.num_frames;
level_tools::UncompressedJointAnim out;
out.name = art_anim.name;
out.framerate = art_anim.speed > 0.f ? art_anim.speed * 60.f : 30.f;
out.frames = total_frames;
out.joints.resize(2 + num_joints);
auto d64 = (const u8*)fixed.data64.data();
auto d32 = (const u8*)fixed.data32.data();
auto d16 = (const u8*)fixed.data16.data();
if (fixed.mat[0])
d64 += 64;
if (fixed.mat[1])
d64 += 64;
for (int tqi = 0; tqi < num_joints; tqi++) {
int ctrl_idx = tqi / 8;
int ctrl_shift = 4 * (tqi % 8);
int c = 0xf & (hdr.control_bits[ctrl_idx] >> ctrl_shift);
auto& joint = out.joints[2 + tqi];
if (!(c & 0b0001)) {
math::Vector3f t;
if (c & 0b1000) {
t.x() = read_f32(d64) / 4096.f;
t.y() = read_f32(d64) / 4096.f;
t.z() = read_f32(d32) / 4096.f;
} else {
t.x() = read_s16(d32) * kTransScale;
t.y() = read_s16(d32) * kTransScale;
t.z() = read_s16(d16) * kTransScale;
}
joint.trans_frames.push_back(t);
}
if (!(c & 0b0010)) {
math::Vector4f q;
q.x() = read_s16(d64) * kQuatScale;
q.y() = read_s16(d64) * kQuatScale;
q.z() = read_s16(d64) * kQuatScale;
q.w() = read_s16(d64) * kQuatScale;
joint.quat_frames.push_back(q);
}
if (!(c & 0b0100)) {
math::Vector3f s;
s.x() = read_s16(d32) * kScaleScale;
s.y() = read_s16(d32) * kScaleScale;
s.z() = read_s16(d16) * kScaleScale;
joint.scale_frames.push_back(s);
}
}
for (int fi = 0; fi < total_frames; fi++) {
const auto& frame = ctrl.frame[fi];
const u8* data64 = (const u8*)frame.data64.data();
const u8* data32 = (const u8*)frame.data32.data();
const u8* data16 = (const u8*)frame.data16.data();
if (!fixed.mat[0])
data64 += sizeof(math::Matrix4f);
if (!fixed.mat[1])
data64 += sizeof(math::Matrix4f);
for (int tqi = 0; tqi < num_joints; tqi++) {
int ctrl_idx = tqi / 8;
int ctrl_shift = 4 * (tqi % 8);
int c = 0xf & (hdr.control_bits[ctrl_idx] >> ctrl_shift);
auto& joint = out.joints[2 + tqi];
if (c & 0b0001) {
math::Vector3f t;
if (c & 0b1000) {
t.x() = read_f32(data64) / 4096.f;
t.y() = read_f32(data64) / 4096.f;
t.z() = read_f32(data32) / 4096.f;
} else {
t.x() = read_s16(data32) * kTransScale;
t.y() = read_s16(data32) * kTransScale;
t.z() = read_s16(data16) * kTransScale;
}
joint.trans_frames.push_back(t);
}
if (c & 0b0010) {
math::Vector4f q;
q.x() = read_s16(data64) * kQuatScale;
q.y() = read_s16(data64) * kQuatScale;
q.z() = read_s16(data64) * kQuatScale;
q.w() = read_s16(data64) * kQuatScale;
joint.quat_frames.push_back(q);
}
if (c & 0b0100) {
math::Vector3f s;
s.x() = read_s16(data32) * kScaleScale;
s.y() = read_s16(data32) * kScaleScale;
s.z() = read_s16(data16) * kScaleScale;
joint.scale_frames.push_back(s);
}
}
}
for (int ji = 2; ji < (int)out.joints.size(); ji++) {
auto& joint = out.joints[ji];
while ((int)joint.trans_frames.size() < total_frames) {
if (joint.trans_frames.empty())
joint.trans_frames.emplace_back(0.f, 0.f, 0.f);
else
joint.trans_frames.push_back(joint.trans_frames[0]);
}
while ((int)joint.quat_frames.size() < total_frames) {
if (joint.quat_frames.empty())
joint.quat_frames.emplace_back(0.f, 0.f, 0.f, 1.f);
else
joint.quat_frames.push_back(joint.quat_frames[0]);
}
while ((int)joint.scale_frames.size() < total_frames) {
if (joint.scale_frames.empty())
joint.scale_frames.emplace_back(1.f, 1.f, 1.f);
else
joint.scale_frames.push_back(joint.scale_frames[0]);
}
}
return out;
}
int make_anim_float_accessor(const std::vector<float>& values, tinygltf::Model& model) {
int buf_idx = (int)model.buffers.size();
auto& buf = model.buffers.emplace_back();
buf.data.resize(values.size() * sizeof(float));
memcpy(buf.data.data(), values.data(), buf.data.size());
int bv_idx = (int)model.bufferViews.size();
auto& bv = model.bufferViews.emplace_back();
bv.buffer = buf_idx;
bv.byteOffset = 0;
bv.byteLength = buf.data.size();
int acc_idx = (int)model.accessors.size();
auto& acc = model.accessors.emplace_back();
acc.bufferView = bv_idx;
acc.byteOffset = 0;
acc.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
acc.count = (int)values.size();
acc.type = TINYGLTF_TYPE_SCALAR;
if (!values.empty()) {
float mn = values[0], mx = values[0];
for (float v : values) {
mn = std::min(mn, v);
mx = std::max(mx, v);
}
acc.minValues = {(double)mn};
acc.maxValues = {(double)mx};
}
return acc_idx;
}
int make_anim_vec3_accessor(const std::vector<math::Vector3f>& values, tinygltf::Model& model) {
static_assert(sizeof(math::Vector3f) == 3 * sizeof(float));
int buf_idx = (int)model.buffers.size();
auto& buf = model.buffers.emplace_back();
buf.data.resize(values.size() * sizeof(math::Vector3f));
memcpy(buf.data.data(), values.data(), buf.data.size());
int bv_idx = (int)model.bufferViews.size();
auto& bv = model.bufferViews.emplace_back();
bv.buffer = buf_idx;
bv.byteOffset = 0;
bv.byteLength = buf.data.size();
int acc_idx = (int)model.accessors.size();
auto& acc = model.accessors.emplace_back();
acc.bufferView = bv_idx;
acc.byteOffset = 0;
acc.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
acc.count = (int)values.size();
acc.type = TINYGLTF_TYPE_VEC3;
return acc_idx;
}
int make_anim_vec4_accessor(const std::vector<math::Vector4f>& values, tinygltf::Model& model) {
static_assert(sizeof(math::Vector4f) == 4 * sizeof(float));
int buf_idx = (int)model.buffers.size();
auto& buf = model.buffers.emplace_back();
buf.data.resize(values.size() * sizeof(math::Vector4f));
memcpy(buf.data.data(), values.data(), buf.data.size());
int bv_idx = (int)model.bufferViews.size();
auto& bv = model.bufferViews.emplace_back();
bv.buffer = buf_idx;
bv.byteOffset = 0;
bv.byteLength = buf.data.size();
int acc_idx = (int)model.accessors.size();
auto& acc = model.accessors.emplace_back();
acc.bufferView = bv_idx;
acc.byteOffset = 0;
acc.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
acc.count = (int)values.size();
acc.type = TINYGLTF_TYPE_VEC4;
return acc_idx;
}
void add_animation_to_gltf(const level_tools::UncompressedJointAnim& anim,
const tinygltf::Skin& skin,
tinygltf::Model& model) {
if (anim.frames == 0 || anim.joints.size() <= 2)
return;
auto& gltf_anim = model.animations.emplace_back();
gltf_anim.name = anim.name;
std::vector<float> times(anim.frames);
for (int i = 0; i < anim.frames; i++)
times[i] = i / anim.framerate;
int time_acc = make_anim_float_accessor(times, model);
int n_anim_joints = (int)anim.joints.size();
int n_skin_joints = (int)skin.joints.size();
for (int ji = 2; ji < n_anim_joints && ji < n_skin_joints; ji++) {
const auto& joint = anim.joints[ji];
int target_node = skin.joints[ji];
auto add_channel = [&](int val_acc, const std::string& path) {
int si = (int)gltf_anim.samplers.size();
auto& sampler = gltf_anim.samplers.emplace_back();
sampler.input = time_acc;
sampler.output = val_acc;
sampler.interpolation = "LINEAR";
auto& channel = gltf_anim.channels.emplace_back();
channel.sampler = si;
channel.target_node = target_node;
channel.target_path = path;
};
if ((int)joint.trans_frames.size() == anim.frames)
add_channel(make_anim_vec3_accessor(joint.trans_frames, model), "translation");
if ((int)joint.quat_frames.size() == anim.frames)
add_channel(make_anim_vec4_accessor(joint.quat_frames, model), "rotation");
if ((int)joint.scale_frames.size() == anim.frames)
add_channel(make_anim_vec3_accessor(joint.scale_frames, model), "scale");
}
}
void add_merc(const tfrag3::Level& level,
const std::map<std::string, level_tools::ArtData>& art_data,
const tfrag3::MercModel& mmodel,
@@ -956,6 +1227,15 @@ void add_merc(const tfrag3::Level& level,
skin.inverseBindMatrices = make_inv_matrix_bind_poses(game_bones, model);
}
if (art != art_data.end() && !art->second.anims.empty() && node.skin >= 0 &&
node.skin < model.skins.size()) {
const auto& skin = model.skins[node.skin];
for (const auto& ja : art->second.anims) {
auto uncompressed = decompress_anim(ja);
add_animation_to_gltf(uncompressed, skin, model);
}
}
for (size_t effect_idx = 0; effect_idx < mmodel.effects.size(); effect_idx++) {
const auto& effect = mmodel.effects[effect_idx];
for (size_t draw_idx = 0; draw_idx < effect.all_draws.size(); draw_idx++) {
+20 -3
View File
@@ -225,9 +225,9 @@
:tool 'build-level
:out '(,(string-append "$OUT/obj/" name ".go")))))
(defmacro build-actor (name &key (gen-mesh #f) &key (force-run #f) &key (texture-bucket 0))
(defmacro build-actor (name &key (gen-mesh #f) &key (force-run #f) &key (texture-bucket 0) &key (framerate 60.0) &key (master-art-group #f) &key (master-ag-map ()) &key (joint-channel 6))
(let* ((path (string-append "custom_assets/jak1/models/custom_levels/" name ".glb")))
`(defstep :in '(,path ,(symbol->string gen-mesh) ,(symbol->string force-run) ,(if (integer? texture-bucket) (int->string texture-bucket) (symbol->string texture-bucket)))
`(defstep :in '(,path ,gen-mesh ,force-run ,texture-bucket ,framerate ,master-art-group ,master-ag-map ,joint-channel)
:tool 'build-actor
:out '(,(string-append "$OUT/obj/" name "-ag.go")))))
@@ -1653,13 +1653,30 @@
;; Set up the build system to build the level geometry
;; this path is relative to the custom_assets/jak1/levels/ folder
;; it should point to the .jsonc file that specifies the level.
;; options:
;; - force-run: when #t, always forces a rebuild of the level instead of checking the "last modified" timestamp.
;; - gen-fr3: when #f, does not generate the .fr3 file for the level. useful for temporarily skipping the slow fr3 build process when
;; there's many custom levels that include extra art groups.
(build-custom-level "test-zone")
;; the DGO file
(custom-level-cgo "TSZ.DGO" "test-zone/testzone.gd")
;; generate the art group for a custom actor.
;; requires a .glb model file in custom_assets/jak1/models/custom_levels
;; to also generate a collide-mesh, add :gen-mesh #t
;; options:
;; - gen-mesh: when #t, generates a collision mesh for this actor.
;; - force-run: when #t, always forces a rebuild of the actor instead of checking the "last modified" timestamp.
;; - texture-bucket: sets the "texture-level" of the actor, which determines the level bucket to draw the actor in, affecting
;; the draw order. default is 0, which sets texture-level 0. if set to #f, no "texture-level" lump will be added to the
;; art group, defaulting to level 1. for example, actors that are in texture-level 1 will be drawn after shadows, meaning that
;; no shadows will be cast on them. the default value of 0 avoids this.
;; - framerate: set the framerate for custom animations, if available. defaults to 60.
;; - master-art-group: the name of the art group to link custom animations to. this can be used to
;; give existing art groups custom animations if there are enough empty slots left over.
;; - master-ag-map: a list of pairs of (anim-name master-art-group-idx), linking the animation with the given name
;; to the given index in the master art group.
;; - joint-channel: how many joint channels the actor should have. defaults to 6.
;; more complicated actors like jak that make a lot of use of animation blending can have 24+ channels.
(build-actor "test-actor" :gen-mesh #t)
;;;;;;;;;;;;;;;;;;;;;
+17 -1
View File
@@ -299,6 +299,10 @@
;; Set up the build system to build the level geometry
;; this path is relative to the custom_assets/jak2/levels folder
;; it should point to the .jsonc file that specifies the level.
;; options:
;; - force-run: when #t, always forces a rebuild of the level instead of checking the "last modified" timestamp.
;; - gen-fr3: when #f, does not generate the .fr3 file for the level. useful for temporarily skipping the slow fr3 build process when
;; there's many custom levels that include extra art groups.
(build-custom-level "test-zone")
;; the DGO file
(goal-src "levels/test-zone/test-zone-obs.gc" "process-focusable")
@@ -306,7 +310,19 @@
;; generate the art group for a custom actor.
;; requires a .glb model file in custom_assets/jak1/models/custom_levels
;; to also generate a collide-mesh, add :gen-mesh #t
;; options:
;; - gen-mesh: when #t, generates a collision mesh for this actor.
;; - force-run: when #t, always forces a rebuild of the actor instead of checking the "last modified" timestamp.
;; - texture-bucket: sets the "texture-level" of the actor, which determines the level bucket to draw the actor in, affecting
;; the draw order. default is 0, which sets texture-level 0. if set to #f, no "texture-level" lump will be added to the
;; art group, defaulting to level 1.
;; - framerate: set the framerate for custom animations, if available. defaults to 60.
;; - master-art-group: the name of the art group to link custom animations to. this can be used to
;; give existing art groups custom animations if there are enough empty slots left over.
;; - master-ag-map: a list of pairs of (anim-name master-art-group-idx), linking the animation with the given name
;; to the given index in the master art group.
;; - joint-channel: how many joint channels the actor should have. defaults to 6.
;; more complicated actors like jak that make a lot of use of animation blending can have 24+ channels.
(build-actor "test-actor" :force-run #t :gen-mesh #t)
;;;;;;;;;;;;;;;;;;;;;
+2 -2
View File
@@ -141,9 +141,9 @@
:tool 'build-level2
:out '(,(string-append "$OUT/obj/" name ".go")))))
(defmacro build-actor (name &key (gen-mesh #f) &key (force-run #f) &key (texture-bucket 0))
(defmacro build-actor (name &key (gen-mesh #f) &key (force-run #f) &key (texture-bucket 0) &key (framerate 60.0) &key (master-art-group #f) &key (master-ag-map ()) &key (joint-channel 6))
(let* ((path (string-append "custom_assets/jak2/models/custom_levels/" name ".glb")))
`(defstep :in '(,path ,(symbol->string gen-mesh) ,(symbol->string force-run) ,(if (integer? texture-bucket) (int->string texture-bucket) (symbol->string texture-bucket)))
`(defstep :in '(,path ,gen-mesh ,force-run ,texture-bucket ,framerate ,master-art-group ,master-ag-map ,joint-channel)
:tool 'build-actor2
:out '(,(string-append "$OUT/obj/" name "-ag.go")))))
+16 -1
View File
@@ -402,6 +402,9 @@
;; Set up the build system to build the level geometry
;; this path is relative to the custom_levels/jak3 folder
;; it should point to the .jsonc file that specifies the level.
;; - force-run: when #t, always forces a rebuild of the level instead of checking the "last modified" timestamp.
;; - gen-fr3: when #f, does not generate the .fr3 file for the level. useful for temporarily skipping the slow fr3 build process when
;; there's many custom levels that include extra art groups.
(build-custom-level "test-zone")
;; the DGO file
(goal-src "levels/test-zone/test-zone-obs.gc" "process-focusable")
@@ -409,7 +412,19 @@
;; generate the art group for a custom actor.
;; requires a .glb model file in custom_assets/jak3/models/custom_levels
;; to also generate a collide-mesh, add :gen-mesh #t
;; options:
;; - gen-mesh: when #t, generates a collision mesh for this actor.
;; - force-run: when #t, always forces a rebuild of the actor instead of checking the "last modified" timestamp.
;; - texture-bucket: sets the "texture-level" of the actor, which determines the level bucket to draw the actor in, affecting
;; the draw order. default is 0, which sets texture-level 0. if set to #f, no "texture-level" lump will be added to the
;; art group, defaulting to level 1.
;; - framerate: set the framerate for custom animations, if available. defaults to 60.
;; - master-art-group: the name of the art group to link custom animations to. this can be used to
;; give existing art groups custom animations if there are enough empty slots left over.
;; - master-ag-map: a list of pairs of (anim-name master-art-group-idx), linking the animation with the given name
;; to the given index in the master art group.
;; - joint-channel: how many joint channels the actor should have. defaults to 6.
;; more complicated actors like jak that make a lot of use of animation blending can have 24+ channels.
(build-actor "test-actor" :gen-mesh #t)
;;;;;;;;;;;;;;;;;;;;;
+2 -2
View File
@@ -256,8 +256,8 @@
:tool 'build-level3
:out '(,(string-append "$OUT/obj/" name ".go")))))
(defmacro build-actor (name &key (gen-mesh #f) &key (force-run #f) &key (texture-bucket 0))
(defmacro build-actor (name &key (gen-mesh #f) &key (force-run #f) &key (texture-bucket 0) &key (framerate 60.0) &key (master-art-group #f) &key (master-ag-map ()) &key (joint-channel 6))
(let* ((path (string-append "custom_assets/jak3/models/custom_levels/" name ".glb")))
`(defstep :in '(,path ,(symbol->string gen-mesh) ,(symbol->string force-run) ,(if (integer? texture-bucket) (int->string texture-bucket) (symbol->string texture-bucket)))
`(defstep :in '(,path ,gen-mesh ,force-run ,texture-bucket ,framerate ,master-art-group ,master-ag-map ,joint-channel)
:tool 'build-actor3
:out '(,(string-append "$OUT/obj/" name "-ag.go")))))
@@ -68,6 +68,8 @@ std::vector<math::Vector<float, n>> extract_keyframed_gltf_vecn(
UncompressedJointAnim extract_anim_from_gltf(const tinygltf::Model& model,
const tinygltf::Animation& anim,
const std::map<int, int>& node_to_joint,
const std::string& master_art_group,
int master_art_group_index,
float framerate) {
UncompressedJointAnim out;
out.name = anim.name;
@@ -75,6 +77,8 @@ UncompressedJointAnim extract_anim_from_gltf(const tinygltf::Model& model,
const int max_joint = find_max_joint(anim, node_to_joint);
lg::info("Max joint is {}", max_joint);
out.joints.resize(max_joint + 1);
out.master_art_group_name = master_art_group;
out.master_art_group_index = master_art_group_index;
for (const auto& channel : anim.channels) {
const int channel_node_idx = channel.target_node;
@@ -238,6 +242,8 @@ CompressedAnim compress_animation(const UncompressedJointAnim& in) {
out.name = in.name;
out.framerate = in.framerate;
out.frames.resize(in.frames);
out.master_art_group_name = in.master_art_group_name;
out.master_art_group_index = in.master_art_group_index;
for (int matrix = 0; matrix < 2; matrix++) {
const auto& joint_data = in.joints.at(matrix);
if (is_matrix_constant(joint_data)) {
@@ -18,6 +18,8 @@ struct UncompressedSingleJointAnim {
struct UncompressedJointAnim {
std::string name;
std::string master_art_group_name;
int master_art_group_index = -1;
std::vector<UncompressedSingleJointAnim> joints;
float framerate = 60;
int frames = 0;
@@ -44,6 +46,8 @@ struct CompressedJointMetadata {
struct CompressedAnim {
std::string name;
std::string master_art_group_name;
int master_art_group_index = -1;
CompressedFrame fixed;
std::vector<CompressedFrame> frames;
bool matrix_animated[2] = {false, false};
@@ -56,12 +60,17 @@ struct CompressedAnim {
* @param model The GLTF model containing the animation
* @param anim The animation to convert
* @param node_to_joint Mapping from GLTF node index to the joint index
* @param master_art_group The master art group to link this animation to, if set.
* @param master_art_group_index The index of the slot in the master art group to link this
* animation to.
* @param framerate Number of key-frames per second. (this doesn't have to match frame rate, the
* game will interpolate between keyframes as needed.)
*/
UncompressedJointAnim extract_anim_from_gltf(const tinygltf::Model& model,
const tinygltf::Animation& anim,
const std::map<int, int>& node_to_joint,
const std::string& master_art_group,
int master_art_group_index,
float framerate);
CompressedAnim compress_animation(const UncompressedJointAnim& in);
+10 -3
View File
@@ -150,7 +150,10 @@ std::vector<Joint> convert_joints(const std::vector<GltfJoint>& gjoints) {
}
std::vector<anim::CompressedAnim> process_anim(const tinygltf::Model& model,
const std::vector<GltfJoint>& gjoints) {
const std::vector<GltfJoint>& gjoints,
const std::string& master_art_group,
const std::map<std::string, int>& master_ag_map,
float framerate) {
if (model.animations.empty()) {
lg::warn("no animations detected!"); // TODO: make up a dummy one
return {};
@@ -164,8 +167,12 @@ std::vector<anim::CompressedAnim> process_anim(const tinygltf::Model& model,
std::vector<anim::CompressedAnim> ret;
for (auto& anim : model.animations) {
lg::info("Processing animation {}", anim.name);
ret.push_back(
anim::compress_animation(anim::extract_anim_from_gltf(model, anim, node_to_joint, 60)));
int master_ag_idx = -1;
if (!master_ag_map.empty() && master_ag_map.find(anim.name) != master_ag_map.end()) {
master_ag_idx = master_ag_map.at(anim.name);
}
ret.push_back(anim::compress_animation(anim::extract_anim_from_gltf(
model, anim, node_to_joint, master_art_group, master_ag_idx, framerate)));
}
return ret;
}
+7 -1
View File
@@ -31,6 +31,9 @@ struct BuildActorParams {
s32 joint_channel = -1;
math::Vector4f trans_offset = {0.f, 0.f, 0.f, 1.f};
std::vector<float> lod_dist{};
std::string master_art_group;
std::map<std::string, int> master_ag_map; // ja name -> master ag slot
float framerate = 60;
};
struct GltfJoint {
@@ -45,4 +48,7 @@ tinygltf::Model load_gltf_model(const fs::path& path);
std::vector<GltfJoint> extract_skeleton(const tinygltf::Model& model, int skin_idx);
std::vector<Joint> convert_joints(const std::vector<GltfJoint>& gjoints);
std::vector<anim::CompressedAnim> process_anim(const tinygltf::Model& model,
const std::vector<GltfJoint>& gjoints);
const std::vector<GltfJoint>& gjoints,
const std::string& master_art_group,
const std::map<std::string, int>& master_ag_map,
float framerate);
+13 -4
View File
@@ -131,8 +131,16 @@ ArtJointAnim::ArtJointAnim(const anim::CompressedAnim& anim, const std::vector<J
speed = 1.0f;
artist_base = 0.0f;
artist_step = 1.0f;
master_art_group_name = name;
master_art_group_index = 2;
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;
}
for (auto& joint : joints) {
data.emplace_back(joint, anim.frames.size());
}
@@ -564,7 +572,7 @@ int ArtGroup::get_joint_idx(const std::string& name) {
*/
bool run_build_actor(const std::string& mdl_name,
const std::string& ag_out,
const BuildActorParams& params) {
const BuildActorParams1& params) {
std::string ag_name;
if (fs::exists(file_util::get_jak_project_dir() / mdl_name)) {
ag_name = fs::path(mdl_name).stem().string();
@@ -595,7 +603,8 @@ bool run_build_actor(const std::string& mdl_name,
// convert to game format
joints = convert_joints(skeleton_joints);
// get animation from user.
user_anims = process_anim(model, skeleton_joints);
user_anims = process_anim(model, skeleton_joints, params.master_art_group, params.master_ag_map,
params.framerate);
} else {
auto identity = math::Matrix4f::identity();
+3 -10
View File
@@ -138,14 +138,7 @@ struct CollideMesh {
}
};
struct BuildActorParams {
bool gen_collide_mesh = false;
s8 texture_bucket = -1;
s32 texture_level = -1;
s32 joint_channel = -1;
math::Vector4f trans_offset = {0.f, 0.f, 0.f, 1.f};
std::vector<float> lod_dist{};
};
struct BuildActorParams1 : BuildActorParams {};
struct ArtJointGeo : ArtElement {
std::vector<Joint> data;
@@ -161,7 +154,7 @@ struct ArtJointGeo : ArtElement {
explicit ArtJointGeo(const std::string& name,
std::vector<CollideMesh> cmeshes,
std::vector<Joint>& joints,
const BuildActorParams& params) {
const BuildActorParams1& params) {
this->name = name + "-lod0";
length = joints.size();
for (auto& joint : joints) {
@@ -230,5 +223,5 @@ struct ArtGroup : Art {
bool run_build_actor(const std::string& input_model,
const std::string& output_file,
const BuildActorParams& params);
const BuildActorParams1& params);
} // namespace jak1
+13 -4
View File
@@ -131,8 +131,16 @@ ArtJointAnim::ArtJointAnim(const anim::CompressedAnim& anim, const std::vector<J
speed = 1.0f;
artist_base = 0.0f;
artist_step = 1.0f;
master_art_group_name = name;
master_art_group_index = 2;
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 {
@@ -557,7 +565,7 @@ int ArtGroup::get_joint_idx(const std::string& name) {
*/
bool run_build_actor(const std::string& mdl_name,
const std::string& ag_out,
const BuildActorParams& params) {
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();
@@ -588,7 +596,8 @@ bool run_build_actor(const std::string& mdl_name,
// convert to game format
joints = convert_joints(skeleton_joints);
// get animation from user.
user_anims = process_anim(model, skeleton_joints);
user_anims = process_anim(model, skeleton_joints, params.master_art_group, params.master_ag_map,
params.framerate);
} else {
auto identity = math::Matrix4f::identity();
+3 -10
View File
@@ -120,14 +120,7 @@ struct CollideMesh {
}
};
struct BuildActorParams {
bool gen_collide_mesh = false;
s8 texture_bucket = -1;
s32 texture_level = -1;
s32 joint_channel = -1;
math::Vector4f trans_offset = {0.f, 0.f, 0.f, 1.f};
std::vector<float> lod_dist{};
};
struct BuildActorParams2 : BuildActorParams {};
struct ArtJointGeo : ArtElement {
std::vector<Joint> data;
@@ -143,7 +136,7 @@ struct ArtJointGeo : ArtElement {
explicit ArtJointGeo(const std::string& name,
std::vector<CollideMesh> cmeshes,
std::vector<Joint>& joints,
const BuildActorParams& params) {
const BuildActorParams2& params) {
this->name = name + "-lod0";
length = joints.size();
for (auto& joint : joints) {
@@ -211,5 +204,5 @@ struct ArtGroup : Art {
bool run_build_actor(const std::string& input_model,
const std::string& output_file,
const BuildActorParams& params);
const BuildActorParams2& params);
} // namespace jak2
+13 -4
View File
@@ -131,8 +131,16 @@ ArtJointAnim::ArtJointAnim(const anim::CompressedAnim& anim, const std::vector<J
speed = 1.0f;
artist_base = 0.0f;
artist_step = 1.0f;
master_art_group_name = name;
master_art_group_index = 2;
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 {
@@ -569,7 +577,7 @@ int ArtGroup::get_joint_idx(const std::string& name) {
*/
bool run_build_actor(const std::string& mdl_name,
const std::string& ag_out,
const BuildActorParams& params) {
const BuildActorParams3& params) {
std::string ag_name;
if (fs::exists(file_util::get_jak_project_dir() / mdl_name)) {
ag_name = fs::path(mdl_name).stem().string();
@@ -600,7 +608,8 @@ bool run_build_actor(const std::string& mdl_name,
// convert to game format
joints = convert_joints(skeleton_joints);
// get animation from user.
user_anims = process_anim(model, skeleton_joints);
user_anims = process_anim(model, skeleton_joints, params.master_art_group, params.master_ag_map,
params.framerate);
} else {
auto identity = math::Matrix4f::identity();
+3 -10
View File
@@ -126,14 +126,7 @@ struct CollideMesh {
}
};
struct BuildActorParams {
bool gen_collide_mesh = false;
s8 texture_bucket = -1;
s32 texture_level = -1;
s32 joint_channel = -1;
math::Vector4f trans_offset = {0.f, 0.f, 0.f, 1.f};
std::vector<float> lod_dist{};
};
struct BuildActorParams3 : BuildActorParams {};
struct ArtJointGeo : ArtElement3 {
std::vector<Joint> data;
@@ -149,7 +142,7 @@ struct ArtJointGeo : ArtElement3 {
explicit ArtJointGeo(const std::string& name,
std::vector<CollideMesh> cmeshes,
std::vector<Joint>& joints,
const BuildActorParams& params) {
const BuildActorParams3& params) {
this->name = name + "-lod0";
master_art_group_name = name;
master_art_group_index = 0;
@@ -214,5 +207,5 @@ struct ArtGroup : Art {
bool run_build_actor(const std::string& input_model,
const std::string& output_file,
const BuildActorParams& params);
const BuildActorParams3& params);
} // namespace jak3
+3 -3
View File
@@ -64,7 +64,7 @@ int main(int argc, char** argv) {
switch (game_version) {
case GameVersion::Jak1: {
jak1::BuildActorParams params;
jak1::BuildActorParams1 params;
params.gen_collide_mesh = gen_collide_mesh;
params.texture_bucket = texture_bucket;
params.texture_level = texture_level;
@@ -72,7 +72,7 @@ int main(int argc, char** argv) {
jak1::run_build_actor(mdl_name, output_file, params);
} break;
case GameVersion::Jak2: {
jak2::BuildActorParams params;
jak2::BuildActorParams2 params;
params.gen_collide_mesh = gen_collide_mesh;
params.texture_bucket = texture_bucket;
params.texture_level = texture_level;
@@ -80,7 +80,7 @@ int main(int argc, char** argv) {
jak2::run_build_actor(mdl_name, output_file, params);
} break;
case GameVersion::Jak3: {
jak3::BuildActorParams params;
jak3::BuildActorParams3 params;
params.gen_collide_mesh = gen_collide_mesh;
params.texture_bucket = texture_bucket;
params.texture_level = texture_level;
+5 -1
View File
@@ -160,7 +160,11 @@ goos::Object MakeSystem::handle_defstep(const goos::Object& form,
if (in.is_pair()) {
step->input.clear();
goos::for_each_in_list(in, [&](const goos::Object& o) {
step->input.push_back(m_path_map.apply_remaps(o.as_string()->data));
if (o.type == goos::ObjectType::STRING) {
step->input.push_back(m_path_map.apply_remaps(o.as_string()->data));
} else {
step->input.push_back(m_path_map.apply_remaps(o.print()));
}
});
} else {
step->input = {m_path_map.apply_remaps(in.as_string()->data)};
+73 -15
View File
@@ -313,89 +313,147 @@ bool BuildLevel3Tool::run(const ToolInput& task, const PathMap& path_map) {
BuildActorTool::BuildActorTool() : Tool("build-actor") {}
bool BuildActorTool::needs_run(const ToolInput& task, const PathMap& path_map) {
if (task.input.size() > 4) {
if (task.input.size() > 8) {
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
}
auto rerun = task.input.at(2) == "#t";
std::vector<std::string> deps{task.input.at(0)};
std::vector deps{task.input.at(0)};
return rerun || Tool::needs_run({deps, deps, task.output, task.arg}, path_map);
}
bool BuildActorTool::run(const ToolInput& task, const PathMap& path_map) {
(void)path_map;
if (task.input.size() > 4) {
if (task.input.size() > 8) {
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
}
jak1::BuildActorParams params;
jak1::BuildActorParams1 params;
params.gen_collide_mesh = task.input.at(1) == "#t";
if (task.input.at(3) == "#f") {
params.texture_bucket = -1;
} else {
try {
params.texture_bucket = static_cast<u8>(std::stoi(task.input.at(3)));
params.texture_bucket = static_cast<s8>(std::stoi(task.input.at(3)));
} catch (std::invalid_argument&) {
throw std::runtime_error("[build-actor] texture-bucket must be #f or a valid integer.");
}
}
params.framerate = std::stof(task.input.at(4));
if (task.input.at(5) != "#f") {
params.master_art_group = task.input.at(5);
}
auto master_ag_list = m_reader.read_from_string(task.input.at(6));
// e.g. ((jakb-board-stance 180) (jakb-board-airwalk 181))
if (!master_ag_list.as_pair()->cdr.is_empty_list()) {
std::map<std::string, int> master_ag_map;
goos::for_each_in_list(master_ag_list.as_pair()->cdr.as_pair()->car,
[&](const goos::Object& o) {
auto map = o.as_pair();
auto ja = std::string(map->car.as_symbol().name_ptr);
auto idx = map->cdr.as_pair()->car.as_int();
master_ag_map.insert({ja, idx});
});
params.master_ag_map = master_ag_map;
}
if (task.input.at(7) != "6") {
params.joint_channel = std::stoi(task.input.at(7));
}
return jak1::run_build_actor(task.input.at(0), task.output.at(0), params);
}
BuildActor2Tool::BuildActor2Tool() : Tool("build-actor2") {}
bool BuildActor2Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
if (task.input.size() > 4) {
if (task.input.size() > 8) {
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
}
auto rerun = task.input.at(2) == "#t";
std::vector<std::string> deps{task.input.at(0)};
std::vector deps{task.input.at(0)};
return rerun || Tool::needs_run({deps, deps, task.output, task.arg}, path_map);
}
bool BuildActor2Tool::run(const ToolInput& task, const PathMap& path_map) {
(void)path_map;
if (task.input.size() > 4) {
if (task.input.size() > 8) {
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
}
jak2::BuildActorParams params;
jak2::BuildActorParams2 params;
params.gen_collide_mesh = task.input.at(1) == "#t";
if (task.input.at(3) == "#f") {
params.texture_bucket = -1;
} else {
try {
params.texture_bucket = static_cast<u8>(std::stoi(task.input.at(3)));
params.texture_bucket = static_cast<s8>(std::stoi(task.input.at(3)));
} catch (std::invalid_argument&) {
throw std::runtime_error("[build-actor2] texture-bucket must be #f or a valid integer.");
}
}
params.framerate = std::stof(task.input.at(4));
if (task.input.at(5) != "#f") {
params.master_art_group = task.input.at(5);
}
auto master_ag_list = m_reader.read_from_string(task.input.at(6));
if (!master_ag_list.as_pair()->cdr.is_empty_list()) {
std::map<std::string, int> master_ag_map;
goos::for_each_in_list(master_ag_list.as_pair()->cdr.as_pair()->car,
[&](const goos::Object& o) {
auto map = o.as_pair();
auto ja = std::string(map->car.as_symbol().name_ptr);
auto idx = map->cdr.as_pair()->car.as_int();
master_ag_map.insert({ja, idx});
});
params.master_ag_map = master_ag_map;
}
if (task.input.at(7) != "6") {
params.joint_channel = std::stoi(task.input.at(7));
}
return jak2::run_build_actor(task.input.at(0), task.output.at(0), params);
}
BuildActor3Tool::BuildActor3Tool() : Tool("build-actor3") {}
bool BuildActor3Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
if (task.input.size() > 4) {
if (task.input.size() > 8) {
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
}
auto rerun = task.input.at(2) == "#t";
std::vector<std::string> deps{task.input.at(0)};
std::vector deps{task.input.at(0)};
return rerun || Tool::needs_run({deps, deps, task.output, task.arg}, path_map);
}
bool BuildActor3Tool::run(const ToolInput& task, const PathMap& path_map) {
(void)path_map;
if (task.input.size() > 4) {
if (task.input.size() > 8) {
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
}
jak3::BuildActorParams params;
jak3::BuildActorParams3 params;
params.gen_collide_mesh = task.input.at(1) == "#t";
if (task.input.at(3) == "#f") {
params.texture_bucket = -1;
} else {
try {
params.texture_bucket = static_cast<u8>(std::stoi(task.input.at(3)));
params.texture_bucket = static_cast<s8>(std::stoi(task.input.at(3)));
} catch (std::invalid_argument&) {
throw std::runtime_error("[build-actor3] texture-bucket must be #f or a valid integer.");
}
}
params.framerate = std::stof(task.input.at(4));
if (task.input.at(5) != "#f") {
params.master_art_group = task.input.at(5);
}
auto master_ag_list = m_reader.read_from_string(task.input.at(6));
if (!master_ag_list.as_pair()->cdr.is_empty_list()) {
std::map<std::string, int> master_ag_map;
goos::for_each_in_list(master_ag_list.as_pair()->cdr.as_pair()->car,
[&](const goos::Object& o) {
auto map = o.as_pair();
auto ja = std::string(map->car.as_symbol().name_ptr);
auto idx = map->cdr.as_pair()->car.as_int();
master_ag_map.insert({ja, idx});
});
params.master_ag_map = master_ag_map;
}
if (task.input.at(7) != "6") {
params.joint_channel = std::stoi(task.input.at(7));
}
return jak3::run_build_actor(task.input.at(0), task.output.at(0), params);
}
+9
View File
@@ -98,6 +98,9 @@ class BuildActorTool : public Tool {
BuildActorTool();
bool run(const ToolInput& task, const PathMap& path_map) override;
bool needs_run(const ToolInput& task, const PathMap& path_map) override;
private:
goos::Reader m_reader;
};
class BuildActor2Tool : public Tool {
@@ -105,6 +108,9 @@ class BuildActor2Tool : public Tool {
BuildActor2Tool();
bool run(const ToolInput& task, const PathMap& path_map) override;
bool needs_run(const ToolInput& task, const PathMap& path_map) override;
private:
goos::Reader m_reader;
};
class BuildActor3Tool : public Tool {
@@ -112,4 +118,7 @@ class BuildActor3Tool : public Tool {
BuildActor3Tool();
bool run(const ToolInput& task, const PathMap& path_map) override;
bool needs_run(const ToolInput& task, const PathMap& path_map) override;
private:
goos::Reader m_reader;
};