mirror of
https://github.com/open-goal/jak-project
synced 2026-07-11 15:28:58 -04:00
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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user