mirror of
https://github.com/open-goal/jak-project
synced 2026-06-04 10:49:04 -04:00
710f3ac117
Custom levels for Jak 2/3 now support envmapped TIE geometry. The TIE extract was also changed to ignore materials that have the specular flag set, but are missing a roughness texture. Jak 2/3 now also support the `build-actor` tool. The `build-custom-level` and `build-actor` macros now have a few new options: - Both now have a `force-run` option (`#f` by default) that, when set to `#t`, will always run level/art group generation even if the output files are up to date. - `build-custom-level` has a `gen-fr3` option (`#t` by default) that, when set to `#f`, will skip generating the FR3 file for the custom level and only generate the GOAL level file to skip the potentially slow process of finding and adding art groups and textures. Useful for when you want to temporarily edit only the GOAL side of the level (such as entity placement, etc.). - `build-actor` has a `texture-bucket` option (default 0) which will determine what DMA sink group the model will be placed in, which is useful to determine the draw order of the model. Previously, this was omitted, resulting in shadows not drawing over custom actors because the actors were put in a bucket that is drawn after shadows (this behavior can be restored with `:texture-bucket #f`).
629 lines
21 KiB
C++
629 lines
21 KiB
C++
/*!
|
|
* Mesh extraction for GLTF meshes.
|
|
*/
|
|
|
|
#include "gltf_mesh_extract.h"
|
|
|
|
#include <optional>
|
|
|
|
#include "color_quantization.h"
|
|
|
|
#include "common/log/log.h"
|
|
#include "common/math/geometry.h"
|
|
#include "common/util/Timer.h"
|
|
#include "common/util/gltf_util.h"
|
|
#include "common/util/image_resize.h"
|
|
|
|
using namespace gltf_util;
|
|
constexpr int kColorTreeDepth = 13;
|
|
namespace gltf_mesh_extract {
|
|
|
|
void dedup_tfrag_vertices(TfragOutput& data) {
|
|
Timer timer;
|
|
size_t original_size = data.tfrag_vertices.size();
|
|
std::vector<tfrag3::PreloadedVertex> new_verts;
|
|
std::vector<u32> old_to_new;
|
|
|
|
gltf_util::dedup_vertices(data.tfrag_vertices, new_verts, old_to_new);
|
|
data.tfrag_vertices = std::move(new_verts);
|
|
|
|
// TODO: properly split vertices between trees...
|
|
for (auto drawlist : {&data.normal_strip_draws, &data.trans_strip_draws}) {
|
|
for (auto& draw : *drawlist) {
|
|
ASSERT(draw.runs.empty()); // not supported yet
|
|
for (auto& idx : draw.plain_indices) {
|
|
idx = old_to_new.at(idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
lg::info("Deduplication took {:.2f} ms, {} -> {} ({:.2f} %)", timer.getMs(), original_size,
|
|
data.tfrag_vertices.size(), 100.f * data.tfrag_vertices.size() / original_size);
|
|
}
|
|
|
|
void dedup_tie_vertices(TieOutput& data) {
|
|
Timer timer;
|
|
size_t original_size = data.vertices.size();
|
|
|
|
std::vector<TieFullVertex> old_verts;
|
|
old_verts.reserve(data.vertices.size());
|
|
for (size_t i = 0; i < data.vertices.size(); i++) {
|
|
auto& x = old_verts.emplace_back();
|
|
x.color_index = data.color_indices[i];
|
|
x.vertex = data.vertices[i];
|
|
}
|
|
|
|
std::vector<TieFullVertex> new_verts;
|
|
std::vector<u32> old_to_new;
|
|
|
|
gltf_util::dedup_vertices(old_verts, new_verts, old_to_new);
|
|
data.vertices.clear();
|
|
data.color_indices.clear();
|
|
data.vertices.reserve(new_verts.size());
|
|
data.color_indices.reserve(new_verts.size());
|
|
for (auto& x : new_verts) {
|
|
data.vertices.push_back(x.vertex);
|
|
data.color_indices.push_back(x.color_index);
|
|
}
|
|
|
|
// TODO: properly split vertices between trees...
|
|
for (auto drawlist : {&data.base_draws, &data.envmap_draws}) {
|
|
for (auto& draw : *drawlist) {
|
|
ASSERT(draw.runs.empty()); // not supported yet
|
|
for (auto& idx : draw.plain_indices) {
|
|
idx = old_to_new.at(idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
lg::info("Deduplication took {:.2f} ms, {} -> {} ({:.2f} %)", timer.getMs(), original_size,
|
|
data.vertices.size(), 100.f * data.vertices.size() / original_size);
|
|
}
|
|
|
|
bool prim_needs_tie(const tinygltf::Model& model, const tinygltf::Primitive& prim) {
|
|
if (prim.material >= 0) {
|
|
auto mat = model.materials.at(prim.material);
|
|
return envmap_is_valid(mat);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void extract(const Input& in,
|
|
TfragOutput& out,
|
|
const tinygltf::Model& model,
|
|
const std::vector<NodeWithTransform>& all_nodes) {
|
|
std::vector<math::Vector<u8, 4>> all_vtx_colors;
|
|
ASSERT(out.tfrag_vertices.empty());
|
|
|
|
struct MaterialInfo {
|
|
tfrag3::StripDraw draw;
|
|
bool needs_tie = false;
|
|
};
|
|
std::map<int, MaterialInfo> info_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.extras.Has("set_invisible") && node.extras.Get("set_invisible").Get<int>()) {
|
|
continue;
|
|
}
|
|
if (node.mesh >= 0) {
|
|
const auto& mesh = model.meshes[node.mesh];
|
|
mesh_count++;
|
|
for (const auto& prim : mesh.primitives) {
|
|
if (prim.material >= 0 && model.materials[prim.material].extras.Has("set_invisible") &&
|
|
model.materials[prim.material].extras.Get("set_invisible").Get<int>()) {
|
|
continue;
|
|
}
|
|
|
|
if (prim_needs_tie(model, prim)) {
|
|
continue;
|
|
}
|
|
prim_count++;
|
|
// extract index buffer
|
|
std::vector<u32> prim_indices =
|
|
gltf_index_buffer(model, prim.indices, out.tfrag_vertices.size());
|
|
ASSERT_MSG(prim.mode == TINYGLTF_MODE_TRIANGLES, "Unsupported triangle mode");
|
|
// extract vertices
|
|
auto verts = gltf_vertices(model, prim.attributes, n.w_T_node, true, false, mesh.name);
|
|
out.tfrag_vertices.insert(out.tfrag_vertices.end(), verts.vtx.begin(), verts.vtx.end());
|
|
all_vtx_colors.insert(all_vtx_colors.end(), verts.vtx_colors.begin(),
|
|
verts.vtx_colors.end());
|
|
ASSERT(all_vtx_colors.size() == out.tfrag_vertices.size());
|
|
|
|
auto& info = info_by_material[prim.material];
|
|
info.draw.mode = make_default_draw_mode(); // todo rm
|
|
info.draw.tree_tex_id = texture_pool_debug_checker(in.tex_pool); // todo rm
|
|
info.draw.num_triangles += prim_indices.size() / 3;
|
|
if (info.draw.vis_groups.empty()) {
|
|
auto& grp = info.draw.vis_groups.emplace_back();
|
|
grp.num_inds += prim_indices.size();
|
|
grp.num_tris += info.draw.num_triangles;
|
|
grp.vis_idx_in_pc_bvh = UINT16_MAX;
|
|
} else {
|
|
auto& grp = info.draw.vis_groups.back();
|
|
grp.num_inds += prim_indices.size();
|
|
grp.num_tris += info.draw.num_triangles;
|
|
grp.vis_idx_in_pc_bvh = UINT16_MAX;
|
|
}
|
|
|
|
info.draw.plain_indices.insert(info.draw.plain_indices.end(), prim_indices.begin(),
|
|
prim_indices.end());
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto& [mat_idx, d_] : info_by_material) {
|
|
// out.strip_draws.push_back(d_);
|
|
// auto& draw = out.strip_draws.back();
|
|
tfrag3::StripDraw draw = d_.draw;
|
|
draw.mode = make_default_draw_mode();
|
|
|
|
if (mat_idx == -1) {
|
|
lg::warn("Draw had a material index of -1, using default texture.");
|
|
draw.tree_tex_id = texture_pool_debug_checker(in.tex_pool);
|
|
out.normal_strip_draws.push_back(draw);
|
|
continue;
|
|
}
|
|
|
|
const auto& mat = model.materials[mat_idx];
|
|
setup_alpha_from_material(mat, &draw.mode);
|
|
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 = texture_pool_debug_checker(in.tex_pool);
|
|
if (draw.mode.get_ab_enable()) {
|
|
out.trans_strip_draws.push_back(draw);
|
|
} else {
|
|
out.normal_strip_draws.push_back(draw);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const auto& tex = model.textures[tex_idx];
|
|
ASSERT(tex.sampler >= 0);
|
|
ASSERT(tex.source >= 0);
|
|
setup_draw_mode_from_sampler(model.samplers.at(tex.sampler), &draw.mode);
|
|
|
|
const auto& img = model.images[tex.source];
|
|
draw.tree_tex_id = texture_pool_add_texture(in.tex_pool, img);
|
|
|
|
if (draw.mode.get_ab_enable()) {
|
|
out.trans_strip_draws.push_back(draw);
|
|
} else {
|
|
out.normal_strip_draws.push_back(draw);
|
|
}
|
|
}
|
|
lg::info("total of {} normal, {} transparent unique materials", out.normal_strip_draws.size(),
|
|
out.trans_strip_draws.size());
|
|
|
|
lg::info("Merged {} meshes and {} prims into {} vertices", mesh_count, prim_count,
|
|
out.tfrag_vertices.size());
|
|
|
|
Timer quantize_timer;
|
|
auto quantized = quantize_colors_kd_tree(all_vtx_colors, kColorTreeDepth);
|
|
for (size_t i = 0; i < out.tfrag_vertices.size(); i++) {
|
|
out.tfrag_vertices[i].color_index = quantized.vtx_to_color[i];
|
|
}
|
|
out.color_palette = std::move(quantized.final_colors);
|
|
lg::info("Color palette generation took {:.2f} ms", quantize_timer.getMs());
|
|
|
|
dedup_tfrag_vertices(out);
|
|
}
|
|
|
|
s8 normal_to_s8(float in) {
|
|
s32 in_s32 = in * 127.f;
|
|
ASSERT(in_s32 <= INT8_MAX);
|
|
ASSERT(in_s32 >= INT8_MIN);
|
|
return in_s32;
|
|
}
|
|
|
|
void add_to_packed_verts(std::vector<tfrag3::PackedTieVertices::Vertex>* out,
|
|
const std::vector<tfrag3::PreloadedVertex>& vtx,
|
|
const std::vector<math::Vector3f>& normals) {
|
|
ASSERT(vtx.size() == normals.size());
|
|
for (size_t i = 0; i < normals.size(); i++) {
|
|
auto& x = out->emplace_back();
|
|
// currently not supported.
|
|
x.r = 255;
|
|
x.g = 255;
|
|
x.b = 255;
|
|
x.a = 255;
|
|
|
|
x.x = vtx[i].x;
|
|
x.y = vtx[i].y;
|
|
x.z = vtx[i].z;
|
|
|
|
x.s = vtx[i].s;
|
|
x.t = vtx[i].t;
|
|
|
|
x.nx = normal_to_s8(normals[i].x());
|
|
x.ny = normal_to_s8(normals[i].y());
|
|
x.nz = normal_to_s8(normals[i].z());
|
|
}
|
|
}
|
|
|
|
void extract(const Input& in,
|
|
TieOutput& out,
|
|
const tinygltf::Model& model,
|
|
const std::vector<NodeWithTransform>& all_nodes) {
|
|
std::vector<math::Vector<u8, 4>> all_vtx_colors;
|
|
|
|
struct MaterialInfo {
|
|
tfrag3::StripDraw draw;
|
|
bool needs_tie = false;
|
|
};
|
|
std::map<int, MaterialInfo> info_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.extras.Has("set_invisible") && node.extras.Get("set_invisible").Get<int>()) {
|
|
continue;
|
|
}
|
|
if (node.mesh >= 0) {
|
|
const auto& mesh = model.meshes[node.mesh];
|
|
mesh_count++;
|
|
for (const auto& prim : mesh.primitives) {
|
|
if (prim.material >= 0 && model.materials[prim.material].extras.Has("set_invisible") &&
|
|
model.materials[prim.material].extras.Get("set_invisible").Get<int>()) {
|
|
continue;
|
|
}
|
|
|
|
if (!prim_needs_tie(model, prim)) {
|
|
continue;
|
|
}
|
|
prim_count++;
|
|
// extract index buffer
|
|
std::vector<u32> prim_indices = gltf_index_buffer(model, prim.indices, out.vertices.size());
|
|
ASSERT_MSG(prim.mode == TINYGLTF_MODE_TRIANGLES, "Unsupported triangle mode");
|
|
// extract vertices
|
|
auto verts = gltf_vertices(model, prim.attributes, n.w_T_node, true, true, mesh.name);
|
|
add_to_packed_verts(&out.vertices, verts.vtx, verts.normals);
|
|
all_vtx_colors.insert(all_vtx_colors.end(), verts.vtx_colors.begin(),
|
|
verts.vtx_colors.end());
|
|
ASSERT(all_vtx_colors.size() == out.vertices.size());
|
|
|
|
auto& info = info_by_material[prim.material];
|
|
info.draw.mode = make_default_draw_mode(); // todo rm
|
|
info.draw.tree_tex_id = texture_pool_debug_checker(in.tex_pool); // todo rm
|
|
info.draw.num_triangles += prim_indices.size() / 3;
|
|
if (info.draw.vis_groups.empty()) {
|
|
auto& grp = info.draw.vis_groups.emplace_back();
|
|
grp.num_inds += prim_indices.size();
|
|
grp.num_tris += info.draw.num_triangles;
|
|
grp.vis_idx_in_pc_bvh = UINT16_MAX;
|
|
} else {
|
|
auto& grp = info.draw.vis_groups.back();
|
|
grp.num_inds += prim_indices.size();
|
|
grp.num_tris += info.draw.num_triangles;
|
|
grp.vis_idx_in_pc_bvh = UINT16_MAX;
|
|
}
|
|
|
|
info.draw.plain_indices.insert(info.draw.plain_indices.end(), prim_indices.begin(),
|
|
prim_indices.end());
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto& [mat_idx, d_] : info_by_material) {
|
|
// out.strip_draws.push_back(d_);
|
|
// auto& draw = out.strip_draws.back();
|
|
tfrag3::StripDraw draw = d_.draw;
|
|
draw.mode = make_default_draw_mode();
|
|
|
|
if (mat_idx == -1) {
|
|
lg::warn("Draw had a material index of -1, using default texture.");
|
|
draw.tree_tex_id = texture_pool_debug_checker(in.tex_pool);
|
|
out.base_draws.push_back(draw);
|
|
continue;
|
|
}
|
|
|
|
const auto& mat = model.materials[mat_idx];
|
|
setup_alpha_from_material(mat, &draw.mode);
|
|
int base_tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index;
|
|
if (base_tex_idx == -1) {
|
|
lg::warn("Material {} has no texture, using default texture.", mat.name);
|
|
draw.tree_tex_id = texture_pool_debug_checker(in.tex_pool);
|
|
out.base_draws.push_back(draw);
|
|
continue;
|
|
}
|
|
int roughness_tex_idx = mat.pbrMetallicRoughness.metallicRoughnessTexture.index;
|
|
ASSERT(roughness_tex_idx >= 0);
|
|
const auto& base_tex = model.textures[base_tex_idx];
|
|
ASSERT(base_tex.sampler >= 0);
|
|
ASSERT(base_tex.source >= 0);
|
|
setup_draw_mode_from_sampler(model.samplers.at(base_tex.sampler), &draw.mode);
|
|
const auto& roughness_tex = model.textures.at(roughness_tex_idx);
|
|
ASSERT(roughness_tex.sampler >= 0);
|
|
ASSERT(roughness_tex.source >= 0);
|
|
|
|
// draw.tree_tex_id = texture_pool_add_texture(in.tex_pool, model.images[base_tex.source]);
|
|
draw.tree_tex_id = texture_pool_add_envmap_control_texture(
|
|
in.tex_pool, model, base_tex.source, roughness_tex.source, !draw.mode.get_clamp_s_enable(),
|
|
!draw.mode.get_clamp_t_enable());
|
|
out.base_draws.push_back(draw);
|
|
|
|
// now, setup envmap draw:
|
|
auto envmap_settings = envmap_settings_from_gltf(mat);
|
|
const auto& envmap_tex = model.textures[envmap_settings.texture_idx];
|
|
ASSERT(envmap_tex.sampler >= 0);
|
|
ASSERT(envmap_tex.source >= 0);
|
|
draw.mode = make_default_draw_mode();
|
|
setup_draw_mode_from_sampler(model.samplers.at(envmap_tex.sampler), &draw.mode);
|
|
draw.tree_tex_id = texture_pool_add_texture(in.tex_pool, model.images[envmap_tex.source]);
|
|
draw.mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_DST_DST);
|
|
draw.mode.enable_ab();
|
|
|
|
out.envmap_draws.push_back(draw);
|
|
}
|
|
lg::info("total of {} normal TIE draws, {} envmap", out.base_draws.size(),
|
|
out.envmap_draws.size());
|
|
|
|
lg::info("Merged {} meshes and {} prims into {} vertices", mesh_count, prim_count,
|
|
out.vertices.size());
|
|
|
|
Timer quantize_timer;
|
|
auto quantized = quantize_colors_kd_tree(all_vtx_colors, kColorTreeDepth);
|
|
for (size_t i = 0; i < out.vertices.size(); i++) {
|
|
out.color_indices.push_back(quantized.vtx_to_color[i]);
|
|
}
|
|
out.color_palette = std::move(quantized.final_colors);
|
|
lg::info("Color palette generation took {:.2f} ms", quantize_timer.getMs());
|
|
|
|
dedup_tie_vertices(out);
|
|
}
|
|
|
|
std::optional<std::vector<jak1::CollideFace>> subdivide_face_if_needed(jak1::CollideFace face_in) {
|
|
math::Vector3f v_min = face_in.v[0];
|
|
v_min.min_in_place(face_in.v[1]);
|
|
v_min.min_in_place(face_in.v[2]);
|
|
v_min -= 16.f;
|
|
bool needs_subdiv = false;
|
|
for (auto& vert : face_in.v) {
|
|
if ((vert - v_min).squared_length() > 154.f * 154.f * 4096.f * 4096.f) {
|
|
needs_subdiv = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (needs_subdiv) {
|
|
math::Vector3f a = (face_in.v[0] + face_in.v[1]) * 0.5f;
|
|
math::Vector3f b = (face_in.v[1] + face_in.v[2]) * 0.5f;
|
|
math::Vector3f c = (face_in.v[2] + face_in.v[0]) * 0.5f;
|
|
math::Vector3f v0 = face_in.v[0];
|
|
math::Vector3f v1 = face_in.v[1];
|
|
math::Vector3f v2 = face_in.v[2];
|
|
jak1::CollideFace fs[4];
|
|
fs[0].v[0] = v0;
|
|
fs[0].v[1] = a;
|
|
fs[0].v[2] = c;
|
|
fs[0].bsphere = math::bsphere_of_triangle(face_in.v);
|
|
|
|
fs[1].v[0] = a;
|
|
fs[1].v[1] = v1;
|
|
fs[1].v[2] = b;
|
|
fs[1].bsphere = math::bsphere_of_triangle(fs[1].v);
|
|
fs[1].pat = face_in.pat;
|
|
|
|
fs[2].v[0] = a;
|
|
fs[2].v[1] = b;
|
|
fs[2].v[2] = c;
|
|
fs[2].bsphere = math::bsphere_of_triangle(fs[2].v);
|
|
fs[2].pat = face_in.pat;
|
|
|
|
fs[3].v[0] = b;
|
|
fs[3].v[1] = v2;
|
|
fs[3].v[2] = c;
|
|
fs[3].bsphere = math::bsphere_of_triangle(fs[3].v);
|
|
fs[3].pat = face_in.pat;
|
|
|
|
std::vector<jak1::CollideFace> result;
|
|
for (auto f : fs) {
|
|
auto next_faces = subdivide_face_if_needed(f);
|
|
if (next_faces) {
|
|
result.insert(result.end(), next_faces->begin(), next_faces->end());
|
|
} else {
|
|
result.push_back(f);
|
|
}
|
|
}
|
|
return result;
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
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>()) {
|
|
// unset.
|
|
result.set = false;
|
|
return result;
|
|
}
|
|
|
|
result.set = true;
|
|
|
|
if (val.Get("ignore").Get<int>()) {
|
|
result.ignore = true;
|
|
return result;
|
|
}
|
|
result.ignore = false;
|
|
|
|
int mat = val.Get("collide_material").Get<int>();
|
|
ASSERT(mat < (int)jak1::PatSurface::Material::MAX_MATERIAL);
|
|
result.pat.set_material(jak1::PatSurface::Material(mat));
|
|
|
|
int evt = val.Get("collide_event").Get<int>();
|
|
ASSERT(evt < (int)jak1::PatSurface::Event::MAX_EVENT);
|
|
result.pat.set_event(jak1::PatSurface::Event(evt));
|
|
|
|
if (val.Get("nolineofsight").Get<int>()) {
|
|
result.pat.set_nolineofsight(true);
|
|
}
|
|
|
|
if (val.Get("noedge").Get<int>()) {
|
|
result.pat.set_noedge(true);
|
|
}
|
|
|
|
if (val.Has("collide_mode")) {
|
|
int mode = val.Get("collide_mode").Get<int>();
|
|
ASSERT(mode < (int)jak1::PatSurface::Mode::MAX_MODE);
|
|
result.pat.set_mode(jak1::PatSurface::Mode(mode));
|
|
}
|
|
|
|
if (val.Get("nocamera").Get<int>()) {
|
|
result.pat.set_nocamera(true);
|
|
}
|
|
|
|
if (val.Get("noentity").Get<int>()) {
|
|
result.pat.set_noentity(true);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void extract(const Input& in,
|
|
CollideOutput& out,
|
|
const tinygltf::Model& model,
|
|
const std::vector<NodeWithTransform>& all_nodes) {
|
|
[[maybe_unused]] int mesh_count = 0;
|
|
[[maybe_unused]] int prim_count = 0;
|
|
int suspicious_faces = 0;
|
|
|
|
for (const auto& n : all_nodes) {
|
|
const auto& node = model.nodes[n.node_idx];
|
|
PatResult mesh_default_collide = 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;
|
|
PatResult pat = mesh_default_collide;
|
|
if (mat_idx != -1) {
|
|
const auto& mat = model.materials[mat_idx];
|
|
auto mat_pat = 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_index_buffer(model, prim.indices, 0);
|
|
ASSERT_MSG(prim.mode == TINYGLTF_MODE_TRIANGLES, "Unsupported triangle mode");
|
|
// extract vertices
|
|
auto verts = gltf_vertices(model, prim.attributes, n.w_T_node, false, true, mesh.name);
|
|
|
|
for (size_t iidx = 0; iidx < prim_indices.size(); iidx += 3) {
|
|
jak1::CollideFace face;
|
|
|
|
// get the positions
|
|
for (int j = 0; j < 3; j++) {
|
|
auto& vtx = verts.vtx.at(prim_indices.at(iidx + j));
|
|
face.v[j].x() = vtx.x;
|
|
face.v[j].y() = vtx.y;
|
|
face.v[j].z() = vtx.z;
|
|
}
|
|
|
|
// now face normal
|
|
math::Vector3f face_normal =
|
|
(face.v[2] - face.v[0]).cross(face.v[1] - face.v[0]).normalized();
|
|
|
|
float dots[3];
|
|
for (int j = 0; j < 3; j++) {
|
|
dots[j] = face_normal.dot(verts.normals.at(prim_indices.at(iidx + j)).normalized());
|
|
}
|
|
|
|
if (dots[0] > 1e-3 && dots[1] > 1e-3 && dots[2] > 1e-3) {
|
|
suspicious_faces++;
|
|
auto temp = face.v[2];
|
|
face.v[2] = face.v[1];
|
|
face.v[1] = temp;
|
|
}
|
|
|
|
face.bsphere = math::bsphere_of_triangle(face.v);
|
|
face.bsphere.w() += 1e-1 * 5;
|
|
for (int j = 0; j < 3; j++) {
|
|
float output_dist = face.bsphere.w() - (face.bsphere.xyz() - face.v[j]).length();
|
|
if (output_dist < 0) {
|
|
lg::print("{}\n", output_dist);
|
|
lg::print("BAD:\n{}\n{}\n{}\n", face.v[0].to_string_aligned(),
|
|
face.v[1].to_string_aligned(), face.v[2].to_string_aligned());
|
|
lg::print("bsphere: {}\n", face.bsphere.to_string_aligned());
|
|
}
|
|
}
|
|
face.pat = pat.pat;
|
|
out.faces.push_back(face);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<jak1::CollideFace> fixed_faces;
|
|
int fix_count = 0;
|
|
for (auto& face : out.faces) {
|
|
auto try_fix = subdivide_face_if_needed(face);
|
|
if (try_fix) {
|
|
fix_count++;
|
|
fixed_faces.insert(fixed_faces.end(), try_fix->begin(), try_fix->end());
|
|
} else {
|
|
fixed_faces.push_back(face);
|
|
}
|
|
}
|
|
|
|
if (in.double_sided_collide) {
|
|
size_t os = fixed_faces.size();
|
|
for (size_t i = 0; i < os; i++) {
|
|
auto f0 = fixed_faces.at(i);
|
|
std::swap(f0.v[0], f0.v[1]);
|
|
fixed_faces.push_back(f0);
|
|
}
|
|
}
|
|
|
|
out.faces = std::move(fixed_faces);
|
|
|
|
if (in.auto_wall_enable) {
|
|
lg::info("automatically detecting walls with angle {}", in.auto_wall_angle);
|
|
int wall_count = 0;
|
|
float wall_cos = std::cos(in.auto_wall_angle * 2.f * 3.14159 / 360.f);
|
|
for (auto& face : out.faces) {
|
|
math::Vector3f face_normal =
|
|
(face.v[1] - face.v[0]).cross(face.v[2] - face.v[0]).normalized();
|
|
if (face_normal[1] < wall_cos) {
|
|
face.pat.set_mode(jak1::PatSurface::Mode::WALL);
|
|
wall_count++;
|
|
}
|
|
}
|
|
lg::info("automatic wall: {}/{} converted to walls", wall_count, out.faces.size());
|
|
}
|
|
|
|
lg::info("{} out of {} faces appeared to have wrong orientation and were flipped",
|
|
suspicious_faces, out.faces.size());
|
|
lg::info("{} faces were too big and were subdivided", fix_count);
|
|
// lg::info("Collision extract{} {}", mesh_count, prim_count);
|
|
}
|
|
|
|
void extract(const Input& in, Output& out) {
|
|
lg::info("Reading gltf mesh: {}", in.filename);
|
|
Timer read_timer;
|
|
tinygltf::TinyGLTF loader;
|
|
tinygltf::Model model;
|
|
std::string err, warn;
|
|
bool res = loader.LoadBinaryFromFile(&model, &err, &warn, in.filename);
|
|
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);
|
|
extract(in, out.tfrag, model, all_nodes);
|
|
extract(in, out.collide, model, all_nodes);
|
|
extract(in, out.tie, model, all_nodes);
|
|
lg::info("GLTF total took {:.2f} ms", read_timer.getMs());
|
|
}
|
|
} // namespace gltf_mesh_extract
|