diff --git a/.github/scripts/releases/extract_build_unix.sh b/.github/scripts/releases/extract_build_unix.sh index c3731f608b3..1c91f85f2c1 100755 --- a/.github/scripts/releases/extract_build_unix.sh +++ b/.github/scripts/releases/extract_build_unix.sh @@ -32,4 +32,4 @@ cp -r $SOURCE/decompiler/config $DEST/data/decompiler/ cp -r $SOURCE/goal_src $DEST/data cp -r $SOURCE/game/assets $DEST/data/game/ cp -r $SOURCE/game/graphics/opengl_renderer/shaders $DEST/data/game/graphics/opengl_renderer -cp -r $SOURCE/custom_levels $DEST/data +cp -r $SOURCE/custom_assets $DEST/data diff --git a/.github/scripts/releases/extract_build_windows.sh b/.github/scripts/releases/extract_build_windows.sh index 019f001b0c0..36c8ee804c7 100755 --- a/.github/scripts/releases/extract_build_windows.sh +++ b/.github/scripts/releases/extract_build_windows.sh @@ -24,4 +24,4 @@ cp -r $SOURCE/decompiler/config $DEST/data/decompiler/ cp -r $SOURCE/goal_src $DEST/data cp -r $SOURCE/game/assets $DEST/data/game/ cp -r $SOURCE/game/graphics/opengl_renderer/shaders $DEST/data/game/graphics/opengl_renderer -cp -r $SOURCE/custom_levels $DEST/data +cp -r $SOURCE/custom_assets $DEST/data diff --git a/.gitignore b/.gitignore index e2d7c668d47..8356b233040 100644 --- a/.gitignore +++ b/.gitignore @@ -54,7 +54,9 @@ imgui.ini node_modules/ # texture replacements -texture_replacements/* +custom_assets/jak1/texture_replacements/* +custom_assets/jak2/texture_replacements/* +custom_assets/jak3/texture_replacements/* # generated cmake files svnrev.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index fd61aaa1043..caba17a241a 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -87,9 +87,11 @@ add_library(common util/term_util.cpp util/Timer.cpp util/unicode_util.cpp - versions/versions.cpp) + util/gltf_util.cpp + versions/versions.cpp + ) -target_link_libraries(common fmt lzokay replxx libzstd_static tree-sitter sqlite3 libtinyfiledialogs) +target_link_libraries(common fmt lzokay replxx libzstd_static tree-sitter sqlite3 libtinyfiledialogs tiny_gltf) if(WIN32) target_link_libraries(common wsock32 ws2_32 windowsapp) diff --git a/common/util/gltf_util.cpp b/common/util/gltf_util.cpp new file mode 100644 index 00000000000..984c0a60712 --- /dev/null +++ b/common/util/gltf_util.cpp @@ -0,0 +1,467 @@ +#include "gltf_util.h" + +#include "common/log/log.h" + +namespace gltf_util { + +/*! + * Convert a GLTF position buffer or similar to std::vector + */ +std::vector extract_vec3f(const u8* data, u32 count, u32 stride) { + std::vector result; + result.reserve(count); + for (u32 i = 0; i < count; i++) { + memcpy(&result.emplace_back(), data, sizeof(math::Vector3f)); + data += stride; + } + return result; +} + +std::vector extract_vec2f(const u8* data, u32 count, u32 stride) { + std::vector result; + result.reserve(count); + for (u32 i = 0; i < count; i++) { + memcpy(&result.emplace_back(), data, sizeof(math::Vector2f)); + data += stride; + } + return result; +} + +/*! + * Convert a GLTF color buffer (float format) to u8 colors. + */ +std::vector> extract_color_from_vec4_float(const u8* data, + u32 count, + u32 stride) { + std::vector> result; + result.reserve(count); + for (u32 i = 0; i < count; i++) { + math::Vector temp; + memcpy(&temp, data, sizeof(math::Vector)); + data += stride; + result.emplace_back(temp.x() * 255, temp.y() * 255, temp.z() * 255, temp.w() * 255); + } + return result; +} + +/*! + * Convert a GLTF color buffer (u16 format) to u8 colors. + */ +std::vector> extract_color_from_vec4_u16(const u8* data, + u32 count, + u32 stride) { + std::vector> result; + result.reserve(count); + for (u32 i = 0; i < count; i++) { + math::Vector temp; + memcpy(&temp, data, sizeof(math::Vector)); + data += stride; + result.emplace_back(temp.x() >> 8, temp.y() >> 8, temp.z() >> 8, temp.w() >> 8); + } + return result; +} + +/*! + * Convert a GLTF index buffer + */ +std::vector gltf_index_buffer(const tinygltf::Model& model, + int indices_idx, + u32 index_offset) { + const auto& indices_accessor = model.accessors[indices_idx]; + const auto& buffer_view = model.bufferViews[indices_accessor.bufferView]; + const auto& buffer = model.buffers[buffer_view.buffer]; + const auto data_ptr = buffer.data.data() + buffer_view.byteOffset + indices_accessor.byteOffset; + const auto stride = indices_accessor.ByteStride(buffer_view); + const auto count = indices_accessor.count; + + switch (indices_accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_BYTE: + return index_list_to_u32(data_ptr, count, index_offset, stride); + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: + return index_list_to_u32(data_ptr, count, index_offset, stride); + case TINYGLTF_COMPONENT_TYPE_SHORT: + return index_list_to_u32(data_ptr, count, index_offset, stride); + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + return index_list_to_u32(data_ptr, count, index_offset, stride); + case TINYGLTF_COMPONENT_TYPE_INT: + return index_list_to_u32(data_ptr, count, index_offset, stride); + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: + return index_list_to_u32(data_ptr, count, index_offset, stride); + default: + ASSERT_MSG(false, "unsupported component type"); + } +} + +/*! + * Extract positions, colors, and normals from a mesh. + */ +ExtractedVertices gltf_vertices(const tinygltf::Model& model, + const std::map& attributes, + const math::Matrix4f& w_T_local, + bool get_colors, + bool get_normals, + const std::string& debug_name) { + std::vector result; + std::vector> vtx_colors; + + { + const auto& position_attrib = attributes.find("POSITION"); + ASSERT_MSG(position_attrib != attributes.end(), "Did not find position attribute."); + + const auto attrib_accessor = model.accessors[position_attrib->second]; + const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView]; + const auto& buffer = model.buffers[buffer_view.buffer]; + const auto data_ptr = buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset; + const auto byte_stride = attrib_accessor.ByteStride(buffer_view); + const auto count = attrib_accessor.count; + + ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC3, "POSITION wasn't vec3"); + ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT, + "POSITION wasn't float"); + // for (auto& attrib : attributes) { + // lg::print("attrib: {}\n", attrib.first); + //} + auto mesh_verts = extract_vec3f(data_ptr, count, byte_stride); + result.reserve(mesh_verts.size()); + for (auto& vert : mesh_verts) { + auto& new_vert = result.emplace_back(); + math::Vector4f v_in(vert.x(), vert.y(), vert.z(), 1); + math::Vector4f v_w = w_T_local * v_in; + new_vert.x = v_w.x() * 4096; + new_vert.y = v_w.y() * 4096; + new_vert.z = v_w.z() * 4096; + } + } + + if (get_colors) { + const auto& color_attrib = attributes.find("COLOR_0"); + if (color_attrib == attributes.end()) { + lg::error("Mesh {} didn't have any colors, using white", debug_name); + for (size_t i = 0; i < result.size(); i++) { + vtx_colors.emplace_back(0x80, 0x80, 0x80, 0xff); + } + } else { + const auto attrib_accessor = model.accessors[color_attrib->second]; + const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView]; + const auto& buffer = model.buffers[buffer_view.buffer]; + const auto data_ptr = + buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset; + const auto byte_stride = attrib_accessor.ByteStride(buffer_view); + const auto count = attrib_accessor.count; + + ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC4, "COLOR_0 wasn't vec4"); + std::vector> colors; + switch (attrib_accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_FLOAT: + colors = extract_color_from_vec4_float(data_ptr, count, byte_stride); + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + colors = extract_color_from_vec4_u16(data_ptr, count, byte_stride); + break; + default: + lg::die("Unknown component type for COLOR_0: {}", attrib_accessor.componentType); + } + vtx_colors.insert(vtx_colors.end(), colors.begin(), colors.end()); + } + } + + bool got_texture = false; + { + const auto& texcoord_attrib = attributes.find("TEXCOORD_0"); + if (texcoord_attrib != attributes.end()) { + const auto attrib_accessor = model.accessors[texcoord_attrib->second]; + const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView]; + const auto& buffer = model.buffers[buffer_view.buffer]; + const auto data_ptr = + buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset; + const auto byte_stride = attrib_accessor.ByteStride(buffer_view); + const auto count = attrib_accessor.count; + + ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC2, "TEXCOORD wasn't vec2"); + ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT, + "TEXCOORD wasn't float"); + auto mesh_verts = extract_vec2f(data_ptr, count, byte_stride); + ASSERT(mesh_verts.size() == result.size()); + got_texture = true; + for (size_t i = 0; i < mesh_verts.size(); i++) { + result[i].s = mesh_verts[i].x(); + result[i].t = mesh_verts[i].y(); + } + } else { + if (!get_normals) { + // don't warn if we're just getting collision + lg::warn("No texcoord attribute for mesh: {}", debug_name); + } + } + } + + std::vector normals; + if (get_normals) { + const auto& normal_attrib = attributes.find("NORMAL"); + if (normal_attrib != attributes.end()) { + const auto attrib_accessor = model.accessors[normal_attrib->second]; + const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView]; + const auto& buffer = model.buffers[buffer_view.buffer]; + const auto data_ptr = + buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset; + const auto byte_stride = attrib_accessor.ByteStride(buffer_view); + const auto count = attrib_accessor.count; + + ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC3, "NORMAL wasn't vec3"); + ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT, + "NORMAL wasn't float"); + normals = extract_vec3f(data_ptr, count, byte_stride); + for (auto& nrm : normals) { + math::Vector4f nrm4(nrm.x(), nrm.y(), nrm.z(), 0.f); + nrm = (w_T_local * nrm4).xyz(); + } + ASSERT(normals.size() == result.size()); + } else { + lg::error("No NORMAL attribute for mesh: {}", debug_name); + } + } + + for (auto& v : result) { + v.color_index = 0; + if (!got_texture) { + v.s = 0; + v.t = 0; + } + } + // TODO: other properties + return {result, vtx_colors, normals}; +} + +DrawMode make_default_draw_mode() { + DrawMode mode; + mode.set_depth_write_enable(true); + mode.set_depth_test(GsTest::ZTest::GEQUAL); + mode.set_alpha_blend(DrawMode::AlphaBlend::DISABLED); + mode.set_aref(0); + mode.set_alpha_fail(GsTest::AlphaFail::KEEP); + mode.set_clamp_s_enable(false); + mode.set_clamp_t_enable(false); + mode.disable_filt(); // for checkerboard... + mode.enable_tcc(); // ? + mode.disable_at(); + mode.enable_zt(); + mode.disable_ab(); + mode.disable_decal(); + mode.enable_fog(); + return mode; +} + +int texture_pool_debug_checker(TexturePool* pool) { + const auto& existing = pool->textures_by_name.find("DEBUG_CHECKERBOARD"); + if (existing == pool->textures_by_name.end()) { + size_t idx = pool->textures_by_idx.size(); + pool->textures_by_name["DEBUG_CHECKERBOARD"] = idx; + auto& tex = pool->textures_by_idx.emplace_back(); + tex.w = 16; + tex.h = 16; + tex.debug_name = "DEBUG_CHECKERBOARD"; + tex.debug_tpage_name = "DEBUG"; + tex.load_to_pool = false; + tex.combo_id = 0; // doesn't matter, not a pool tex + tex.data.resize(16 * 16); + u32 c0 = 0xa0303030; + u32 c1 = 0xa0e0e0e0; + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + tex.data[i * 16 + j] = (((i / 4) & 1) ^ ((j / 4) & 1)) ? c1 : c0; + } + } + return idx; + } else { + return existing->second; + } +} + +int texture_pool_add_texture(TexturePool* pool, const tinygltf::Image& tex) { + const auto& existing = pool->textures_by_name.find(tex.name); + if (existing != pool->textures_by_name.end()) { + lg::info("Reusing image: {}", tex.name); + return existing->second; + } else { + lg::info("adding new texture: {}, size {} kB", tex.name, tex.width * tex.height * 4 / 1024); + } + + ASSERT(tex.bits == 8); + ASSERT(tex.component == 4); + ASSERT(tex.pixel_type == TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE); + + size_t idx = pool->textures_by_idx.size(); + pool->textures_by_name[tex.name] = idx; + auto& tt = pool->textures_by_idx.emplace_back(); + tt.w = tex.width; + tt.h = tex.height; + tt.debug_name = tex.name; + tt.debug_tpage_name = "custom-level"; + tt.load_to_pool = false; + tt.combo_id = 0; // doesn't matter, not a pool tex + tt.data.resize(tt.w * tt.h); + ASSERT(tex.image.size() >= tt.data.size()); + memcpy(tt.data.data(), tex.image.data(), tt.data.size() * 4); + return idx; +} + +math::Matrix4f affine_translation(const math::Vector3f& translation) { + math::Matrix4f result = math::Matrix4f::identity(); + result(0, 3) = translation[0]; + result(1, 3) = translation[1]; + result(2, 3) = translation[2]; + result(3, 3) = 1; + return result; +} + +math::Matrix4f affine_scale(const math::Vector3f& scale) { + math::Matrix4f result = math::Matrix4f::zero(); + result(0, 0) = scale[0]; + result(1, 1) = scale[1]; + result(2, 2) = scale[2]; + result(3, 3) = 1; + return result; +} + +math::Matrix4f affine_rot_qxyzw(const math::Vector4f& quat) { + math::Matrix4f result = math::Matrix4f::zero(); + result(3, 3) = 1; + result(0, 0) = 1.0 - 2.0 * (quat.y() * quat.y() + quat.z() * quat.z()); + result(0, 1) = 2.0 * (quat.x() * quat.y() - quat.z() * quat.w()); + result(0, 2) = 2.0 * (quat.x() * quat.z() + quat.y() * quat.w()); + result(1, 0) = 2.0 * (quat.x() * quat.y() + quat.z() * quat.w()); + result(1, 1) = 1.0 - 2.0 * (quat.x() * quat.x() + quat.z() * quat.z()); + result(1, 2) = 2.0 * (quat.y() * quat.z() - quat.x() * quat.w()); + result(2, 0) = 2.0 * (quat.x() * quat.z() - quat.y() * quat.w()); + result(2, 1) = 2.0 * (quat.y() * quat.z() + quat.x() * quat.w()); + result(2, 2) = 1.0 - 2.0 * (quat.x() * quat.x() + quat.y() * quat.y()); + return result; +} + +math::Vector3f vector3f_from_gltf(const std::vector& in) { + ASSERT(in.size() == 3); + return math::Vector3f{in[0], in[1], in[2]}; +} + +math::Vector4f vector4f_from_gltf(const std::vector& in) { + ASSERT(in.size() == 4); + return math::Vector4f{in[0], in[1], in[2], in[3]}; +} + +math::Matrix4f matrix_from_node(const tinygltf::Node& node) { + if (!node.matrix.empty()) { + math::Matrix4f result; + for (int i = 0; i < 16; i++) { + result.data()[i] = node.matrix[i]; + } + return result; + } else { + // from trs + math::Matrix4f t, r, s; + if (!node.translation.empty()) { + t = affine_translation(vector3f_from_gltf(node.translation)); + } else { + t = math::Matrix4f::identity(); + } + + if (!node.rotation.empty()) { + r = affine_rot_qxyzw(vector4f_from_gltf(node.rotation)); + } else { + r = math::Matrix4f::identity(); + } + + if (!node.scale.empty()) { + s = affine_scale(vector3f_from_gltf(node.scale)); + } else { + s = math::Matrix4f::identity(); + } + + return t * r * s; + } +} + +/*! + * Recursively walk the tree of nodes, flatten, and compute w_T_node for each. + */ +void node_find_helper(const tinygltf::Model& model, + const math::Matrix4f& w_T_parent, + int node_idx, + std::vector* out) { + const auto& node = model.nodes.at(node_idx); + math::Matrix4f w_T_node = w_T_parent * matrix_from_node(node); + out->push_back({node_idx, w_T_node}); + for (auto& child : node.children) { + node_find_helper(model, w_T_node, child, out); + } +} + +std::vector flatten_nodes_from_all_scenes(const tinygltf::Model& model) { + std::vector out; + for (auto& scene : model.scenes) { + for (auto& nidx : scene.nodes) { + math::Matrix4f identity = math::Matrix4f::identity(); + node_find_helper(model, identity, nidx, &out); + } + } + return out; +} + +void dedup_vertices(const std::vector& vertices_in, + std::vector& vertices_out, + std::vector& old_to_new_out) { + ASSERT(vertices_out.empty()); + ASSERT(old_to_new_out.empty()); + old_to_new_out.resize(vertices_in.size(), -1); + + std::unordered_map vtx_to_new; + + for (size_t in_idx = 0; in_idx < vertices_in.size(); in_idx++) { + auto& vtx = vertices_in[in_idx]; + const auto& lookup = vtx_to_new.find(vtx); + if (lookup == vtx_to_new.end()) { + // first time seeing this one + size_t new_idx = vertices_out.size(); + vertices_out.push_back(vtx); + old_to_new_out[in_idx] = new_idx; + vtx_to_new[vtx] = new_idx; + } else { + old_to_new_out[in_idx] = lookup->second; + } + } +} + +DrawMode draw_mode_from_sampler(const tinygltf::Sampler& sampler) { + DrawMode mode = make_default_draw_mode(); + if (sampler.magFilter == TINYGLTF_TEXTURE_FILTER_NEAREST) { + ASSERT(sampler.minFilter == TINYGLTF_TEXTURE_FILTER_NEAREST); + mode.set_filt_enable(false); + } else { + ASSERT(sampler.minFilter != TINYGLTF_TEXTURE_FILTER_NEAREST); + mode.set_filt_enable(true); + } + + switch (sampler.wrapS) { + case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE: + mode.set_clamp_s_enable(true); + break; + case TINYGLTF_TEXTURE_WRAP_REPEAT: + mode.set_clamp_s_enable(false); + break; + default: + ASSERT(false); + } + + switch (sampler.wrapT) { + case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE: + mode.set_clamp_t_enable(true); + break; + case TINYGLTF_TEXTURE_WRAP_REPEAT: + mode.set_clamp_t_enable(false); + break; + default: + ASSERT(false); + } + + return mode; +} +} // namespace gltf_util \ No newline at end of file diff --git a/common/util/gltf_util.h b/common/util/gltf_util.h new file mode 100644 index 00000000000..71dcc47bfaf --- /dev/null +++ b/common/util/gltf_util.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include + +#include "common/common_types.h" +#include "common/custom_data/Tfrag3Data.h" +#include "common/math/Vector.h" + +#include "third-party/tiny_gltf/tiny_gltf.h" + +namespace gltf_util { + +/*! + * Convert a GLTF index buffer to std::vector + */ +template +std::vector index_list_to_u32(const u8* data, u32 num_verts, u32 offset, u32 stride) { + std::vector result; + result.reserve(num_verts); + for (u32 i = 0; i < num_verts; i++) { + T val; + memcpy(&val, data, sizeof(T)); + result.push_back(offset + val); + data += stride; + } + return result; +} + +std::vector extract_vec3f(const u8* data, u32 count, u32 stride); +std::vector extract_vec2f(const u8* data, u32 count, u32 stride); +std::vector> extract_color_from_vec4_u16(const u8* data, u32 count, u32 stride); +std::vector gltf_index_buffer(const tinygltf::Model& model, int indices_idx, u32 index_offset); + +struct ExtractedVertices { + std::vector vtx; + std::vector> vtx_colors; + std::vector normals; +}; + +ExtractedVertices gltf_vertices(const tinygltf::Model& model, + const std::map& attributes, + const math::Matrix4f& w_T_local, + bool get_colors, + bool get_normals, + const std::string& debug_name); +DrawMode make_default_draw_mode(); + +struct TexturePool { + std::unordered_map textures_by_name; + std::vector textures_by_idx; +}; + +int texture_pool_add_texture(TexturePool* pool, const tinygltf::Image& tex); +int texture_pool_debug_checker(TexturePool* pool); + +struct NodeWithTransform { + int node_idx; + math::Matrix4f w_T_node; +}; + +void dedup_vertices(const std::vector& vertices_in, + std::vector& vertices_out, + std::vector& old_to_new_out); + +std::vector flatten_nodes_from_all_scenes(const tinygltf::Model& model); + +DrawMode draw_mode_from_sampler(const tinygltf::Sampler& sampler); + +} // namespace gltf_util \ No newline at end of file diff --git a/custom_levels/README.md b/custom_assets/README.md similarity index 72% rename from custom_levels/README.md rename to custom_assets/README.md index 0a6a9aff043..21687081be5 100644 --- a/custom_levels/README.md +++ b/custom_assets/README.md @@ -5,13 +5,13 @@ Disclaimer: custom levels are still in development and are missing most features The first three steps are already done for "test zone", so this can be used as a starting point. # 1: File Setup -To create a custom level, copy the layout of `custom_levels/test-zone`. See `test-zone.jsonc` for information on how to name things. The `.gd` file also contains the level name. +To create a custom level, copy the layout of `custom_assets/jak1/levels/test-zone`. See `test-zone.jsonc` for information on how to name things. The `.gd` file also contains the level name. # 2: Modify the engine -Modify `goal_src/engine/level/level-info.gc` to add level info for each custom level. There is level info for `test-zone` at the bottom that can be used as an example. +Modify `goal_src/jak1/engine/level/level-info.gc` to add level info for each custom level. There is level info for `test-zone` at the bottom that can be used as an example. # 3: Modify the build system -Modify `goal_src/game.gp` and add a custom level target: +Modify `goal_src/jak1/game.gp` and add a custom level target: ```lisp (build-custom-level "test-zone") ;; the DGO file @@ -21,7 +21,7 @@ Modify `goal_src/game.gp` and add a custom level target: # 4: Export the GLTF file from blender. For now, all meshes are displayed and treated as ground collision. This causes buggy collision because walls shouldn't use "floor" mode. -Blender will create a `.glb` file, which must have the name specified in the `.jsonc` file and should be located in `custom_level/your_level` +Blender will create a `.glb` file, which must have the name specified in the `.jsonc` file and should be located in `custom_assets/jak1/levels/your_level` # 5: Rebuild the game Any time the `.glb` file is changed, you must rebuild the game. Launch the compiler (`goalc`) and run `(mi)` to rebuild everything. It's recommended to leave the compiler open - it will remember files that haven't changed and skip rebuilding them. diff --git a/custom_levels/blender/gltf2_blender_extract.py b/custom_assets/blender_plugins/gltf2_blender_extract.py similarity index 100% rename from custom_levels/blender/gltf2_blender_extract.py rename to custom_assets/blender_plugins/gltf2_blender_extract.py diff --git a/custom_levels/blender/opengoal.py b/custom_assets/blender_plugins/opengoal.py similarity index 100% rename from custom_levels/blender/opengoal.py rename to custom_assets/blender_plugins/opengoal.py diff --git a/custom_levels/jak1/test-zone/test-zone.jsonc b/custom_assets/jak1/levels/test-zone/test-zone.jsonc similarity index 81% rename from custom_levels/jak1/test-zone/test-zone.jsonc rename to custom_assets/jak1/levels/test-zone/test-zone.jsonc index d89c1f730d9..928a3f286b4 100644 --- a/custom_levels/jak1/test-zone/test-zone.jsonc +++ b/custom_assets/jak1/levels/test-zone/test-zone.jsonc @@ -10,7 +10,7 @@ // Must have vertex colors. Use the blender cycles renderer, bake, diffuse, uncheck color, // and bake to vertex colors. For now, only the first vertex color group is used, so make sure you // only have 1. - "gltf_file": "custom_levels/jak1/test-zone/test-zone2.glb", + "gltf_file": "custom_assets/jak1/levels/test-zone/test-zone2.glb", // automatically set wall vs. ground based on angle. Useful if you don't want to assign this yourself "automatic_wall_detection": true, @@ -55,20 +55,22 @@ // adds a 'symbol' tag (using the 'type' and 'string' lump types works the same way): // "symbol-list": ["symbol", "sym-1", "sym-2"] - // The base actor id for your custom level. If you have multiple levels this should be unique! - "base_id": 100, + // The base actor id for your custom level. If you have multiple levels, this should be unique! + "base_id": 100, // All art groups you want to use in your custom level. Will add their models and corresponding textures to the FR3 file. // Note: You will still have to add them to your level's .gd file. - // Removed so that the release builds don't have to double-decompile the game // "art_groups": ["plat-ag"], - "art_groups": [], - // Any textures you want to include in your custom level. This is mainly useful for things such as the zoomer HUD, - // which is not in the common level files and has no art group associated with it. - // To get a list of all the textures, you can extract all of the game's textures - // by setting "save_texture_pngs" to true in the decompiler config. - "textures": [ + // If you have any custom models in the "custom_assets/jak1/models" folder that you want to use in your level, add them to this list. + // Note: Like with art groups, these should also be added to your level's .gd file. + "custom_models": ["test-actor"], + + // Any textures you want to include in your custom level. This is mainly useful for things such as the zoomer HUD, + // which is not in the common level files and has no art group associated with it. + // To get a list of all the textures, you can extract all of the game's textures + // by setting "save_texture_pngs" to true in the decompiler config. + "textures": [ // all textures required for the zoomer HUD // "zoomerhud", // "zoomerhud-dial", @@ -130,6 +132,16 @@ "lump": { "name": "test-eco" } + }, + { + "trans": [-5.41, 3.5, 28.42], // translation + "etype": "test-actor", // actor type + "game_task": 0, // associated game task (for powercells, etc) + "quat": [0, 0, 0, 1], // quaternion + "bsphere": [-7.41, 3.5, 28.42, 10], // bounding sphere + "lump": { + "name": "test-actor" + } } // { // "trans": [-7.41, 3.5, 28.42], // translation diff --git a/custom_levels/jak1/test-zone/test-zone2.glb b/custom_assets/jak1/levels/test-zone/test-zone2.glb similarity index 100% rename from custom_levels/jak1/test-zone/test-zone2.glb rename to custom_assets/jak1/levels/test-zone/test-zone2.glb diff --git a/custom_levels/jak1/test-zone/testzone.gd b/custom_assets/jak1/levels/test-zone/testzone.gd similarity index 86% rename from custom_levels/jak1/test-zone/testzone.gd rename to custom_assets/jak1/levels/test-zone/testzone.gd index 778b875e078..8889f9bbc59 100644 --- a/custom_levels/jak1/test-zone/testzone.gd +++ b/custom_assets/jak1/levels/test-zone/testzone.gd @@ -4,6 +4,8 @@ ;; the actual file name still needs to be 8.3 ("TSZ.DGO" ("static-screen.o" - "test-zone.go" + "test-zone-obs.o" "plat-ag.go" + "test-actor-ag.go" + "test-zone.go" )) \ No newline at end of file diff --git a/custom_assets/jak1/models/test-actor.glb b/custom_assets/jak1/models/test-actor.glb new file mode 100644 index 00000000000..49bac261e1f Binary files /dev/null and b/custom_assets/jak1/models/test-actor.glb differ diff --git a/custom_levels/jak2/test-zone/test-zone.jsonc b/custom_assets/jak2/levels/test-zone/test-zone.jsonc similarity index 98% rename from custom_levels/jak2/test-zone/test-zone.jsonc rename to custom_assets/jak2/levels/test-zone/test-zone.jsonc index ae2bddbb198..56f08a3adc5 100644 --- a/custom_levels/jak2/test-zone/test-zone.jsonc +++ b/custom_assets/jak2/levels/test-zone/test-zone.jsonc @@ -10,7 +10,7 @@ // Must have vertex colors. Use the blender cycles renderer, bake, diffuse, uncheck color, // and bake to vertex colors. For now, only the first vertex color group is used, so make sure you // only have 1. - "gltf_file": "custom_levels/jak2/test-zone/test-zone2.glb", + "gltf_file": "custom_assets/jak2/levels/test-zone/test-zone2.glb", // automatically set wall vs. ground based on angle. Useful if you don't want to assign this yourself "automatic_wall_detection": true, diff --git a/custom_levels/jak2/test-zone/test-zone2.glb b/custom_assets/jak2/levels/test-zone/test-zone2.glb similarity index 100% rename from custom_levels/jak2/test-zone/test-zone2.glb rename to custom_assets/jak2/levels/test-zone/test-zone2.glb diff --git a/custom_levels/jak2/test-zone/testzone.gd b/custom_assets/jak2/levels/test-zone/testzone.gd similarity index 100% rename from custom_levels/jak2/test-zone/testzone.gd rename to custom_assets/jak2/levels/test-zone/testzone.gd diff --git a/custom_levels/jak3/test-zone/test-zone.jsonc b/custom_assets/jak3/levels/test-zone/test-zone.jsonc similarity index 98% rename from custom_levels/jak3/test-zone/test-zone.jsonc rename to custom_assets/jak3/levels/test-zone/test-zone.jsonc index 70e898342c3..1b4b9948dd4 100644 --- a/custom_levels/jak3/test-zone/test-zone.jsonc +++ b/custom_assets/jak3/levels/test-zone/test-zone.jsonc @@ -10,7 +10,7 @@ // Must have vertex colors. Use the blender cycles renderer, bake, diffuse, uncheck color, // and bake to vertex colors. For now, only the first vertex color group is used, so make sure you // only have 1. - "gltf_file": "custom_levels/jak3/test-zone/test-zone2.glb", + "gltf_file": "custom_assets/jak3/levels/test-zone/test-zone2.glb", // automatically set wall vs. ground based on angle. Useful if you don't want to assign this yourself "automatic_wall_detection": true, diff --git a/custom_levels/jak3/test-zone/test-zone2.glb b/custom_assets/jak3/levels/test-zone/test-zone2.glb similarity index 100% rename from custom_levels/jak3/test-zone/test-zone2.glb rename to custom_assets/jak3/levels/test-zone/test-zone2.glb diff --git a/custom_levels/jak3/test-zone/testzone.gd b/custom_assets/jak3/levels/test-zone/testzone.gd similarity index 100% rename from custom_levels/jak3/test-zone/testzone.gd rename to custom_assets/jak3/levels/test-zone/testzone.gd diff --git a/decompiler/extractor/main.cpp b/decompiler/extractor/main.cpp index 59ea6ec9b79..879671b8556 100644 --- a/decompiler/extractor/main.cpp +++ b/decompiler/extractor/main.cpp @@ -191,7 +191,8 @@ void decompile(const fs::path& iso_data_path, } // texture replacements - auto replacements_path = file_util::get_jak_project_dir() / "texture_replacements"; + auto replacements_path = file_util::get_jak_project_dir() / "custom_assets" / + game_version_names[config.game_version] / "texture_replacements"; if (fs::exists(replacements_path)) { tex_db.replace_textures(replacements_path); } diff --git a/decompiler/level_extractor/extract_level.cpp b/decompiler/level_extractor/extract_level.cpp index 38564ec68d6..42e341e65b9 100644 --- a/decompiler/level_extractor/extract_level.cpp +++ b/decompiler/level_extractor/extract_level.cpp @@ -19,6 +19,7 @@ #include "decompiler/level_extractor/extract_tfrag.h" #include "decompiler/level_extractor/extract_tie.h" #include "decompiler/level_extractor/fr3_to_gltf.h" +#include "goalc/build_actor/jak1/build_actor.h" namespace decompiler { diff --git a/decompiler/main.cpp b/decompiler/main.cpp index f0e4d7bfcda..5586970e038 100644 --- a/decompiler/main.cpp +++ b/decompiler/main.cpp @@ -322,7 +322,8 @@ int main(int argc, char** argv) { tex_db.merge_textures(texture_merge_path); } - auto replacements_path = file_util::get_jak_project_dir() / "texture_replacements"; + auto replacements_path = file_util::get_jak_project_dir() / "custom_assets" / + game_version_names[config.game_version] / "texture_replacements"; if (fs::exists(replacements_path)) { tex_db.replace_textures(replacements_path); } diff --git a/goal_src/jak1/game.gp b/goal_src/jak1/game.gp index 47559b79a3e..03785582cd7 100644 --- a/goal_src/jak1/game.gp +++ b/goal_src/jak1/game.gp @@ -153,10 +153,21 @@ ) ) +(defun custom-actor-cgo (output-name desc-file-name) + "Add a CGO with the given output name (in $OUT/iso) and input name (in custom_assets/jak1/models/)" + (let ((out-name (string-append "$OUT/iso/" output-name))) + (defstep :in (string-append "custom_assets/jak1/models/" desc-file-name) + :tool 'dgo + :out `(,out-name) + ) + (set! *all-cgos* (cons out-name *all-cgos*)) + ) + ) + (defun custom-level-cgo (output-name desc-file-name) - "Add a CGO with the given output name (in $OUT/iso) and input name (in custom_levels/jak1/)" + "Add a CGO with the given output name (in $OUT/iso) and input name (in custom_assets/jak1/levels/)" (let ((out-name (string-append "$OUT/iso/" output-name))) - (defstep :in (string-append "custom_levels/jak1/" desc-file-name) + (defstep :in (string-append "custom_assets/jak1/levels/" desc-file-name) :tool 'dgo :out `(,out-name) ) @@ -208,11 +219,16 @@ ) (defmacro build-custom-level (name) - (let* ((path (string-append "custom_levels/jak1/" name "/" name ".jsonc"))) + (let* ((path (string-append "custom_assets/jak1/levels/" name "/" name ".jsonc"))) `(defstep :in ,path :tool 'build-level :out '(,(string-append "$OUT/obj/" name ".go"))))) +(defmacro build-actor (name) + (let* ((path (string-append "custom_assets/jak1/models/" name ".glb"))) + `(defstep :in ,path + :tool 'build-actor + :out '(,(string-append "$OUT/obj/" name "-ag.go"))))) (defun copy-iso-file (name subdir ext) (let* ((path (string-append "$ISO/" subdir name ext)) @@ -1636,12 +1652,16 @@ ;;;;;;;;;;;;;;;;;;;;;;;;; ;; Set up the build system to build the level geometry -;; this path is relative to the custom_levels/jak1 folder +;; this path is relative to the custom_assets/jak1/levels/ folder ;; it should point to the .jsonc file that specifies the level. (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 +(build-actor "test-actor") + ;;;;;;;;;;;;;;;;;;;;; ;; Game Engine Code ;;;;;;;;;;;;;;;;;;;;; @@ -2080,6 +2100,7 @@ (goal-src "pc/debug/default-menu-pc.gc" "anim-tester-x" "part-tester" "entity-debug") (goal-src "pc/debug/pc-debug-common.gc" "pckernel-impl" "entity-h" "game-info-h" "level-h" "settings-h" "gsound-h" "target-util") (goal-src "pc/debug/pc-debug-methods.gc" "pc-debug-common") +(goal-src "levels/test-zone/test-zone-obs.gc" "process-drawable") (group-list "all-code" `(,@(reverse *all-gc*)) diff --git a/goal_src/jak1/levels/test-zone/test-zone-obs.gc b/goal_src/jak1/levels/test-zone/test-zone-obs.gc new file mode 100644 index 00000000000..6715ff573d4 --- /dev/null +++ b/goal_src/jak1/levels/test-zone/test-zone-obs.gc @@ -0,0 +1,123 @@ +;;-*-Lisp-*- +(in-package goal) + +(deftype test-actor (process-drawable) + ((root collide-shape-moving :override) + (birth-time time-frame) + (base vector :inline) + (old-base vector :inline) + (bob-offset int64) + (bob-amount float) + ) + (:methods (init-collision! (_type_) none)) + (:state-methods idle) + ) + +(def-art-elt test-actor-ag test-actor-lod0-jg 0) +(def-art-elt test-actor-ag test-actor-lod0-mg 1) +(def-art-elt test-actor-ag test-actor-idle-ja 2) + +(defskelgroup *test-actor-sg* test-actor test-actor-lod0-jg test-actor-idle-ja + ((test-actor-lod0-mg (meters 9999999))) + :bounds (static-spherem 0 0 0 4.5) + :texture-level 2 + ) + +(defmethod init-collision! ((this test-actor)) + (let ((cshape (new 'process 'collide-shape-moving this (collide-list-enum hit-by-player)))) + (set! (-> cshape dynam) (copy *standard-dynamics* 'process)) + (set! (-> cshape reaction) default-collision-reaction) + (set! (-> cshape no-reaction) + (the (function collide-shape-moving collide-shape-intersect vector vector none) nothing) + ) + ;; (let ((mesh (new 'process 'collide-shape-prim-mesh cshape (the uint 0) (the uint 0)))) + ;; (set! (-> mesh prim-core collide-as) (collide-kind enemy)) + ;; (set! (-> mesh collide-with) (collide-kind target)) + ;; (set! (-> mesh transform-index) 6) + ;; (set-vector! (-> mesh local-sphere) 0.0 0.0 0.0 (meters 4.5)) + ;; (set-root-prim! cshape mesh) + ;; ) + (let ((sphere (new 'process 'collide-shape-prim-sphere cshape (the uint 0)))) + (set! (-> sphere prim-core collide-as) (collide-kind enemy)) + (set! (-> sphere collide-with) (collide-kind target)) + (set! (-> sphere prim-core action) (collide-action solid)) + (set! (-> sphere prim-core offense) (collide-offense normal-attack)) + (set-vector! (-> sphere local-sphere) 0.0 0.0 0.0 (meters 2)) + (set-root-prim! cshape sphere) + ) + (set! (-> cshape nav-radius) (* 0.75 (-> cshape root-prim local-sphere w))) + (backup-collide-with-as cshape) + (set! (-> this root) cshape) + ) + (none) + ) + +(defmethod init-from-entity! ((this test-actor) (e entity-actor)) + (logior! (-> this mask) (process-mask enemy)) + (init-collision! this) + (process-drawable-from-entity! this e) + (set! (-> this bob-amount) 1024.0) + (set! (-> this bob-offset) (+ (the int (-> this root trans x)) (the int (-> this root trans y)) (the int (-> this root trans z)))) + (set-time! (-> this birth-time)) + (vector-copy! (-> this base) (-> this root trans)) + (vector-copy! (-> this old-base) (-> this root trans)) + (initialize-skeleton this *test-actor-sg* '()) + (logclear! (-> this mask) (process-mask actor-pause)) + (transform-post) + (go-virtual idle :proc this) + (none) + ) + + +(defbehavior test-actor-init-by-other test-actor ((pos vector)) + (logior! (-> self mask) (process-mask enemy)) + (init-collision! self) + (initialize-skeleton self *test-actor-sg* '()) + (vector-copy! (-> self root trans) pos) + (quaternion-identity! (-> self root quat)) + (vector-identity! (-> self root scale)) + (set! (-> self bob-amount) 1024.0) + (set! (-> self bob-offset) (+ (the int (-> self root trans x)) (the int (-> self root trans y)) (the int (-> self root trans z)))) + (set-time! (-> self birth-time)) + (vector-copy! (-> self base) (-> self root trans)) + (vector-copy! (-> self old-base) (-> self root trans)) + (logclear! (-> self mask) (process-mask actor-pause)) + (transform-post) + (go-virtual idle) + ) + +(defstate idle (test-actor) + :virtual #t + :event (behavior ((proc process) (argc int) (message symbol) (block event-message-block)) + (case message + (('attack 'touch) + (if (= (-> proc type) target) + (send-event proc 'attack #f (static-attack-info ((shove-up (meters 2.5)) (shove-back (meters 7.5))))) + ) + ) + ) + ) + :code (behavior () + (loop + (quaternion-rotate-y! (-> self root quat) (-> self root quat) (* (degrees 45) (seconds-per-frame))) + (let ((bob (-> self bob-amount))) + (when (< 0.0 bob) + (set! (-> self root trans y) + (+ (-> self base y) + (* bob (sin (* 109.22667 (the float (mod (+ (- (current-time) (-> self birth-time)) (-> self bob-offset)) (seconds 2)))))) + ) + ) + (update-transforms! (-> self root)) + ) + ) + ; (dotimes (i (-> self node-list length)) + ; (let* ((joint (-> self node-list data i)) (jpos (vector<-cspace! (new-stack-vector0) joint))) + ; (add-debug-sphere #t (bucket-id debug) jpos (meters 0.1) (static-rgba 0 #xff 0 #x40)) + ; (add-debug-text-sphere (!= (-> joint joint) #f) (bucket-id debug) jpos (meters 0.1) (-> joint joint name) (static-rgba 0 #xff 0 #x40)) + ; ) + ; ) + (suspend) + ) + ) + :post transform-post + ) \ No newline at end of file diff --git a/goal_src/jak2/game.gp b/goal_src/jak2/game.gp index 29f3973ffcc..4c3a75eadd2 100644 --- a/goal_src/jak2/game.gp +++ b/goal_src/jak2/game.gp @@ -296,7 +296,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;; ;; Set up the build system to build the level geometry -;; this path is relative to the custom_levels/jak2 folder +;; this path is relative to the custom_assets/jak2/levels folder ;; it should point to the .jsonc file that specifies the level. (build-custom-level "test-zone") ;; the DGO file diff --git a/goal_src/jak2/lib/project-lib.gp b/goal_src/jak2/lib/project-lib.gp index 1ace9a754ed..07ca1a63d97 100644 --- a/goal_src/jak2/lib/project-lib.gp +++ b/goal_src/jak2/lib/project-lib.gp @@ -84,9 +84,9 @@ ) (defun custom-level-cgo (output-name desc-file-name) - "Add a CGO with the given output name (in $OUT/iso) and input name (in custom_levels/jak2/)" + "Add a CGO with the given output name (in $OUT/iso) and input name (in custom_assets/jak2/levels/)" (let ((out-name (string-append "$OUT/iso/" output-name))) - (defstep :in (string-append "custom_levels/jak2/" desc-file-name) + (defstep :in (string-append "custom_assets/jak2/levels/" desc-file-name) :tool 'dgo :out `(,out-name) ) @@ -136,7 +136,7 @@ ) (defmacro build-custom-level (name) - (let* ((path (string-append "custom_levels/jak2/" name "/" name ".jsonc"))) + (let* ((path (string-append "custom_assets/jak2/levels/" name "/" name ".jsonc"))) `(defstep :in ,path :tool 'build-level2 :out '(,(string-append "$OUT/obj/" name ".go"))))) diff --git a/goal_src/jak3/lib/project-lib.gp b/goal_src/jak3/lib/project-lib.gp index d9549b9411c..087f583dea7 100644 --- a/goal_src/jak3/lib/project-lib.gp +++ b/goal_src/jak3/lib/project-lib.gp @@ -237,9 +237,9 @@ )) (defun custom-level-cgo (output-name desc-file-name) - "Add a CGO with the given output name (in $OUT/iso) and input name (in custom_levels/jak3/)" + "Add a CGO with the given output name (in $OUT/iso) and input name (in custom_assets/jak3/levels/)" (let ((out-name (string-append "$OUT/iso/" output-name))) - (defstep :in (string-append "custom_levels/jak3/" desc-file-name) + (defstep :in (string-append "custom_assets/jak3/levels/" desc-file-name) :tool 'dgo :out `(,out-name) ) @@ -248,7 +248,7 @@ ) (defmacro build-custom-level (name) - (let* ((path (string-append "custom_levels/jak3/" name "/" name ".jsonc"))) + (let* ((path (string-append "custom_assets/jak3/levels/" name "/" name ".jsonc"))) `(defstep :in ,path :tool 'build-level3 :out '(,(string-append "$OUT/obj/" name ".go"))))) \ No newline at end of file diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 9a3d0be8809..81964a43f5a 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -6,6 +6,7 @@ add_library(compiler emitter/Register.cpp debugger/disassemble.cpp build_level/common/build_level.cpp + build_actor/common/MercExtract.cpp build_level/jak1/build_level.cpp build_level/jak2/build_level.cpp build_level/jak3/build_level.cpp @@ -57,6 +58,7 @@ add_library(compiler data_compiler/dir_tpages.cpp data_compiler/game_count.cpp data_compiler/DataObjectGenerator.cpp + build_actor/jak1/build_actor.cpp debugger/Debugger.cpp debugger/DebugInfo.cpp listener/Listener.cpp @@ -80,8 +82,9 @@ add_executable(goalc main.cpp) add_executable(goalc-simple simple_main.cpp) add_executable(build_level build_level/main.cpp) +add_executable(build_actor build_actor/main.cpp) target_link_libraries(goalc common Zydis compiler) target_link_libraries(goalc-simple common Zydis compiler) target_link_libraries(build_level common Zydis compiler) - +target_link_libraries(build_actor common Zydis compiler) diff --git a/goalc/build_actor/common/MercExtract.cpp b/goalc/build_actor/common/MercExtract.cpp new file mode 100644 index 00000000000..bb49f7db580 --- /dev/null +++ b/goalc/build_actor/common/MercExtract.cpp @@ -0,0 +1,155 @@ +#include "MercExtract.h" + +#include "common/log/log.h" + +void extract(const std::string& name, + MercExtractData& out, + const tinygltf::Model& model, + const std::vector& all_nodes, + u32 index_offset, + u32 vertex_offset, + u32 tex_offset) { + ASSERT(out.new_vertices.empty()); + + std::map draw_by_material; + int mesh_count = 0; + int prim_count = 0; + + for (const auto& n : all_nodes) { + const auto& node = model.nodes[n.node_idx]; + if (node.mesh >= 0) { + const auto& mesh = model.meshes[node.mesh]; + mesh_count++; + for (const auto& prim : mesh.primitives) { + prim_count++; + // extract index buffer + std::vector prim_indices = gltf_util::gltf_index_buffer( + model, prim.indices, out.new_vertices.size() + vertex_offset); + ASSERT_MSG(prim.mode == TINYGLTF_MODE_TRIANGLES, "Unsupported triangle mode"); + // extract vertices + auto verts = + gltf_util::gltf_vertices(model, prim.attributes, n.w_T_node, true, true, mesh.name); + out.new_vertices.insert(out.new_vertices.end(), verts.vtx.begin(), verts.vtx.end()); + out.new_colors.insert(out.new_colors.end(), verts.vtx_colors.begin(), + verts.vtx_colors.end()); + out.normals.insert(out.normals.end(), verts.normals.begin(), verts.normals.end()); + ASSERT(out.new_colors.size() == out.new_vertices.size()); + + // TODO: just putting it all in one material + auto& draw = draw_by_material[prim.material]; + draw.mode = gltf_util::make_default_draw_mode(); // todo rm + draw.tree_tex_id = 0; // todo rm + draw.num_triangles += prim_indices.size() / 3; + // if (draw.vis_groups.empty()) { + // auto& grp = draw.vis_groups.emplace_back(); + // grp.num_inds += prim_indices.size(); + // grp.num_tris += draw.num_triangles; + // grp.vis_idx_in_pc_bvh = UINT32_MAX; + // } else { + // auto& grp = draw.vis_groups.back(); + // grp.num_inds += prim_indices.size(); + // grp.num_tris += draw.num_triangles; + // grp.vis_idx_in_pc_bvh = UINT32_MAX; + // } + draw.index_count = prim_indices.size(); + draw.first_index = index_offset + out.new_indices.size(); + + out.new_indices.insert(out.new_indices.end(), prim_indices.begin(), prim_indices.end()); + } + } + } + + tfrag3::MercEffect e; + out.new_model.name = name; + out.new_model.max_bones = 120; // idk + out.new_model.max_draws = 200; + for (const auto& [mat_idx, d_] : draw_by_material) { + e.all_draws.push_back(d_); + auto& draw = e.all_draws.back(); + draw.mode = gltf_util::make_default_draw_mode(); + + if (mat_idx == -1) { + lg::warn("Draw had a material index of -1, using default texture."); + draw.tree_tex_id = 0; + continue; + } + const auto& mat = model.materials[mat_idx]; + int tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index; + if (tex_idx == -1) { + lg::warn("Material {} has no texture, using default texture.", mat.name); + draw.tree_tex_id = 0; + continue; + } + + const auto& tex = model.textures[tex_idx]; + ASSERT(tex.sampler >= 0); + ASSERT(tex.source >= 0); + draw.mode = gltf_util::draw_mode_from_sampler(model.samplers.at(tex.sampler)); + + const auto& img = model.images[tex.source]; + draw.tree_tex_id = tex_offset + texture_pool_add_texture(&out.tex_pool, img); + } + lg::info("total of {} unique materials", e.all_draws.size()); + out.new_model.effects.push_back(e); + out.new_model.effects.push_back(e); + out.new_model.effects.push_back(e); + out.new_model.effects.push_back(e); + + lg::info("Merged {} meshes and {} prims into {} vertices", mesh_count, prim_count, + out.new_vertices.size()); +} + +void merc_convert(MercSwapData& out, const MercExtractData& in) { + // easy + out.new_model = in.new_model; + out.new_indices = in.new_indices; + out.new_textures = in.tex_pool.textures_by_idx; + + // convert vertices + for (size_t i = 0; i < in.new_vertices.size(); i++) { + const auto& y = in.new_vertices[i]; + auto& x = out.new_vertices.emplace_back(); + x.pos[0] = y.x; + x.pos[1] = y.y; + x.pos[2] = y.z; + x.normal[0] = in.normals.at(i).x(); + x.normal[1] = in.normals.at(i).y(); + x.normal[2] = in.normals.at(i).z(); + x.weights[0] = 1.0f; + x.weights[1] = 0.0f; + x.weights[2] = 0.0f; + x.st[0] = y.s; + x.st[1] = y.t; + x.rgba[0] = in.new_colors[i][0]; + x.rgba[1] = in.new_colors[i][1]; + x.rgba[2] = in.new_colors[i][2]; + x.rgba[3] = in.new_colors[i][3]; + x.mats[0] = 3; + x.mats[1] = 0; + x.mats[2] = 0; + } +} + +MercSwapData load_merc_model(u32 current_idx_count, + u32 current_vtx_count, + u32 current_tex_count, + const std::string& path, + const std::string& name) { + MercSwapData result; + lg::info("Reading gltf mesh: {}", path); + tinygltf::TinyGLTF loader; + tinygltf::Model model; + std::string err, warn; + bool res = loader.LoadBinaryFromFile(&model, &err, &warn, path); + ASSERT_MSG(warn.empty(), warn.c_str()); + ASSERT_MSG(err.empty(), err.c_str()); + ASSERT_MSG(res, "Failed to load GLTF file!"); + auto all_nodes = gltf_util::flatten_nodes_from_all_scenes(model); + + MercExtractData extract_data; + extract(name, extract_data, model, all_nodes, current_idx_count, current_vtx_count, + current_tex_count); + merc_convert(result, extract_data); + + return result; +} \ No newline at end of file diff --git a/goalc/build_actor/common/MercExtract.h b/goalc/build_actor/common/MercExtract.h new file mode 100644 index 00000000000..302abdfc525 --- /dev/null +++ b/goalc/build_actor/common/MercExtract.h @@ -0,0 +1,35 @@ +#pragma once + +#include "common/util/gltf_util.h" + +struct MercExtractData { + gltf_util::TexturePool tex_pool; + std::vector new_indices; + std::vector new_vertices; + std::vector> new_colors; + std::vector normals; + + tfrag3::MercModel new_model; +}; + +// Data produced by loading a replacement model +struct MercSwapData { + std::vector new_indices; + std::vector new_vertices; + std::vector new_textures; + tfrag3::MercModel new_model; +}; + +void extract(const std::string& name, + MercExtractData& out, + const tinygltf::Model& model, + const std::vector& all_nodes, + u32 index_offset, + u32 vertex_offset, + u32 tex_offset); +void merc_convert(MercSwapData& out, const MercExtractData& in); +MercSwapData load_merc_model(u32 current_idx_count, + u32 current_vtx_count, + u32 current_tex_count, + const std::string& path, + const std::string& name); \ No newline at end of file diff --git a/goalc/build_actor/common/art_types.h b/goalc/build_actor/common/art_types.h new file mode 100644 index 00000000000..8bd524051b3 --- /dev/null +++ b/goalc/build_actor/common/art_types.h @@ -0,0 +1,33 @@ +#pragma once + +#include "common/custom_data/Tfrag3Data.h" + +#include "decompiler/level_extractor/MercData.h" +#include "decompiler/level_extractor/common_formats.h" +#include "goalc/build_level/common/Entity.h" +#include "goalc/build_level/common/FileInfo.h" +#include "goalc/data_compiler/DataObjectGenerator.h" + +struct Art { + std::string name; + s32 length; + ResLump lump; +}; + +struct ArtElement : Art { + u8 pad[12]; +}; + +struct MercEyeAnimFrame { + s8 pupil_trans_x; + s8 pupil_trans_y; + s8 blink; + s8 iris_scale; + s8 pupil_scale; + s8 lid_scale; +}; + +struct MercEyeAnimBlock { + s16 max_frame; + std::vector frames; +}; \ No newline at end of file diff --git a/goalc/build_actor/jak1/build_actor.cpp b/goalc/build_actor/jak1/build_actor.cpp new file mode 100644 index 00000000000..b58fdd0e008 --- /dev/null +++ b/goalc/build_actor/jak1/build_actor.cpp @@ -0,0 +1,445 @@ +#include "build_actor.h" + +#include "common/log/log.h" + +#include "third-party/tiny_gltf/tiny_gltf.h" + +using namespace gltf_util; +namespace jak1 { + +std::map g_joint_map; + +size_t Joint::generate(DataObjectGenerator& gen) const { + gen.align_to_basic(); + gen.add_type_tag("joint"); + size_t result = gen.current_offset_bytes(); + gen.add_ref_to_string_in_pool(name); + gen.add_word(number); + if (parent == -1) { + gen.add_symbol_link("#f"); + } else { + gen.link_word_to_byte(gen.add_word(0), g_joint_map[parent]); + } + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + gen.add_word_float(bind_pose(i, j)); + } + } + return result; +} + +size_t JointAnimCompressed::generate(DataObjectGenerator& gen) const { + gen.align_to_basic(); + gen.add_type_tag("joint-anim-compressed"); + size_t result = gen.current_offset_bytes(); + gen.add_ref_to_string_in_pool(name); + gen.add_word((length << 16) + number); + // for (auto& word : data) { + // gen.add_word(word); + // } + return result; +} + +size_t JointAnimCompressedFrame::generate(DataObjectGenerator& gen) const { + size_t result = gen.current_offset_bytes(); + gen.add_word(offset_64); // 0 + gen.add_word(offset_32); // 4 + gen.add_word(offset_16); // 8 + gen.add_word(reserved); // 12 + gen.align(4); + return result; +} + +size_t JointAnimCompressedHDR::generate(DataObjectGenerator& gen) const { + size_t result = gen.current_offset_bytes(); + for (auto& bit : control_bits) { + gen.add_word(bit); + } + gen.add_word(num_joints); + gen.add_word(matrix_bits); + gen.align(4); + return result; +} + +size_t JointAnimCompressedFixed::generate(DataObjectGenerator& gen) const { + size_t result = gen.current_offset_bytes(); + hdr.generate(gen); // 0-64 (inline) + gen.add_word(offset_64); // 64 + gen.add_word(offset_32); // 68 + gen.add_word(offset_16); // 72 + gen.add_word(reserved); // 76 + // default joint poses (taken from money-idle) + for (size_t i = 0; i < 8; i++) { + gen.add_word_float(data[i].x()); + gen.add_word_float(data[i].y()); + gen.add_word_float(data[i].z()); + gen.add_word_float(data[i].w()); + } + gen.add_word(0); + gen.add_word(0x7fff0000); + gen.add_word(0x2250000); + gen.add_word(0x10001000); + gen.add_word(0x10000000); + gen.add_word(0); + gen.add_word(0); + gen.add_word(0); + gen.align(4); + return result; +} + +size_t JointAnimCompressedControl::generate(DataObjectGenerator& gen) const { + size_t result = gen.current_offset_bytes(); + gen.add_word(num_frames); // 0 + gen.add_word(fixed_qwc); // 4 + gen.add_word(frame_qwc); // 8 + + auto ja_fixed_slot = gen.add_word(0); + auto ja_frame_slot = gen.add_word(0); + gen.align(4); + gen.link_word_to_byte(ja_fixed_slot, fixed.generate(gen)); + gen.link_word_to_byte(ja_frame_slot, frame[0].generate(gen)); + return result; +} + +size_t ArtJointGeo::generate_res_lump(DataObjectGenerator& gen) const { + gen.align_to_basic(); + gen.add_type_tag("res-lump"); + size_t result = gen.current_offset_bytes(); + return result; +} + +size_t ArtJointGeo::generate(DataObjectGenerator& gen) const { + gen.align_to_basic(); + gen.add_type_tag("art-joint-geo"); + size_t result = gen.current_offset_bytes(); + gen.add_word(0); // 4 + gen.add_ref_to_string_in_pool(name); // 8 + gen.add_word(length); // 12 + auto res_slot = gen.add_word(0); // 16 (res-lump) + gen.add_word(0); // 20 + gen.add_word(0); + gen.add_word(0); + std::vector joint_slots; + for (size_t i = 0; i < length; i++) { + joint_slots.push_back(gen.add_word(0)); + } + gen.align(4); + for (size_t i = 0; i < length; i++) { + auto joint = data.at(i).generate(gen); + gen.link_word_to_byte(joint_slots.at(i), joint); + g_joint_map[data.at(i).number] = joint; + } + auto res_header = lump.generate_header(gen, "res-lump"); + gen.link_word_to_byte(res_slot, res_header); + lump.generate_tag_list_and_data(gen, res_header); + return result; +} + +size_t ArtJointAnim::generate(DataObjectGenerator& gen) const { + gen.align_to_basic(); + gen.add_type_tag("art-joint-anim"); + size_t result = gen.current_offset_bytes(); + gen.add_symbol_link("#f"); // 4 (eye block) + gen.add_ref_to_string_in_pool(name); // 8 + gen.add_word(length); // 12 + gen.add_symbol_link("#f"); // 16 (res-lump) + gen.add_word_float(speed); // 20 + gen.add_word_float(artist_base); // 24 + gen.add_word_float(artist_step); // 28 + gen.add_ref_to_string_in_pool(master_art_group_name); // 32 + gen.add_word(master_art_group_index); // 36 + gen.add_symbol_link("#f"); // 40 (blerc) + auto ctrl_slot = gen.add_word(0); + std::vector frame_slots; + for (size_t i = 0; i < length; i++) { + frame_slots.push_back(gen.add_word(0)); + } + gen.align(4); + gen.link_word_to_byte(ctrl_slot, frames.generate(gen)); + for (size_t i = 0; i < length; i++) { + gen.link_word_to_byte(frame_slots.at(i), data.at(i).generate(gen)); + } + return result; +} + +static size_t gen_dummy_frag_geo(DataObjectGenerator& gen) { + size_t result = gen.current_offset_bytes(); + // frag geo stolen from money-lod0 + static std::vector words = { + 0xa4320c04, 0x8000026, 0x302a0000, 0x604, 0xa910, 0xac16, 0x100af1c, + 0xb23d, 0x100b585, 0x100b870, 0xbb76, 0xbe52, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x0, 0x0, 0x89b78086, 0xf1a910a, 0x3a4b7000, 0x5f818086, + 0xf1a1094, 0x29347014, 0x49648186, 0x91313, 0x4e5d8024, 0x354b8186, 0xf1a6416, + 0x3a497024, 0x40538186, 0x91919, 0x647b8034, 0x24348186, 0xf1a371c, 0x647f7034, + 0x495d8186, 0x91f1f, 0x7a9c8044, 0x35498086, 0xf1a2231, 0x8eb57044, 0x5f7b8186, + 0x92525, 0x83ad8054, 0x5f7f8186, 0xf1a2828, 0x9fcc7054, 0x759c8186, 0x92b2b, + 0x7aa38064, 0x1c257f86, 0x273b2e2e, 0x94b86040, 0xe198186, 0x2737d034, 0x71936038, + 0xe188186, 0x273bcd3a, 0x57676030, 0x1c2a8186, 0x2737613d, 0x34456028, 0xe1b8086, + 0x485d40c7, 0x2e3c5028, 0x293c8186, 0x485d5b43, 0x131a5020, 0x26398186, 0x6e804646, + 0xf164020, 0x4b688186, 0x6e805549, 0x34018, 0x4c688186, 0x979f4c4c, 0x5073018, + 0x72978086, 0x979f4fc1, 0x5073010, 0x73988086, 0x6e807352, 0x34010, 0x4c698186, + 0x485d6d58, 0x5085018, 0x2f488086, 0x273b5e67, 0x21256020, 0x526d8186, 0x2737a66a, + 0x13196018, 0x72988186, 0x485da070, 0x5085010, 0x98c78086, 0x6e80767f, 0xf164008, + 0x95c48186, 0x979fc479, 0x13193008, 0xb0e68186, 0x979f7c7c, 0x2e3b3000, 0xb4ea8186, + 0x6e808282, 0x2b394000, 0x95c48186, 0x485d9d85, 0x131b5008, 0xb0e68186, 0x485d8888, + 0x2e3c5000, 0x8fbb8186, 0x2737978b, 0x212a6008, 0xa2db8186, 0x273b8e8e, 0x34486000, + 0x6c998086, 0x273b9aa3, 0x13186010, 0x87f86, 0x485dcaca, 0x51685030, 0x75a37f86, + 0x90707, 0x4e648000, 0x5f858186, 0x90d0d, 0x45538014, 0x0, 0x0, + 0xcb01005f, 0xcb00fffa, 0xcb010064, 0x5101e01, 0x0, 0x0, 0x306, + 0x60030000, 0x120, 0x0, 0x1cf02c14, 0x2008044, 0x0, 0x0, + 0x34, 0x81010000, 0x0, 0x0, 0x8, 0x6d100000, 0x44, + 0x80, 0x42, 0x30000, 0x86321604, 0x400001c, 0x2422440e, 0x4, + 0x1004f28, 0x1008b4c, 0xb225, 0xca4c, 0x1000128, 0x1000422, 0x1000a2e, + 0x10064ca, 0x6a34, 0x1006d2e, 0x70ca, 0x1007340, 0x7946, 0x7f4c, + 0x100884c, 0x8e4f, 0x9479, 0x9a7c, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x0, 0x0, 0x131a8086, + 0x215d0d9d, 0x87c45040, 0x38186, 0x4780a313, 0x65984038, 0x38186, 0x47806116, + 0x3d684030, 0x5078186, 0x709fa919, 0x3e693030, 0x13198186, 0x709f5b1c, 0x1b3c3028, + 0x21208186, 0x96bbaf1f, 0x21452028, 0x34428186, 0x96bf5522, 0xe242020, 0x3a408086, + 0xb6da252e, 0x27411024, 0x647f8186, 0xb6da4628, 0x16261014, 0x647a8186, 0xcbf1402b, + 0x32450014, 0x4e528086, 0xcbf1313a, 0x3b5b0024, 0x29268086, 0xb6da34b5, 0x51811034, + 0x45458186, 0xcbf13737, 0x51860034, 0x64808186, 0xd3ff3d3d, 0x51800040, 0x7aa58186, + 0xcbf14343, 0x3b520000, 0x8ebf8186, 0xb6dac749, 0x27401000, 0x71948186, 0x96bf854c, + 0x132010, 0x57668186, 0x96bb8252, 0x122018, 0x2e3b8186, 0x709f7c58, 0x1a3020, + 0xf168186, 0x4780765e, 0x18394028, 0x94bb8086, 0x96bb91c4, 0xe202008, 0xa7dc8086, + 0x96bf97c1, 0x21422000, 0xf167f86, 0x4780a0a0, 0x8ac74040, 0x5078186, 0x709fbea6, + 0x64983038, 0x13138186, 0x96bfb8ac, 0x446c2030, 0x13128186, 0x96bbbbbb, 0x5e9a2038, + 0x34458186, 0x370707, 0x94d66048, 0x5088186, 0x215d6710, 0x64975038, 0xcb010064, + 0xcb00ffd3, 0xcb010051, 0x5021000, 0x4088003, 0x10306060, 0x3, 0x0, + 0x8d301104, 0x300001f, 0x2624430a, 0x4, 0x910a, 0x100a928, 0xc42b, + 0x1006aa0, 0x70a6, 0x73bb, 0x9a07, 0xa00d, 0x100a3a0, 0x100a6bb, + 0xac34, 0xb237, 0xb83d, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x8ccc8186, + 0xf1a4c07, 0x16817074, 0x7bb58186, 0xf1a550a, 0x40b77064, 0x94d68186, 0x2737460d, + 0x46bb6068, 0x81b88186, 0x273b8e10, 0x59db6060, 0x87c48186, 0x485d4013, 0x67e65060, + 0x64978186, 0x485d8816, 0x75f85058, 0x65988186, 0x6e803a19, 0x7afd4058, 0x3d688186, + 0x6e80821c, 0x7afd4050, 0x3e698186, 0x979f341f, 0x75f93050, 0x1b3c8186, 0x979f7c22, + 0x67e73048, 0x21458086, 0xbdbb252e, 0x59e02048, 0xe248086, 0xbdbf2876, 0x46be2040, + 0x27418186, 0xdddaaf2b, 0x40c01044, 0x446c8186, 0xbdbfc731, 0x67ed2050, 0x64988186, + 0x979f3737, 0x75f93058, 0x8ac78186, 0x6e803d3d, 0x6bea4060, 0xa2e58186, 0x485d4343, + 0x4cc45068, 0xa2e88186, 0x273b4949, 0x23996070, 0x679c7f86, 0x95252, 0x2ca38064, + 0x517f8086, 0xf1a5894, 0x51cc7054, 0x5e938186, 0x27378b5b, 0x67e76058, 0x44678086, + 0x273b5e97, 0x67e86050, 0x3e688186, 0x485d8561, 0x75f85050, 0x1b3c8186, 0x485d9d64, + 0x67e55048, 0x18398186, 0x6e807f67, 0x6bea4048, 0x1a8186, 0x979f796d, 0x4cc53040, + 0x3b5b8086, 0xf2f1b5be, 0x2cae0044, 0x51868186, 0xf2f1bbbb, 0x35bb0054, 0x51818186, + 0xdddac1c1, 0x51da1054, 0x67a37f86, 0x90101, 0x648080, 0x70ad7f86, 0x94f04, + 0x16858074, 0x0, 0x0, 0x0, 0xcb010051, 0xcb00fffa, 0xcb010016, + 0x5021000, 0xc008003, 0x81860080, 0x0, 0x0, 0x92351604, 0x300001f, + 0x2826450f, 0x4, 0xac31, 0x100af5e, 0x100c449, 0xa01, 0x1000d07, + 0x6143, 0x100643d, 0x10079c1, 0x8537, 0x883d, 0x1008b07, 0x1008e49, + 0x100b243, 0xb549, 0x100b837, 0xbe31, 0xc1c1, 0xc7bb, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, + 0x80808080, 0x80808080, 0x0, 0x0, 0x0, 0x808186, 0x707, + 0x39800040, 0x2ab78186, 0x141a1010, 0xf4b7080, 0x51e78186, 0x2c379113, 0x2c6d6078, + 0x43db8186, 0x2c3b1616, 0x9486080, 0x5ff88186, 0x4d5d9719, 0x26695078, 0x51e68186, + 0x4d5d1c1c, 0x33c5080, 0x64fd8186, 0x73809d1f, 0x25684078, 0x55ea8186, 0x73802222, + 0x394080, 0x5ff98186, 0x9c9fa325, 0x26683078, 0x51e68186, 0x9c9f2828, 0x33b3080, + 0x51ee8186, 0xc2bba92b, 0x2c662078, 0x43dc8186, 0xc2bf2e2e, 0x9422080, 0x3bda8186, + 0xe2da4631, 0x397f1074, 0x2abf8186, 0xe2da3434, 0xf401080, 0x1fbb8086, 0xf7f13740, + 0x397a0074, 0x16a58186, 0xf7f13a3a, 0x23520080, 0x808186, 0xffffcd3d, 0x39800040, + 0x16ae8186, 0xf7f1ca43, 0x4fa50064, 0x2ac08186, 0xe2da7649, 0x63bf1064, 0x51ed8186, + 0xc2bfa64c, 0x46942070, 0x43e08186, 0xc2bb734f, 0x69bb2068, 0x5ff98186, 0x9c9fa052, + 0x4c973070, 0x51e78186, 0x9c9f6d55, 0x6fc43068, 0x64fd8186, 0x73809a58, 0x4d984070, + 0x55ea8186, 0x7380675b, 0x72c74068, 0x5ff88186, 0x4d5d945e, 0x4c985070, 0x36c58186, + 0x9c9f826a, 0x8ae63060, 0x30be8186, 0xc2bf7c70, 0x7cdc2060, 0xd9a8086, 0xc2bb7fbb, + 0x8aee2058, 0x16a37f86, 0x5090101, 0x23640000, 0x1fad7f86, 0x5090404, 0x39850074, + 0x0, 0x0, 0x0, 0xcb010000, 0xcb00ffff, 0xcb010039, 0x5021000, + 0x20001b, 0x6c00c102, 0x2, 0x0, 0x210f0904, 0x6, 0xb090b05, + 0x4, 0x101, 0x707, 0x1307, 0x1c04, 0x1f07, 0x80808080, + 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x0, 0x538186, + 0x90d0d, 0x1f7b0034, 0x95d7f86, 0x91010, 0x359c0044, 0x1f7b8186, 0x91616, + 0x3ead0054, 0x359c8186, 0x91919, 0x35a30064, 0x1f857f86, 0x90404, 0x530014, + 0x9648186, 0x90a0a, 0x95d0024, 0x0, 0x0, 0xcb01001f, 0xcb00fffa, + 0xcb01001f, 0x1021000, 0x223, 0x0, 0x0, 0x0}; + for (auto& word : words) { + gen.add_word(word); + } + return result; +} + +size_t gen_dummy_frag_ctrl(DataObjectGenerator& gen) { + size_t result = gen.current_offset_bytes(); + gen.add_word(0x1067232); + gen.add_word(0x54320603); + gen.add_word(0x5d300002); + gen.add_word(0x5d350002); + gen.add_word(0x120f0002); + gen.add_word(0x2); + gen.add_word(0x0); + gen.add_word(0x0); + return result; +} + +size_t gen_dummy_extra_info(DataObjectGenerator& gen) { + size_t result = gen.current_offset_bytes(); + gen.add_word(0x1); + gen.add_word(0x0); + gen.add_word(0x0); + gen.add_word(0x0); + gen.add_word(0xb74ccccd); + gen.add_word(0x40400000); + gen.add_word(0x8066a1ff); + gen.add_word(0x0); + return result; +} + +size_t generate_dummy_merc_ctrl(DataObjectGenerator& gen, const ArtGroup& ag) { + gen.align_to_basic(); + gen.add_type_tag("merc-ctrl"); + size_t result = gen.current_offset_bytes(); + // excluding align and prejoint + auto joints = ((ArtJointGeo*)ag.elts.at(0))->length - 2; + gen.add_word(0); // 4 + gen.add_ref_to_string_in_pool(ag.name + "-lod0"); // 8 + gen.add_word(0); // 12 + gen.add_symbol_link("#f"); // 16 (res-lump) + gen.add_word(joints); // 20 (num-joints) + gen.add_word(0x0); // 24 (pad) + gen.add_word(0x0); // 28 (pad) + gen.add_word(0x4188ee86); // 32-112 (xyz-scale) + gen.add_word(0xc780ff80); // 36 (st-magic) + gen.add_word(0x40798000); // 40 (st-out-a) + gen.add_word(0x40eb4000); // 44 (st-out-b) + gen.add_word(0x4780ff80); // 48 (st-vif-add) + gen.add_word(0x50000); // 52 ((st-int-off << 16) + st-int-scale) + gen.add_word(0x1); // 56 (effect-count) + gen.add_word(0x0); // 60 (blend-target-count) + gen.add_word(0xe00005); // 64 ((fragment-count << 16) + tri-count) + gen.add_word(0x860101); // 68 + gen.add_word(0x86011b); // 72 + gen.add_word(0x0); // 76 + gen.add_word(0x0); // 80 + gen.add_word(0x120101); // 84 + gen.add_word(0x83002c); // 88 + gen.add_word(0x3e780184); // 92 + gen.add_word(0x0); // 96 + gen.add_word(0x0); // 100 + gen.add_word(0x0); // 104 + gen.add_word(0x0); // 108 + auto frag_geo_slot = gen.add_word(0); // 112-140 (effect) + auto frag_ctrl_slot = gen.add_word(0); // 116 (frag-ctrl) + gen.add_word(0x0); // 120 (blend-data) + gen.add_word(0x0); // 124 (blend-ctrl) + gen.add_word(0x50000); // 128 + gen.add_word(0xe00000); // 132 + gen.add_word(0x100011b); // 136 + auto extra_info_slot = gen.add_word(0); // 140 (extra-info) + gen.link_word_to_byte(extra_info_slot, gen_dummy_extra_info(gen)); + gen.link_word_to_byte(frag_ctrl_slot, gen_dummy_frag_ctrl(gen)); + gen.link_word_to_byte(frag_geo_slot, gen_dummy_frag_geo(gen)); + return result; +} + +std::vector ArtGroup::save_object_file() const { + DataObjectGenerator gen; + gen.add_type_tag("art-group"); + auto ag_words = 8 + length; + while (gen.words() < ag_words) { + gen.add_word(0); + } + auto file_info_slot = info.add_to_object_file(gen); + gen.link_word_to_byte(1, file_info_slot); // 4 (file-info) + gen.link_word_to_string_in_pool(name, 8 / 4); // 8 (name) + gen.set_word(12 / 4, length); // 12 (ag length) + gen.link_word_to_symbol("#f", 16 / 4); // 16 (res-lump) + gen.set_word(20 / 4, 0); // 20 (pad) + gen.set_word(24 / 4, 0); + gen.set_word(28 / 4, 0); + if (!elts.empty()) { + if (elts.at(0)) { + auto jgeo = (ArtJointGeo*)elts.at(0); + gen.link_word_to_byte(32 / 4, jgeo->generate(gen)); + } + if (!elts.at(1)) { + gen.link_word_to_byte(36 / 4, generate_dummy_merc_ctrl(gen, *this)); + } + if (elts.at(2)) { + auto ja = (ArtJointAnim*)elts.at(2); + gen.link_word_to_byte(40 / 4, ja->generate(gen)); + } + } + + return gen.generate_v4(); +} + +bool run_build_actor(const std::string& input_model, + const std::string& ag_out, + const std::string& output_prefix) { + std::string ag_name; + if (fs::exists(file_util::get_jak_project_dir() / input_model)) { + ag_name = fs::path(input_model).stem().string(); + } else { + ASSERT_MSG(false, "Model file not found: " + input_model); + } + + ArtGroup ag(ag_name); + std::vector joints; + auto identity = math::Matrix4f::identity(); + joints.emplace_back("align", 0, -1, identity); + joints.emplace_back("prejoint", 1, -1, identity); + // matrix stolen from "egg" joint from "money" art group + auto main_pose = math::Matrix4f::zero(); + main_pose(0, 0) = 1.0f; + main_pose(0, 1) = -0.0f; + main_pose(0, 2) = 0.0f; + main_pose(0, 3) = -0.0f; + main_pose(1, 0) = -0.0f; + main_pose(1, 1) = 1.0f; + main_pose(1, 2) = -0.0f; + main_pose(1, 3) = 0.0f; + main_pose(2, 0) = 0.0f; + main_pose(2, 1) = -0.0f; + main_pose(2, 2) = 1.0f; + main_pose(2, 3) = -0.0f; + main_pose(3, 0) = -0.0f; + main_pose(3, 1) = -2194.1628418f; + main_pose(3, 2) = -0.0f; + main_pose(3, 3) = 1.0f; + Joint main("main", 2, 1, main_pose); + joints.emplace_back(main); + ArtJointGeo jgeo(ag.name, joints); + ArtJointAnim ja(ag.name, joints); + + jgeo.lump.add_res( + std::make_unique("texture-level", std::vector{2}, DEFAULT_RES_TIME)); + // jgeo.lump.add_res(std::make_unique( + // "trans-offset", std::vector{{0.0f, 2048.0f, 0.0f, 1.0f}}, + // DEFAULT_RES_TIME)); + jgeo.lump.add_res( + std::make_unique("joint-channel", std::vector{0}, DEFAULT_RES_TIME)); + jgeo.lump.add_res(std::make_unique( + "lod-dist", std::vector{5000.0f * METER_LENGTH, 6000.0f * METER_LENGTH}, + DEFAULT_RES_TIME)); + jgeo.lump.sort_res(); + + ag.elts.emplace_back(&jgeo); + // dummy merc-ctrl + ag.elts.emplace_back(nullptr); + ag.elts.emplace_back(&ja); + + ag.length = ag.elts.size(); + + auto ag_file = ag.save_object_file(); + lg::info("ag file size {} bytes", ag_file.size()); + auto save_path = fs::path(ag_out); + file_util::create_dir_if_needed_for_file(ag_out); + lg::info("Saving to {}", save_path.string()); + file_util::write_binary_file(file_util::get_jak_project_dir() / save_path, ag_file.data(), + ag_file.size()); + return true; +} +} // namespace jak1 \ No newline at end of file diff --git a/goalc/build_actor/jak1/build_actor.h b/goalc/build_actor/jak1/build_actor.h new file mode 100644 index 00000000000..0634d08a090 --- /dev/null +++ b/goalc/build_actor/jak1/build_actor.h @@ -0,0 +1,210 @@ +#pragma once + +#include "common/util/gltf_util.h" + +#include "goalc/build_actor/common/art_types.h" +#include "goalc/build_level/collide/common/collide_common.h" + +namespace jak1 { + +struct Joint { + std::string name; + s32 number; + int parent; + math::Matrix4f bind_pose{}; + + Joint(const std::string& name, int number, int parent, math::Matrix4f bind_pose) { + this->name = name; + this->number = number; + this->parent = parent; + this->bind_pose = bind_pose; + } + + size_t generate(DataObjectGenerator& gen) const; +}; + +// basic +struct JointAnim { + std::string name; + s16 number; + s16 length; + + explicit JointAnim(const Joint& joint) { + this->name = joint.name; + number = joint.number; + length = 1; + } +}; + +// basic +struct JointAnimCompressed : JointAnim { + std::vector data; + explicit JointAnimCompressed(const Joint& joint) : JointAnim(joint) { + number = joint.number; + length = 1; + } + size_t generate(DataObjectGenerator& gen) const; +}; + +struct JointAnimFrame { + math::Matrix4f matrices[2]; + std::vector data; + + size_t generate(DataObjectGenerator& gen) const; +}; + +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; + } + size_t generate(DataObjectGenerator& gen) const; +}; + +struct JointAnimCompressedFixed { + JointAnimCompressedHDR hdr; + u32 offset_64; + u32 offset_32; + u32 offset_16; + u32 reserved; + math::Vector4f data[133]; + + JointAnimCompressedFixed() { + offset_64 = 0; + offset_32 = 0x88; + offset_16 = 0x90; + 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); + } + size_t generate(DataObjectGenerator& gen) const; +}; + +struct JointAnimCompressedFrame { + u32 offset_64; + u32 offset_32; + u32 offset_16; + u32 reserved; + math::Vector4f data[133]; + + JointAnimCompressedFrame() { + offset_64 = 0; + offset_32 = 0; + offset_16 = 0; + reserved = 0; + } + + size_t generate(DataObjectGenerator& gen) const; +}; + +struct JointAnimCompressedControl { + u32 num_frames; + u32 fixed_qwc; + u32 frame_qwc; + JointAnimCompressedFixed fixed{}; + JointAnimCompressedFrame frame[1]; + + JointAnimCompressedControl() { + num_frames = 1; + fixed_qwc = 0xf; + frame_qwc = 1; + fixed = JointAnimCompressedFixed(); + frame[0] = JointAnimCompressedFrame(); + } + + size_t generate(DataObjectGenerator& gen) const; +}; + +struct CollideMeshTri { + u8 vert_idx[3]; + u8 unused; + PatSurface pat; +}; + +struct CollideMesh { + s32 joint_id; + u32 num_tris; + u32 num_verts; + std::vector vertices; + CollideMeshTri tris; +}; + +struct ArtJointGeo : ArtElement { + std::vector data; + CollideMesh mesh; + ResLump lump; + + explicit ArtJointGeo(const std::string& name, const std::vector& joints) { + this->name = name + "-lod0"; + length = joints.size(); + for (auto& joint : joints) { + data.push_back(joint); + } + } + size_t generate(DataObjectGenerator& gen) const; + size_t generate_res_lump(DataObjectGenerator& gen) const; + size_t generate_mesh(DataObjectGenerator& gen) const; +}; + +struct ArtJointAnim : ArtElement { + MercEyeAnimBlock eye_anim_data; + float speed; + float artist_base; + float artist_step; + std::string master_art_group_name; + s32 master_art_group_index; + u8* blerc_data = nullptr; + JointAnimCompressedControl frames; + std::vector data; + + ArtJointAnim(const std::string& name, const std::vector& joints) { + this->name = name + "-idle"; + length = joints.size(); + speed = 1.0f; + artist_base = 0.0f; + artist_step = 1.0f; + master_art_group_name = name; + master_art_group_index = 2; + frames = JointAnimCompressedControl(); + for (auto& joint : joints) { + data.emplace_back(joint); + } + } + size_t generate(DataObjectGenerator& gen) const; +}; + +struct ArtGroup : Art { + FileInfo info; + std::vector elts; + std::map joint_map; + + explicit ArtGroup(const std::string& file_name) { + info.file_type = "art-group"; + info.file_name = "/src/next/data/art-group6/" + file_name + "-ag.go"; + name = file_name; + info.major_version = versions::jak1::ART_FILE_VERSION; + info.minor_version = 0; + info.tool_debug = "Created by OpenGOAL buildactor"; + info.mdb_file_name = "Unknown"; + info.maya_file_name = "Unknown"; + } + std::vector save_object_file() const; +}; + +bool run_build_actor(const std::string& input_model, + const std::string& output_file, + const std::string& output_prefix); +} // namespace jak1 diff --git a/goalc/build_actor/main.cpp b/goalc/build_actor/main.cpp new file mode 100644 index 00000000000..9f83a0f4f43 --- /dev/null +++ b/goalc/build_actor/main.cpp @@ -0,0 +1,63 @@ +#include "common/log/log.h" +#include "common/util/Assert.h" +#include "common/util/FileUtil.h" + +#include "goalc/build_actor/jak1/build_actor.h" + +#include "third-party/CLI11.hpp" + +int main(int argc, char** argv) { + // logging + lg::set_stdout_level(lg::level::info); + lg::set_flush_level(lg::level::info); + lg::initialize(); + + // game version + std::string game, input_model, output_file; + fs::path project_path_override; + + // path + if (!file_util::setup_project_path(std::nullopt)) { + return 1; + } + + lg::info("Build Actor Tool", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR); + + CLI::App app{"OpenGOAL Compiler / REPL"}; + app.add_option("input-model", input_model, + "Input model file (for example: custom_assets/jak1/models/test.glb)") + ->required(); + app.add_option("output-file", output_file, + "Output *-ag.go file (for example: out/jak1/obj/test-ag.go)") + ->required(); + app.add_option("-g,--game", game, "Game version (only jak1 for now)")->required(); + app.add_option("--proj-path", project_path_override, + "Specify the location of the 'data/' folder"); + app.validate_positionals(); + CLI11_PARSE(app, argc, argv) + + GameVersion game_version = game_name_to_version(game); + + if (!project_path_override.empty()) { + if (!fs::exists(project_path_override)) { + lg::error("Error: project path override '{}' does not exist", project_path_override.string()); + return 1; + } + if (!file_util::setup_project_path(project_path_override)) { + lg::error("Could not setup project path!"); + return 1; + } + } else if (!file_util::setup_project_path(std::nullopt)) { + return 1; + } + + switch (game_version) { + case GameVersion::Jak1: + jak1::run_build_actor(input_model, output_file, "jak1/"); + break; + default: + ASSERT_NOT_REACHED_MSG("unsupported game version"); + } + + return 0; +} diff --git a/goalc/build_level/common/TexturePool.h b/goalc/build_level/common/TexturePool.h deleted file mode 100644 index 5309eb7515b..00000000000 --- a/goalc/build_level/common/TexturePool.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "common/custom_data/Tfrag3Data.h" - -struct TexturePool { - std::unordered_map textures_by_name; - std::vector textures_by_idx; -}; \ No newline at end of file diff --git a/goalc/build_level/common/Tfrag.h b/goalc/build_level/common/Tfrag.h index 5ceade14a6c..8c5c8521bc7 100644 --- a/goalc/build_level/common/Tfrag.h +++ b/goalc/build_level/common/Tfrag.h @@ -6,8 +6,6 @@ #include "common/custom_data/Tfrag3Data.h" -#include "goalc/build_level/common/TexturePool.h" - class DataObjectGenerator; struct DrawableTreeTfrag { diff --git a/goalc/build_level/common/build_level.cpp b/goalc/build_level/common/build_level.cpp index e334884d248..edb22e7d379 100644 --- a/goalc/build_level/common/build_level.cpp +++ b/goalc/build_level/common/build_level.cpp @@ -41,3 +41,23 @@ std::vector find_art_groups( } return art_groups; } + +void add_model_to_level(GameVersion version, const std::string& name, tfrag3::Level& lvl) { + lg::info("custom level: adding custom model {}", name); + auto glb = name + ".glb"; + auto merc_data = load_merc_model(lvl.merc_data.indices.size(), lvl.merc_data.vertices.size(), + lvl.textures.size(), + fs::path(file_util::get_jak_project_dir() / "custom_assets" / + game_version_names[version] / "models" / glb) + .string(), + name + "-lod0"); + for (auto& idx : merc_data.new_indices) { + lvl.merc_data.indices.push_back(idx); + } + for (auto& vert : merc_data.new_vertices) { + lvl.merc_data.vertices.push_back(vert); + } + lvl.merc_data.models.push_back(merc_data.new_model); + lvl.textures.insert(lvl.textures.end(), merc_data.new_textures.begin(), + merc_data.new_textures.end()); +} \ No newline at end of file diff --git a/goalc/build_level/common/build_level.h b/goalc/build_level/common/build_level.h index d71a8bba50c..f55c24c3524 100644 --- a/goalc/build_level/common/build_level.h +++ b/goalc/build_level/common/build_level.h @@ -9,6 +9,7 @@ #include "common/util/string_util.h" #include "decompiler/level_extractor/extract_level.h" +#include void save_pc_data(const std::string& nickname, tfrag3::Level& data, const fs::path& fr3_output_dir); std::vector get_build_level_deps(const std::string& input_file); @@ -16,3 +17,4 @@ std::vector find_art_groups( std::vector& processed_ags, const std::vector& custom_level_ag, const std::vector& dgo_files); +void add_model_to_level(GameVersion version, const std::string& name, tfrag3::Level& lvl); diff --git a/goalc/build_level/common/gltf_mesh_extract.cpp b/goalc/build_level/common/gltf_mesh_extract.cpp index 812b1a59e5c..786e6ff9f4d 100644 --- a/goalc/build_level/common/gltf_mesh_extract.cpp +++ b/goalc/build_level/common/gltf_mesh_extract.cpp @@ -11,474 +11,20 @@ #include "common/log/log.h" #include "common/math/geometry.h" #include "common/util/Timer.h" +#include "common/util/gltf_util.h" #include "third-party/tiny_gltf/tiny_gltf.h" +using namespace gltf_util; namespace gltf_mesh_extract { -namespace { - -/*! - * Convert a GLTF index buffer to std::vector - */ -template -std::vector index_list_to_u32(const u8* data, u32 num_verts, u32 offset, u32 stride) { - std::vector result; - result.reserve(num_verts); - for (u32 i = 0; i < num_verts; i++) { - T val; - memcpy(&val, data, sizeof(T)); - result.push_back(offset + val); - data += stride; - } - return result; -} - -/*! - * Convert a GLTF position buffer or similar to std::vector - */ -std::vector extract_vec3f(const u8* data, u32 count, u32 stride) { - std::vector result; - result.reserve(count); - for (u32 i = 0; i < count; i++) { - memcpy(&result.emplace_back(), data, sizeof(math::Vector3f)); - data += stride; - } - return result; -} - -std::vector extract_vec2f(const u8* data, u32 count, u32 stride) { - std::vector result; - result.reserve(count); - for (u32 i = 0; i < count; i++) { - memcpy(&result.emplace_back(), data, sizeof(math::Vector2f)); - data += stride; - } - return result; -} - -/*! - * Convert a GLTF color buffer (float format) to u8 colors. - */ -std::vector> extract_color_from_vec4_float(const u8* data, - u32 count, - u32 stride) { - std::vector> result; - result.reserve(count); - for (u32 i = 0; i < count; i++) { - math::Vector temp; - memcpy(&temp, data, sizeof(math::Vector)); - data += stride; - result.emplace_back(temp.x() * 255, temp.y() * 255, temp.z() * 255, temp.w() * 255); - } - return result; -} - -/*! - * Convert a GLTF color buffer (u16 format) to u8 colors. - */ -std::vector> extract_color_from_vec4_u16(const u8* data, - u32 count, - u32 stride) { - std::vector> result; - result.reserve(count); - for (u32 i = 0; i < count; i++) { - math::Vector temp; - memcpy(&temp, data, sizeof(math::Vector)); - data += stride; - result.emplace_back(temp.x() >> 8, temp.y() >> 8, temp.z() >> 8, temp.w() >> 8); - } - return result; -} - -/*! - * Convert a GLTF index buffer - */ -std::vector gltf_index_buffer(const tinygltf::Model& model, - int indices_idx, - u32 index_offset) { - const auto& indices_accessor = model.accessors[indices_idx]; - const auto& buffer_view = model.bufferViews[indices_accessor.bufferView]; - const auto& buffer = model.buffers[buffer_view.buffer]; - const auto data_ptr = buffer.data.data() + buffer_view.byteOffset + indices_accessor.byteOffset; - const auto stride = indices_accessor.ByteStride(buffer_view); - const auto count = indices_accessor.count; - - switch (indices_accessor.componentType) { - case TINYGLTF_COMPONENT_TYPE_BYTE: - return index_list_to_u32(data_ptr, count, index_offset, stride); - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: - return index_list_to_u32(data_ptr, count, index_offset, stride); - case TINYGLTF_COMPONENT_TYPE_SHORT: - return index_list_to_u32(data_ptr, count, index_offset, stride); - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: - return index_list_to_u32(data_ptr, count, index_offset, stride); - case TINYGLTF_COMPONENT_TYPE_INT: - return index_list_to_u32(data_ptr, count, index_offset, stride); - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: - return index_list_to_u32(data_ptr, count, index_offset, stride); - default: - ASSERT_MSG(false, "unsupported component type"); - } -} - -struct ExtractedVertices { - std::vector vtx; - std::vector> vtx_colors; - std::vector normals; -}; - -/*! - * Extract positions, colors, and normals from a mesh. - */ -ExtractedVertices gltf_vertices(const tinygltf::Model& model, - const std::map& attributes, - const math::Matrix4f& w_T_local, - bool get_colors, - bool get_normals, - const std::string& debug_name) { - std::vector result; - std::vector> vtx_colors; - - { - const auto& position_attrib = attributes.find("POSITION"); - ASSERT_MSG(position_attrib != attributes.end(), "Did not find position attribute."); - - const auto attrib_accessor = model.accessors[position_attrib->second]; - const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView]; - const auto& buffer = model.buffers[buffer_view.buffer]; - const auto data_ptr = buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset; - const auto byte_stride = attrib_accessor.ByteStride(buffer_view); - const auto count = attrib_accessor.count; - - ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC3, "POSITION wasn't vec3"); - ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT, - "POSITION wasn't float"); - // for (auto& attrib : attributes) { - // lg::print("attrib: {}\n", attrib.first); - //} - auto mesh_verts = extract_vec3f(data_ptr, count, byte_stride); - result.reserve(mesh_verts.size()); - for (auto& vert : mesh_verts) { - auto& new_vert = result.emplace_back(); - math::Vector4f v_in(vert.x(), vert.y(), vert.z(), 1); - math::Vector4f v_w = w_T_local * v_in; - new_vert.x = v_w.x() * 4096; - new_vert.y = v_w.y() * 4096; - new_vert.z = v_w.z() * 4096; - } - } - - if (get_colors) { - const auto& color_attrib = attributes.find("COLOR_0"); - if (color_attrib == attributes.end()) { - lg::error("Mesh {} didn't have any colors, using white", debug_name); - for (size_t i = 0; i < result.size(); i++) { - vtx_colors.emplace_back(0x80, 0x80, 0x80, 0xff); - } - } else { - const auto attrib_accessor = model.accessors[color_attrib->second]; - const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView]; - const auto& buffer = model.buffers[buffer_view.buffer]; - const auto data_ptr = - buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset; - const auto byte_stride = attrib_accessor.ByteStride(buffer_view); - const auto count = attrib_accessor.count; - - ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC4, "COLOR_0 wasn't vec4"); - std::vector> colors; - switch (attrib_accessor.componentType) { - case TINYGLTF_COMPONENT_TYPE_FLOAT: - colors = extract_color_from_vec4_float(data_ptr, count, byte_stride); - break; - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: - colors = extract_color_from_vec4_u16(data_ptr, count, byte_stride); - break; - default: - lg::die("Unknown component type for COLOR_0: {}", attrib_accessor.componentType); - } - vtx_colors.insert(vtx_colors.end(), colors.begin(), colors.end()); - } - } - - bool got_texture = false; - { - const auto& texcoord_attrib = attributes.find("TEXCOORD_0"); - if (texcoord_attrib != attributes.end()) { - const auto attrib_accessor = model.accessors[texcoord_attrib->second]; - const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView]; - const auto& buffer = model.buffers[buffer_view.buffer]; - const auto data_ptr = - buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset; - const auto byte_stride = attrib_accessor.ByteStride(buffer_view); - const auto count = attrib_accessor.count; - - ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC2, "TEXCOORD wasn't vec2"); - ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT, - "TEXCOORD wasn't float"); - auto mesh_verts = extract_vec2f(data_ptr, count, byte_stride); - ASSERT(mesh_verts.size() == result.size()); - got_texture = true; - for (size_t i = 0; i < mesh_verts.size(); i++) { - result[i].s = mesh_verts[i].x(); - result[i].t = mesh_verts[i].y(); - } - } else { - if (!get_normals) { - // don't warn if we're just getting collision - lg::warn("No texcoord attribute for mesh: {}", debug_name); - } - } - } - - std::vector normals; - if (get_normals) { - const auto& normal_attrib = attributes.find("NORMAL"); - if (normal_attrib != attributes.end()) { - const auto attrib_accessor = model.accessors[normal_attrib->second]; - const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView]; - const auto& buffer = model.buffers[buffer_view.buffer]; - const auto data_ptr = - buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset; - const auto byte_stride = attrib_accessor.ByteStride(buffer_view); - const auto count = attrib_accessor.count; - - ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC3, "NORMAL wasn't vec3"); - ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT, - "NORMAL wasn't float"); - normals = extract_vec3f(data_ptr, count, byte_stride); - for (auto& nrm : normals) { - math::Vector4f nrm4(nrm.x(), nrm.y(), nrm.z(), 0.f); - nrm = (w_T_local * nrm4).xyz(); - } - ASSERT(normals.size() == result.size()); - } else { - lg::error("No NORMAL attribute for mesh: {}", debug_name); - } - } - - for (auto& v : result) { - v.color_index = 0; - if (!got_texture) { - v.s = 0; - v.t = 0; - } - } - // TODO: other properties - return {result, vtx_colors, normals}; -} - -DrawMode make_default_draw_mode() { - DrawMode mode; - mode.set_depth_write_enable(true); - mode.set_depth_test(GsTest::ZTest::GEQUAL); - mode.set_alpha_blend(DrawMode::AlphaBlend::DISABLED); - mode.set_aref(0); - mode.set_alpha_fail(GsTest::AlphaFail::KEEP); - mode.set_clamp_s_enable(false); - mode.set_clamp_t_enable(false); - mode.disable_filt(); // for checkerboard... - mode.enable_tcc(); // ? - mode.disable_at(); - mode.enable_zt(); - mode.disable_ab(); - mode.disable_decal(); - mode.enable_fog(); - return mode; -} - -int texture_pool_debug_checker(TexturePool* pool) { - const auto& existing = pool->textures_by_name.find("DEBUG_CHECKERBOARD"); - if (existing == pool->textures_by_name.end()) { - size_t idx = pool->textures_by_idx.size(); - pool->textures_by_name["DEBUG_CHECKERBOARD"] = idx; - auto& tex = pool->textures_by_idx.emplace_back(); - tex.w = 16; - tex.h = 16; - tex.debug_name = "DEBUG_CHECKERBOARD"; - tex.debug_tpage_name = "DEBUG"; - tex.load_to_pool = false; - tex.combo_id = 0; // doesn't matter, not a pool tex - tex.data.resize(16 * 16); - u32 c0 = 0xa0303030; - u32 c1 = 0xa0e0e0e0; - for (int i = 0; i < 16; i++) { - for (int j = 0; j < 16; j++) { - tex.data[i * 16 + j] = (((i / 4) & 1) ^ ((j / 4) & 1)) ? c1 : c0; - } - } - return idx; - } else { - return existing->second; - } -} - -int texture_pool_add_texture(TexturePool* pool, const tinygltf::Image& tex) { - const auto& existing = pool->textures_by_name.find(tex.name); - if (existing != pool->textures_by_name.end()) { - lg::info("Reusing image: {}", tex.name); - return existing->second; - } else { - lg::info("adding new texture: {}, size {} kB", tex.name, tex.width * tex.height * 4 / 1024); - } - - ASSERT(tex.bits == 8); - ASSERT(tex.component == 4); - ASSERT(tex.pixel_type == TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE); - - size_t idx = pool->textures_by_idx.size(); - pool->textures_by_name[tex.name] = idx; - auto& tt = pool->textures_by_idx.emplace_back(); - tt.w = tex.width; - tt.h = tex.height; - tt.debug_name = tex.name; - tt.debug_tpage_name = "custom-level"; - tt.load_to_pool = false; - tt.combo_id = 0; // doesn't matter, not a pool tex - tt.data.resize(tt.w * tt.h); - ASSERT(tex.image.size() >= tt.data.size()); - memcpy(tt.data.data(), tex.image.data(), tt.data.size() * 4); - return idx; -} -} // namespace - -math::Matrix4f affine_translation(const math::Vector3f& translation) { - math::Matrix4f result = math::Matrix4f::identity(); - result(0, 3) = translation[0]; - result(1, 3) = translation[1]; - result(2, 3) = translation[2]; - result(3, 3) = 1; - return result; -} - -math::Matrix4f affine_scale(const math::Vector3f& scale) { - math::Matrix4f result = math::Matrix4f::zero(); - result(0, 0) = scale[0]; - result(1, 1) = scale[1]; - result(2, 2) = scale[2]; - result(3, 3) = 1; - return result; -} - -math::Matrix4f affine_rot_qxyzw(const math::Vector4f& quat) { - math::Matrix4f result = math::Matrix4f::zero(); - result(3, 3) = 1; - result(0, 0) = 1.0 - 2.0 * (quat.y() * quat.y() + quat.z() * quat.z()); - result(0, 1) = 2.0 * (quat.x() * quat.y() - quat.z() * quat.w()); - result(0, 2) = 2.0 * (quat.x() * quat.z() + quat.y() * quat.w()); - result(1, 0) = 2.0 * (quat.x() * quat.y() + quat.z() * quat.w()); - result(1, 1) = 1.0 - 2.0 * (quat.x() * quat.x() + quat.z() * quat.z()); - result(1, 2) = 2.0 * (quat.y() * quat.z() - quat.x() * quat.w()); - result(2, 0) = 2.0 * (quat.x() * quat.z() - quat.y() * quat.w()); - result(2, 1) = 2.0 * (quat.y() * quat.z() + quat.x() * quat.w()); - result(2, 2) = 1.0 - 2.0 * (quat.x() * quat.x() + quat.y() * quat.y()); - return result; -} - -math::Vector3f vector3f_from_gltf(const std::vector& in) { - ASSERT(in.size() == 3); - return math::Vector3f{in[0], in[1], in[2]}; -} - -math::Vector4f vector4f_from_gltf(const std::vector& in) { - ASSERT(in.size() == 4); - return math::Vector4f{in[0], in[1], in[2], in[3]}; -} - -math::Matrix4f matrix_from_node(const tinygltf::Node& node) { - if (!node.matrix.empty()) { - math::Matrix4f result; - for (int i = 0; i < 16; i++) { - result.data()[i] = node.matrix[i]; - } - return result; - } else { - // from trs - math::Matrix4f t, r, s; - if (!node.translation.empty()) { - t = affine_translation(vector3f_from_gltf(node.translation)); - } else { - t = math::Matrix4f::identity(); - } - - if (!node.rotation.empty()) { - r = affine_rot_qxyzw(vector4f_from_gltf(node.rotation)); - } else { - r = math::Matrix4f::identity(); - } - - if (!node.scale.empty()) { - s = affine_scale(vector3f_from_gltf(node.scale)); - } else { - s = math::Matrix4f::identity(); - } - - return t * r * s; - } -} - -struct NodeWithTransform { - int node_idx; - math::Matrix4f w_T_node; -}; - -/*! - * Recursively walk the tree of nodes, flatten, and compute w_T_node for each. - */ -void node_find_helper(const tinygltf::Model& model, - const math::Matrix4f& w_T_parent, - int node_idx, - std::vector* out) { - const auto& node = model.nodes.at(node_idx); - math::Matrix4f w_T_node = w_T_parent * matrix_from_node(node); - out->push_back({node_idx, w_T_node}); - for (auto& child : node.children) { - node_find_helper(model, w_T_node, child, out); - } -} - -std::vector flatten_nodes_from_all_scenes(const tinygltf::Model& model) { - std::vector out; - for (auto& scene : model.scenes) { - for (auto& nidx : scene.nodes) { - math::Matrix4f identity = math::Matrix4f::identity(); - node_find_helper(model, identity, nidx, &out); - } - } - return out; -} - -void dedup_vertices(const std::vector& vertices_in, - std::vector& vertices_out, - std::vector& old_to_new_out) { - ASSERT(vertices_out.empty()); - ASSERT(old_to_new_out.empty()); - old_to_new_out.resize(vertices_in.size(), -1); - - std::unordered_map vtx_to_new; - - for (size_t in_idx = 0; in_idx < vertices_in.size(); in_idx++) { - auto& vtx = vertices_in[in_idx]; - const auto& lookup = vtx_to_new.find(vtx); - if (lookup == vtx_to_new.end()) { - // first time seeing this one - size_t new_idx = vertices_out.size(); - vertices_out.push_back(vtx); - old_to_new_out[in_idx] = new_idx; - vtx_to_new[vtx] = new_idx; - } else { - old_to_new_out[in_idx] = lookup->second; - } - } -} - void dedup_vertices(TfragOutput& data) { Timer timer; size_t original_size = data.vertices.size(); std::vector new_verts; std::vector old_to_new; - dedup_vertices(data.vertices, new_verts, old_to_new); + gltf_util::dedup_vertices(data.vertices, new_verts, old_to_new); data.vertices = std::move(new_verts); for (auto& draw : data.strip_draws) { @@ -492,41 +38,6 @@ void dedup_vertices(TfragOutput& data) { data.vertices.size(), 100.f * data.vertices.size() / original_size); } -DrawMode draw_mode_from_sampler(const tinygltf::Sampler& sampler) { - DrawMode mode = make_default_draw_mode(); - if (sampler.magFilter == TINYGLTF_TEXTURE_FILTER_NEAREST) { - ASSERT(sampler.minFilter == TINYGLTF_TEXTURE_FILTER_NEAREST); - mode.set_filt_enable(false); - } else { - ASSERT(sampler.minFilter != TINYGLTF_TEXTURE_FILTER_NEAREST); - mode.set_filt_enable(true); - } - - switch (sampler.wrapS) { - case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE: - mode.set_clamp_s_enable(true); - break; - case TINYGLTF_TEXTURE_WRAP_REPEAT: - mode.set_clamp_s_enable(false); - break; - default: - ASSERT(false); - } - - switch (sampler.wrapT) { - case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE: - mode.set_clamp_t_enable(true); - break; - case TINYGLTF_TEXTURE_WRAP_REPEAT: - mode.set_clamp_t_enable(false); - break; - default: - ASSERT(false); - } - - return mode; -} - void extract(const Input& in, TfragOutput& out, const tinygltf::Model& model, diff --git a/goalc/build_level/common/gltf_mesh_extract.h b/goalc/build_level/common/gltf_mesh_extract.h index 94dfc09d6bd..04867b3a383 100644 --- a/goalc/build_level/common/gltf_mesh_extract.h +++ b/goalc/build_level/common/gltf_mesh_extract.h @@ -5,13 +5,16 @@ #include "common/custom_data/Tfrag3Data.h" #include "goalc/build_level/collide/common/collide_common.h" -#include "goalc/build_level/common/TexturePool.h" + +namespace gltf_util { +struct TexturePool; +} namespace gltf_mesh_extract { struct Input { std::string filename; - TexturePool* tex_pool = nullptr; + gltf_util::TexturePool* tex_pool = nullptr; bool get_colors = true; bool auto_wall_enable = true; float auto_wall_angle = 30.f; diff --git a/goalc/build_level/jak1/build_level.cpp b/goalc/build_level/jak1/build_level.cpp index d09b914c6ac..3801eb92c7c 100644 --- a/goalc/build_level/jak1/build_level.cpp +++ b/goalc/build_level/jak1/build_level.cpp @@ -1,5 +1,7 @@ #include "build_level.h" +#include "common/util/gltf_util.h" + #include "decompiler/extractor/extractor_util.h" #include "decompiler/level_extractor/extract_merc.h" #include "goalc/build_level/collide/jak1/collide_bvh.h" @@ -15,9 +17,9 @@ bool run_build_level(const std::string& input_file, const std::string& output_prefix) { auto level_json = parse_commented_json( file_util::read_text_file(file_util::get_file_path({input_file})), input_file); - LevelFile file; // GOAL level file - tfrag3::Level pc_level; // PC level file - TexturePool tex_pool; // pc level texture pool + LevelFile file; // GOAL level file + tfrag3::Level pc_level; // PC level file + gltf_util::TexturePool tex_pool; // pc level texture pool // process input mesh from blender gltf_mesh_extract::Input mesh_extract_in; @@ -202,6 +204,14 @@ bool run_build_level(const std::string& input_file, } } + // add custom models to fr3 + if (level_json.contains("custom_models") && !level_json.at("custom_models").empty()) { + auto models = level_json.at("custom_models").get>(); + for (auto& name : models) { + add_model_to_level(GameVersion::Jak1, name, pc_level); + } + } + // Save the PC level save_pc_data(file.name, pc_level, file_util::get_jak_project_dir() / "out" / output_prefix / "fr3"); diff --git a/goalc/build_level/jak2/build_level.cpp b/goalc/build_level/jak2/build_level.cpp index f7d3726ab84..e7ddfddfbc9 100644 --- a/goalc/build_level/jak2/build_level.cpp +++ b/goalc/build_level/jak2/build_level.cpp @@ -1,5 +1,7 @@ #include "build_level.h" +#include "common/util/gltf_util.h" + #include "decompiler/extractor/extractor_util.h" #include "decompiler/level_extractor/extract_merc.h" #include "goalc/build_level/collide/jak2/collide.h" @@ -14,9 +16,9 @@ bool run_build_level(const std::string& input_file, const std::string& output_prefix) { auto level_json = parse_commented_json( file_util::read_text_file(file_util::get_file_path({input_file})), input_file); - LevelFile file; // GOAL level file - tfrag3::Level pc_level; // PC level file - TexturePool tex_pool; // pc level texture pool + LevelFile file; // GOAL level file + tfrag3::Level pc_level; // PC level file + gltf_util::TexturePool tex_pool; // pc level texture pool // process input mesh from blender gltf_mesh_extract::Input mesh_extract_in; diff --git a/goalc/build_level/jak3/build_level.cpp b/goalc/build_level/jak3/build_level.cpp index 33ac6ee8f12..f90096b31de 100644 --- a/goalc/build_level/jak3/build_level.cpp +++ b/goalc/build_level/jak3/build_level.cpp @@ -14,9 +14,9 @@ bool run_build_level(const std::string& input_file, const std::string& output_prefix) { auto level_json = parse_commented_json( file_util::read_text_file(file_util::get_file_path({input_file})), input_file); - LevelFile file; // GOAL level file - tfrag3::Level pc_level; // PC level file - TexturePool tex_pool; // pc level texture pool + LevelFile file; // GOAL level file + tfrag3::Level pc_level; // PC level file + gltf_util::TexturePool tex_pool; // pc level texture pool // process input mesh from blender gltf_mesh_extract::Input mesh_extract_in; diff --git a/goalc/build_level/main.cpp b/goalc/build_level/main.cpp index 24212505a52..8c4f3e2c4f0 100644 --- a/goalc/build_level/main.cpp +++ b/goalc/build_level/main.cpp @@ -28,8 +28,9 @@ int main(int argc, char** argv) { lg::info("Build Level Tool", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR); CLI::App app{"OpenGOAL Compiler / REPL"}; - app.add_option("input-json", input_json, - "Input JSON file (for example, custom_levels/jak2/test-zone/test-zone.jsonc)") + app.add_option( + "input-json", input_json, + "Input JSON file (for example, custom_assets/jak2/levels/test-zone/test-zone.jsonc)") ->required(); app.add_option("output-file", output_file, "Output .go file, (for example out/jak2/obj/test-zone.go)") diff --git a/goalc/make/MakeSystem.cpp b/goalc/make/MakeSystem.cpp index a7b296da6d8..2a0e2d84756 100644 --- a/goalc/make/MakeSystem.cpp +++ b/goalc/make/MakeSystem.cpp @@ -105,6 +105,7 @@ MakeSystem::MakeSystem(const std::optional repl_config, const std: add_tool(); add_tool(); add_tool(); + add_tool(); } /*! diff --git a/goalc/make/Tools.cpp b/goalc/make/Tools.cpp index c671b9176bb..8bdb5142c36 100644 --- a/goalc/make/Tools.cpp +++ b/goalc/make/Tools.cpp @@ -4,6 +4,7 @@ #include "common/util/DgoWriter.h" #include "common/util/FileUtil.h" +#include "goalc/build_actor/jak1/build_actor.h" #include "goalc/build_level/jak1/build_level.h" #include "goalc/build_level/jak2/build_level.h" #include "goalc/build_level/jak3/build_level.h" @@ -295,4 +296,23 @@ bool BuildLevel3Tool::run(const ToolInput& task, const PathMap& path_map) { throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name())); } return jak3::run_build_level(task.input.at(0), task.output.at(0), path_map.output_prefix); +} + +BuildActorTool::BuildActorTool() : Tool("build-actor") {} + +bool BuildActorTool::needs_run(const ToolInput& task, const PathMap& path_map) { + (void)path_map; + if (task.input.size() != 1) { + throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name())); + } + // std::vector deps{}; + // return Tool::needs_run({task.input, deps, task.output, task.arg}, path_map); + return true; +} + +bool BuildActorTool::run(const ToolInput& task, const PathMap& path_map) { + if (task.input.size() != 1) { + throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name())); + } + return jak1::run_build_actor(task.input.at(0), task.output.at(0), path_map.output_prefix); } \ No newline at end of file diff --git a/goalc/make/Tools.h b/goalc/make/Tools.h index 28c6249b464..28b88b42cb6 100644 --- a/goalc/make/Tools.h +++ b/goalc/make/Tools.h @@ -92,3 +92,10 @@ class BuildLevel3Tool : public Tool { bool run(const ToolInput& task, const PathMap& path_map) override; bool needs_run(const ToolInput& task, const PathMap& path_map) override; }; + +class BuildActorTool : public Tool { + public: + BuildActorTool(); + bool run(const ToolInput& task, const PathMap& path_map) override; + bool needs_run(const ToolInput& task, const PathMap& path_map) override; +};