mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 00:57:44 -04:00
[buildactor] support generating collide-mesh
es 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:
parent
f1de2c9bc5
commit
c64eea6337
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
Binary file not shown.
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>()) {
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
}
|
Loading…
Reference in a new issue