mirror of
https://github.com/open-goal/jak-project
synced 2026-06-28 03:03:29 -04:00
5458d865d4
A few improvements to color palette selection. These were made by tracing some particularly bad colors through and seeing where it made obviously bad decisions for splitting. I tested on crystal cave, and a test GLBs from Kuitar that previously had issues with alpha. - The previous approach to splitting was based on trying to keep a tree of deduplicated colors balanced (same count in each leaf). This is not really a good idea for generating color palettes. A better approach is to try to minimize the volume of the child node, limiting how inaccurate a color can be. Splitting is now chosen based on the average of the _deduplicated channel values_, which in practice seems to work pretty well for Kuitar's levels. Other approaches could work here too. - The previous approach of alternating through dimensions to split on was kept. - The depth of the KD tree during the initial split was increased, allowing it to use up to 8192 colors, instead of just 1024. - In most cases, not all child nodes of the tree have colors in them, meaning that a tree of depth 13 would have less than 8192 colors. If this happens, child nodes are split until the color count reaches 8192. The selection of which nodes to split is somewhat arbitrary, but is breadth-first. The axis for splitting is the one with the largest range. (which might be a better idea in general?) On crystal-cave, the worst case color error was reduced from 221 to 9. --------- Co-authored-by: water111 <awaterford1111445@gmail.com>
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 mat.extensions.contains("KHR_materials_specular");
|
|
}
|
|
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
|