[buildactor] support generating collide-meshes for custom models (#3540)

This adds support for generating collide meshes when importing custom
models. A couple of things to keep in mind:

- A single `collide-mesh` may only have up to 255 vertices.
- When exporting a GLTF file in Blender, a `collide-mesh` will be
generated for every mesh object that has collision properties applied
(ideally, you would set all visual meshes to `ignore` and your collision
meshes to `invisible` in the OpenGOAL plugin's custom properties).
- Ensure that your actor using the model properly allocates enough
`collide-shape-prim-mesh`es for each `collide-mesh` ([example from the
original game that uses multiple
meshes](f6688659f2/goal_src/jak1/levels/finalboss/robotboss.gc (L2628-L2806))).

~One annoying problem that I haven't fully figured out yet (unrelated to
the actual functionality):
`collide-mesh`es are stored in art groups as an `(array collide-mesh)`
in the `art-joint-geo`'s `extra`, so I had to add a new `Res` type to
support this. The way that `array`s are stored in `res-lump`s is a bit
of a hack right now. The lump only stores a pointer to the array, so the
size of that is 4 bytes, but because we have to generate all the actual
array data too, the current `ResLump` code in C++ doesn't handle this
case well and would assert, so I decided to omit the asserts if an
`array` tag is present and "fake" the size so the object file is
generated more closely to how the game expects it until we figure out
something better.~
This was fixed by generating the array data beforehand and creating a
`ResRef` class that takes the pointer to the array data and adds it to
the lump.
This commit is contained in:
Hat Kid 2024-05-29 06:09:20 +02:00 committed by GitHub
parent f1de2c9bc5
commit c64eea6337
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 349 additions and 70 deletions

View file

@ -492,6 +492,7 @@ void MercDraw::serialize(Serializer& ser) {
ser.from_ptr(&first_index);
ser.from_ptr(&index_count);
ser.from_ptr(&num_triangles);
ser.from_ptr(&no_strip);
}
void Blerc::serialize(Serializer& ser) {

View file

@ -18,7 +18,7 @@ namespace tfrag3 {
// - if changing any large things (vertices, vis, bvh, colors, textures) update get_memory_usage
// - if adding a new category to the memory usage, update extract_level to print it.
constexpr int TFRAG3_VERSION = 40;
constexpr int TFRAG3_VERSION = 41;
enum MemoryUsageCategory {
TEXTURE,
@ -530,6 +530,8 @@ struct MercDraw {
u32 first_index;
u32 index_count;
u32 num_triangles;
// no strip hack for custom models
bool no_strip = false;
void serialize(Serializer& ser);
};

View file

@ -1093,6 +1093,7 @@ Merc2::Draw* Merc2::alloc_normal_draw(const tfrag3::MercDraw& mdraw,
draw->first_bone = first_bone;
draw->light_idx = lights;
draw->num_triangles = mdraw.num_triangles;
draw->no_strip = mdraw.no_strip;
if (ignore_alpha) {
draw->flags |= IGNORE_ALPHA;
}
@ -1283,8 +1284,8 @@ void Merc2::do_draws(const Draw* draw_array,
prof.add_tri(draw.num_triangles);
glBindBufferRange(GL_UNIFORM_BUFFER, 1, m_bones_buffer,
sizeof(math::Vector4f) * draw.first_bone, 128 * sizeof(ShaderMercMat));
glDrawElements(GL_TRIANGLE_STRIP, draw.index_count, GL_UNSIGNED_INT,
(void*)(sizeof(u32) * draw.first_index));
glDrawElements(draw.no_strip ? GL_TRIANGLES : GL_TRIANGLE_STRIP, draw.index_count,
GL_UNSIGNED_INT, (void*)(sizeof(u32) * draw.first_index));
}
if (!normal_vtx_buffer_bound) {

View file

@ -190,6 +190,8 @@ class Merc2 {
u8 flags;
ModBuffers mod_vtx_buffer;
u8 fade[4];
// no strip hack for custom models
u8 no_strip;
};
struct LevelDrawBucket {

View file

@ -224,9 +224,9 @@
:tool 'build-level
:out '(,(string-append "$OUT/obj/" name ".go")))))
(defmacro build-actor (name)
(defmacro build-actor (name &key (gen-mesh #f))
(let* ((path (string-append "custom_assets/jak1/models/" name ".glb")))
`(defstep :in ,path
`(defstep :in '(,path ,(symbol->string gen-mesh))
:tool 'build-actor
:out '(,(string-append "$OUT/obj/" name "-ag.go")))))
@ -1660,7 +1660,8 @@
;; generate the art group for a custom actor.
;; requires a .glb model file in custom_assets/jak1/models
(build-actor "test-actor")
;; to also generate a collide-mesh, add :gen-mesh #t
(build-actor "test-actor" :gen-mesh #t)
;;;;;;;;;;;;;;;;;;;;;
;; Game Engine Code

View file

@ -19,7 +19,7 @@
(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)
:bounds (static-spherem 0 0 0 5)
:texture-level 2
)
@ -30,20 +30,30 @@
(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)
(let ((cgroup (new 'process 'collide-shape-prim-group cshape (the uint 1) 0)))
(set! (-> cgroup prim-core collide-as) (collide-kind ground-object))
(set! (-> cgroup collide-with) (collide-kind target))
(set! (-> cgroup prim-core action) (collide-action solid rider-plat-sticky))
(set! (-> cgroup transform-index) 0)
(set-vector! (-> cgroup local-sphere) 0.0 0.0 0.0 (meters 5))
(set-root-prim! cshape cgroup)
(let ((mesh (new 'process 'collide-shape-prim-mesh cshape (the uint 0) (the uint 0))))
(set! (-> mesh prim-core collide-as) (collide-kind ground-object))
(set! (-> mesh collide-with) (collide-kind target))
(set! (-> mesh prim-core action) (collide-action solid rider-plat-sticky))
(set! (-> mesh prim-core offense) (collide-offense indestructible))
(set! (-> mesh transform-index) 0)
(set-vector! (-> mesh local-sphere) 0.0 (meters 0) 0.0 (meters 5))
(append-prim cgroup 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)
@ -91,9 +101,10 @@
: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)))))
)
; (if (= (-> proc type) target)
; (send-event proc 'attack #f (static-attack-info ((shove-up (meters 2.5)) (shove-back (meters 7.5)))))
; )
#t
)
)
)
@ -110,6 +121,7 @@
(update-transforms! (-> self root))
)
)
; (debug-draw-tris (-> (res-lump-struct (-> self draw art-group data 0 extra) 'collide-mesh-group (array collide-mesh)) 0) self 2)
; (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))

View file

@ -2,6 +2,8 @@
#include "common/log/log.h"
#include <goalc/build_level/common/gltf_mesh_extract.h>
void extract(const std::string& name,
MercExtractData& out,
const tinygltf::Model& model,
@ -17,10 +19,28 @@ void extract(const std::string& name,
for (const auto& n : all_nodes) {
const auto& node = model.nodes[n.node_idx];
if (node.extras.Has("set_invisible") && node.extras.Get("set_invisible").Get<int>()) {
continue;
}
// gltf_mesh_extract::PatResult mesh_default_collide =
// gltf_mesh_extract::custom_props_to_pat(node.extras, node.name);
if (node.mesh >= 0) {
const auto& mesh = model.meshes[node.mesh];
mesh_count++;
for (const auto& prim : mesh.primitives) {
// get material
// const auto& mat_idx = prim.material;
// gltf_mesh_extract::PatResult pat = mesh_default_collide;
// if (mat_idx != -1) {
// const auto& mat = model.materials[mat_idx];
// auto mat_pat = gltf_mesh_extract::custom_props_to_pat(mat.extras, mat.name);
// if (mat_pat.set) {
// pat = mat_pat;
// }
// }
// if (pat.set && pat.ignore) {
// continue; // skip, no collide here
// }
prim_count++;
// extract index buffer
std::vector<u32> prim_indices = gltf_util::gltf_index_buffer(
@ -40,6 +60,7 @@ void extract(const std::string& name,
draw.mode = gltf_util::make_default_draw_mode(); // todo rm
draw.tree_tex_id = 0; // todo rm
draw.num_triangles += prim_indices.size() / 3;
draw.no_strip = true;
// if (draw.vis_groups.empty()) {
// auto& grp = draw.vis_groups.emplace_back();
// grp.num_inds += prim_indices.size();
@ -152,4 +173,103 @@ MercSwapData load_merc_model(u32 current_idx_count,
merc_convert(result, extract_data);
return result;
}
}
std::vector<jak1::CollideMesh> gen_collide_mesh_from_model(
const tinygltf::Model& model,
const std::vector<gltf_util::NodeWithTransform>& all_nodes,
int joint_idx) {
// data for a single primitive
struct PrimWork {
std::string mesh_name;
std::vector<math::Vector4f> verts;
std::vector<u32> indices;
gltf_mesh_extract::PatResult pat;
};
std::vector<std::vector<PrimWork>> mesh_data;
int mesh_count = 0;
// int prim_count = 0;
for (const auto& n : all_nodes) {
const auto& node = model.nodes[n.node_idx];
gltf_mesh_extract::PatResult mesh_default_collide =
gltf_mesh_extract::custom_props_to_pat(node.extras, node.name);
if (node.mesh >= 0) {
const auto& mesh = model.meshes[node.mesh];
mesh_count++;
std::vector<PrimWork> prims;
for (const auto& prim : mesh.primitives) {
// get material
const auto& mat_idx = prim.material;
gltf_mesh_extract::PatResult pat = mesh_default_collide;
if (mat_idx != -1) {
const auto& mat = model.materials[mat_idx];
auto mat_pat = gltf_mesh_extract::custom_props_to_pat(mat.extras, mat.name);
if (mat_pat.set) {
pat = mat_pat;
}
}
if (pat.set && pat.ignore) {
continue; // skip, no collide here
}
auto& prim_data = prims.emplace_back();
prim_data.mesh_name = mesh.name;
prim_data.pat = pat;
// prim_count++;
// extract index buffer
std::vector<u32> prim_indices = gltf_util::gltf_index_buffer(model, prim.indices, 0);
ASSERT_MSG(prim.mode == TINYGLTF_MODE_TRIANGLES,
fmt::format("Mesh {}: Unsupported triangle mode {}", mesh.name, prim.mode));
// extract vertices
auto verts =
gltf_util::gltf_vertices(model, prim.attributes, n.w_T_node, true, true, mesh.name);
ASSERT_MSG(verts.vtx.size() <= 255,
fmt::format("primitive of mesh {} has too many vertices (max 255, actual {})",
mesh.name, verts.vtx.size()));
prim_data.verts.reserve(verts.vtx.size());
for (auto& vert : verts.vtx) {
prim_data.verts.emplace_back(vert.x, vert.y, vert.z, 1.0);
}
prim_data.indices = prim_indices;
mesh_data.push_back(prims);
}
}
}
std::vector<jak1::CollideMesh> cmeshes;
cmeshes.reserve(mesh_count);
// we extracted all of the prim data for each mesh, now combine
for (size_t p = 0; p < mesh_data.size(); p++) {
auto& prims = mesh_data.at(p);
std::vector<math::Vector4f> verts;
std::vector<jak1::CollideMeshTri> tris;
int vert_count = 0;
for (auto& prim : prims) {
if (prim.pat.ignore) {
continue;
}
vert_count += verts.size();
verts.insert(verts.end(), prim.verts.begin(), prim.verts.end());
for (size_t i = 0; i < prim.indices.size(); i += 3) {
auto& tri = tris.emplace_back();
tri.vert_idx[0] = prim.indices.at(i);
tri.vert_idx[1] = prim.indices.at(i + 1);
tri.vert_idx[2] = prim.indices.at(i + 2);
tri.pat = prim.pat.pat;
}
}
ASSERT_MSG(vert_count <= 255, fmt::format("Mesh {} has too many vertices (max 255, actual {})",
prims.at(p).mesh_name, vert_count));
auto& cmesh = cmeshes.emplace_back();
// TODO joint idx as a custom property in blender?
cmesh.joint_id = joint_idx;
cmesh.vertices.reserve(255);
cmesh.vertices.insert(cmesh.vertices.begin(), verts.begin(), verts.end());
cmesh.num_verts = cmesh.vertices.size();
cmesh.num_tris = tris.size();
cmesh.tris.reserve(cmesh.num_tris);
for (size_t j = 0; j < cmesh.num_tris; j++) {
cmesh.tris.push_back(tris.at(j));
}
}
return cmeshes;
}

View file

@ -2,6 +2,8 @@
#include "common/util/gltf_util.h"
#include <goalc/build_actor/jak1/build_actor.h>
struct MercExtractData {
gltf_util::TexturePool tex_pool;
std::vector<u32> new_indices;
@ -32,4 +34,8 @@ MercSwapData load_merc_model(u32 current_idx_count,
u32 current_vtx_count,
u32 current_tex_count,
const std::string& path,
const std::string& name);
const std::string& name);
std::vector<jak1::CollideMesh> gen_collide_mesh_from_model(
const tinygltf::Model& model,
const std::vector<gltf_util::NodeWithTransform>& all_nodes,
int joint_idx);

View file

@ -2,6 +2,8 @@
#include "common/log/log.h"
#include "goalc/build_actor/common/MercExtract.h"
#include "third-party/tiny_gltf/tiny_gltf.h"
using namespace gltf_util;
@ -101,14 +103,77 @@ size_t JointAnimCompressedControl::generate(DataObjectGenerator& gen) const {
return result;
}
size_t ArtJointGeo::generate_res_lump(DataObjectGenerator& gen) const {
size_t CollideMesh::generate(DataObjectGenerator& gen) const {
gen.align_to_basic();
gen.add_type_tag("res-lump");
gen.add_type_tag("collide-mesh");
size_t result = gen.current_offset_bytes();
// gen.add_word(0xffffffff - joint_id); // 4 (joint-id)
gen.add_word(joint_id); // 4 (joint-id)
gen.add_word(num_tris); // 8 (num-tris)
gen.add_word(num_verts); // 12 (num-verts)
auto vertices_slot = gen.add_word(0); // 16 (vertex-data)
gen.add_word(0); // 20 (pad)
gen.add_word(0); // 24 (pad)
gen.add_word(0); // 28 (pad)
for (auto& tri : tris) {
u32 word = (tri.vert_idx[0] & 0xff) + ((tri.vert_idx[1] << 8) & 0xff00) +
((tri.vert_idx[2] << 16) & 0xff0000);
gen.add_word(word); // 0 (tris | vertex-index | unused)
gen.add_word(tri.pat.val); // 4 (pat)
}
// vertex data start
gen.link_word_to_byte(vertices_slot, gen.current_offset_bytes());
for (auto& vert : vertices) {
gen.add_word_float(vert.x());
gen.add_word_float(vert.y());
gen.add_word_float(vert.z());
gen.add_word_float(vert.w());
}
return result;
};
void ArtJointGeo::add_res() {
if (!cmeshes.empty()) {
lump.add_res(
std::make_unique<ResRef>("collide-mesh-group", "array", mesh_slot, DEFAULT_RES_TIME));
}
// jgeo.lump.add_res(
// std::make_unique<ResInt32>("texture-level", std::vector<s32>{2}, DEFAULT_RES_TIME));
// jgeo.lump.add_res(std::make_unique<ResVector>(
// "trans-offset", std::vector<math::Vector4f>{{0.0f, 2048.0f, 0.0f, 1.0f}},
// DEFAULT_RES_TIME));
// jgeo.lump.add_res(
// std::make_unique<ResInt32>("joint-channel", std::vector<s32>{0}, DEFAULT_RES_TIME));
// jgeo.lump.add_res(std::make_unique<ResFloat>(
// "lod-dist", std::vector<float>{5000.0f * METER_LENGTH, 6000.0f * METER_LENGTH},
// DEFAULT_RES_TIME));
lump.sort_res();
}
size_t ArtJointGeo::generate_mesh(DataObjectGenerator& gen) const {
std::vector<size_t> data_slots;
std::vector<size_t> content_slots;
gen.align_to_basic();
gen.add_type_tag("array");
size_t result = gen.current_offset_bytes();
gen.add_word(cmeshes.size()); // 0 (length)
gen.add_word(cmeshes.size()); // 4 (allocated-length)
gen.add_type_tag("collide-mesh"); // 8 (content-type)
content_slots.reserve(cmeshes.size());
for (auto& data : cmeshes) {
content_slots.push_back(gen.add_word(0)); // 12 (data)
}
gen.align(4);
for (size_t i = 0; i < content_slots.size(); i++) {
data_slots.push_back(cmeshes.at(i).generate(gen));
}
for (size_t i = 0; i < content_slots.size(); i++) {
gen.link_word_to_byte(content_slots.at(i), data_slots.at(i));
}
return result;
}
size_t ArtJointGeo::generate(DataObjectGenerator& gen) const {
size_t ArtJointGeo::generate(DataObjectGenerator& gen) {
gen.align_to_basic();
gen.add_type_tag("art-joint-geo");
size_t result = gen.current_offset_bytes();
@ -129,6 +194,8 @@ size_t ArtJointGeo::generate(DataObjectGenerator& gen) const {
gen.link_word_to_byte(joint_slots.at(i), joint);
g_joint_map[data.at(i).number] = joint;
}
mesh_slot = generate_mesh(gen);
add_res();
auto res_header = lump.generate_header(gen, "res-lump");
gen.link_word_to_byte(res_slot, res_header);
lump.generate_tag_list_and_data(gen, res_header);
@ -376,14 +443,28 @@ std::vector<u8> ArtGroup::save_object_file() const {
return gen.generate_v4();
}
bool run_build_actor(const std::string& input_model,
int ArtGroup::get_joint_idx(const std::string& name) {
for (auto& elt : this->elts) {
if (elt && typeid(*elt) == typeid(ArtJointGeo)) {
auto jgeo = (ArtJointGeo*)elt;
for (auto& joint : jgeo->data) {
if (joint.name == name) {
return joint.number;
}
}
}
}
return -1;
}
bool run_build_actor(const std::string& mdl_name,
const std::string& ag_out,
const std::string& output_prefix) {
bool gen_collide_mesh) {
std::string ag_name;
if (fs::exists(file_util::get_jak_project_dir() / input_model)) {
ag_name = fs::path(input_model).stem().string();
if (fs::exists(file_util::get_jak_project_dir() / mdl_name)) {
ag_name = fs::path(mdl_name).stem().string();
} else {
ASSERT_MSG(false, "Model file not found: " + input_model);
ASSERT_MSG(false, "Model file not found: " + mdl_name);
}
ArtGroup ag(ag_name);
@ -411,21 +492,22 @@ bool run_build_actor(const std::string& input_model,
main_pose(3, 3) = 1.0f;
Joint main("main", 2, 1, main_pose);
joints.emplace_back(main);
ArtJointGeo jgeo(ag.name, joints);
std::vector<CollideMesh> mesh;
if (gen_collide_mesh) {
tinygltf::TinyGLTF loader;
tinygltf::Model model;
std::string err, warn;
std::string path = (file_util::get_jak_project_dir() / mdl_name).string();
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 = flatten_nodes_from_all_scenes(model);
mesh = gen_collide_mesh_from_model(model, all_nodes, 3);
}
ArtJointGeo jgeo(ag.name, mesh, joints);
ArtJointAnim ja(ag.name, joints);
jgeo.lump.add_res(
std::make_unique<ResInt32>("texture-level", std::vector<s32>{2}, DEFAULT_RES_TIME));
// jgeo.lump.add_res(std::make_unique<ResVector>(
// "trans-offset", std::vector<math::Vector4f>{{0.0f, 2048.0f, 0.0f, 1.0f}},
// DEFAULT_RES_TIME));
jgeo.lump.add_res(
std::make_unique<ResInt32>("joint-channel", std::vector<s32>{0}, DEFAULT_RES_TIME));
jgeo.lump.add_res(std::make_unique<ResFloat>(
"lod-dist", std::vector<float>{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);

View file

@ -139,23 +139,33 @@ struct CollideMesh {
u32 num_tris;
u32 num_verts;
std::vector<math::Vector4f> vertices;
CollideMeshTri tris;
std::vector<CollideMeshTri> tris;
size_t generate(DataObjectGenerator& gen) const;
size_t calc_data_size() const {
// (size-of collide-mesh) + type ptr = 36
return 36 + 16 * vertices.size() + sizeof(CollideMeshTri) * tris.size();
}
};
struct ArtJointGeo : ArtElement {
std::vector<Joint> data;
CollideMesh mesh;
std::vector<CollideMesh> cmeshes;
ResLump lump;
size_t mesh_slot;
explicit ArtJointGeo(const std::string& name, const std::vector<Joint>& joints) {
explicit ArtJointGeo(const std::string& name,
std::vector<CollideMesh> cmeshes,
std::vector<Joint>& joints) {
this->name = name + "-lod0";
length = joints.size();
for (auto& joint : joints) {
data.push_back(joint);
}
this->cmeshes = std::move(cmeshes);
}
size_t generate(DataObjectGenerator& gen) const;
size_t generate_res_lump(DataObjectGenerator& gen) const;
void add_res();
size_t generate(DataObjectGenerator& gen);
size_t generate_mesh(DataObjectGenerator& gen) const;
};
@ -202,9 +212,10 @@ struct ArtGroup : Art {
info.maya_file_name = "Unknown";
}
std::vector<u8> save_object_file() const;
int get_joint_idx(const std::string& name);
};
bool run_build_actor(const std::string& input_model,
const std::string& output_file,
const std::string& output_prefix);
bool gen_collide_mesh);
} // namespace jak1

View file

@ -13,7 +13,8 @@ int main(int argc, char** argv) {
lg::initialize();
// game version
std::string game, input_model, output_file;
std::string game, mdl_name, output_file;
bool gen_collide_mesh;
fs::path project_path_override;
// path
@ -24,7 +25,7 @@ int main(int argc, char** argv) {
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,
app.add_option("input-model", mdl_name,
"Input model file (for example: custom_assets/jak1/models/test.glb)")
->required();
app.add_option("output-file", output_file,
@ -33,6 +34,7 @@ int main(int argc, char** argv) {
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.add_flag("-m,--mesh", gen_collide_mesh, "Whether to generate a collide-mesh for this model");
app.validate_positionals();
CLI11_PARSE(app, argc, argv)
@ -53,7 +55,7 @@ int main(int argc, char** argv) {
switch (game_version) {
case GameVersion::Jak1:
jak1::run_build_actor(input_model, output_file, "jak1/");
jak1::run_build_actor(mdl_name, output_file, gen_collide_mesh);
break;
default:
ASSERT_NOT_REACHED_MSG("unsupported game version");

View file

@ -240,6 +240,26 @@ int ResType::get_alignment() const {
return 4;
}
ResRef::ResRef(const std::string& name, const std::string& type, size_t ref, float key_frame)
: Res(name, key_frame), m_ref(ref), m_type(type) {}
TagInfo ResRef::get_tag_info() const {
TagInfo result;
result.elt_type = m_type;
result.elt_count = 1;
result.inlined = false;
result.data_size = 4;
return result;
}
void ResRef::write_data(DataObjectGenerator& gen) const {
gen.link_word_to_byte(gen.add_word(0), m_ref);
}
int ResRef::get_alignment() const {
return 4;
}
void ResLump::add_res(std::unique_ptr<Res> res) {
m_sorted = false;
m_res.emplace_back(std::move(res));
@ -345,12 +365,15 @@ void ResLump::generate_tag_list_and_data(DataObjectGenerator& gen, size_t header
current_data_ptr += 4;
gen.add_word(0);
}
res->write_data(gen);
ASSERT(gen.current_offset_bytes() - current_data_ptr == rec.reported_size);
ASSERT_MSG(gen.current_offset_bytes() - current_data_ptr == rec.reported_size,
fmt::format("reported size of {} does not match actual size of {}",
rec.reported_size, gen.current_offset_bytes() - current_data_ptr));
current_data_ptr = gen.current_offset_bytes();
}
ASSERT(gen.current_offset_bytes() == data_end);
ASSERT_MSG(gen.current_offset_bytes() == data_end,
fmt::format("mismatch between current offset ({}) and data end ({})",
gen.current_offset_bytes(), data_end));
ASSERT(data_end == current_data_ptr);
// update header

View file

@ -138,6 +138,19 @@ class ResType : public Res {
std::vector<std::string> m_str;
};
class ResRef : public Res {
public:
ResRef(const std::string& name, const std::string& type, size_t ref, float key_frame);
TagInfo get_tag_info() const override;
void write_data(DataObjectGenerator& gen) const override;
int get_alignment() const override;
private:
size_t m_ref;
std::string m_type;
};
/*
(deftype res-lump (basic)
((length int32 :offset-assert 4)

View file

@ -13,8 +13,6 @@
#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 {
@ -201,12 +199,6 @@ std::optional<std::vector<jak1::CollideFace>> subdivide_face_if_needed(jak1::Col
}
}
struct PatResult {
bool set = false;
bool ignore = false;
jak1::PatSurface pat;
};
PatResult custom_props_to_pat(const tinygltf::Value& val, const std::string& /*debug_name*/) {
PatResult result;
if (!val.IsObject() || !val.Has("set_collision") || !val.Get("set_collision").Get<int>()) {

View file

@ -6,6 +6,8 @@
#include "goalc/build_level/collide/common/collide_common.h"
#include "third-party/tiny_gltf/tiny_gltf.h"
namespace gltf_util {
struct TexturePool;
}
@ -36,6 +38,13 @@ struct Output {
CollideOutput collide;
};
struct PatResult {
bool set = false;
bool ignore = false;
jak1::PatSurface pat;
};
PatResult custom_props_to_pat(const tinygltf::Value& val, const std::string& /*debug_name*/);
void extract(const Input& in, Output& out);
} // namespace gltf_mesh_extract

View file

@ -302,7 +302,7 @@ BuildActorTool::BuildActorTool() : Tool("build-actor") {}
bool BuildActorTool::needs_run(const ToolInput& task, const PathMap& path_map) {
(void)path_map;
if (task.input.size() != 1) {
if (task.input.size() > 2) {
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
}
// std::vector<std::string> deps{};
@ -311,8 +311,10 @@ bool BuildActorTool::needs_run(const ToolInput& task, const PathMap& path_map) {
}
bool BuildActorTool::run(const ToolInput& task, const PathMap& path_map) {
if (task.input.size() != 1) {
(void)path_map;
if (task.input.size() > 2) {
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);
auto gen_mesh = task.input.at(1) == "#t";
return jak1::run_build_actor(task.input.at(0), task.output.at(0), gen_mesh);
}