metaforce/hecl/lib/Blender/MeshOptimizer.cpp

440 lines
14 KiB
C++

#include "MeshOptimizer.hpp"
#include <algorithm>
#include <cfloat>
#include <climits>
#include <cmath>
#include <numeric>
#include <unordered_set>
namespace hecl::blender {
logvisor::Module Log("MeshOptimizer");
template <typename T>
static void insert_unique_attr(std::unordered_map<T, uint32_t>& set, const T& attr) {
if (set.find(attr) == set.cend()) {
size_t sz = set.size();
set.insert(std::make_pair(attr, sz));
}
}
template <typename T>
static std::vector<T> sort_unordered_map(const std::unordered_map<T, uint32_t>& map) {
struct SortableIterator {
typename std::unordered_map<T, uint32_t>::const_iterator it;
bool operator<(const SortableIterator& other) const { return it->second < other.it->second; }
explicit SortableIterator(typename std::unordered_map<T, uint32_t>::const_iterator i) : it(i) {}
};
std::vector<SortableIterator> to_sort;
to_sort.reserve(map.size());
for (auto I = map.cbegin(), E = map.cend(); I != E; ++I)
to_sort.emplace_back(I);
std::sort(to_sort.begin(), to_sort.end());
std::vector<T> ret;
ret.reserve(to_sort.size());
for (const auto& sit : to_sort)
ret.push_back(sit.it->first);
return ret;
}
static bool material_is_lightmapped(const Material& mat) {
auto search = mat.iprops.find("retro_lightmapped");
if (search != mat.iprops.cend())
return search->second;
return false;
}
MeshOptimizer::Vertex::Vertex(Connection& conn) {
co.read(conn);
uint32_t skin_count;
conn._readValue(skin_count);
if (skin_count > MaxSkinEntries)
Log.report(logvisor::Fatal, FMT_STRING("Skin entry overflow {}/{}"), skin_count, MaxSkinEntries);
for (uint32_t i = 0; i < skin_count; ++i)
skin_ents[i] = Mesh::SkinBind(conn);
}
MeshOptimizer::Loop::Loop(Connection& conn, uint32_t color_count, uint32_t uv_count) {
normal.read(conn);
for (uint32_t i = 0; i < color_count; ++i)
colors[i].read(conn);
for (uint32_t i = 0; i < uv_count; ++i)
uvs[i].read(conn);
conn._readValue(vert);
conn._readValue(edge);
conn._readValue(face);
conn._readValue(link_loop_next);
conn._readValue(link_loop_prev);
conn._readValue(link_loop_radial_next);
conn._readValue(link_loop_radial_prev);
}
MeshOptimizer::Edge::Edge(Connection& conn) {
for (uint32_t i = 0; i < 2; ++i)
conn._readValue(verts[i]);
uint32_t face_count;
conn._readValue(face_count);
if (face_count > MaxLinkFaces)
Log.report(logvisor::Fatal, FMT_STRING("Face overflow {}/{}"), face_count, MaxLinkFaces);
for (uint32_t i = 0; i < face_count; ++i)
conn._readValue(link_faces[i]);
conn._readValue(is_contiguous);
}
MeshOptimizer::Face::Face(Connection& conn) {
normal.read(conn);
centroid.read(conn);
conn._readValue(material_index);
for (uint32_t i = 0; i < 3; ++i)
conn._readValue(loops[i]);
}
uint32_t MeshOptimizer::get_pos_idx(const Vertex& v) const {
auto search = b_pos.find(v.co);
if (search != b_pos.cend())
return search->second;
return UINT32_MAX;
}
uint32_t MeshOptimizer::get_norm_idx(const Loop& l) const {
auto search = b_norm.find(l.normal);
if (search != b_norm.cend())
return search->second;
return UINT32_MAX;
}
uint32_t MeshOptimizer::get_skin_idx(const Vertex& v) const {
auto search = b_skin.find(v.skin_ents);
if (search != b_skin.cend())
return search->second;
return UINT32_MAX;
}
uint32_t MeshOptimizer::get_color_idx(const Loop& l, uint32_t cidx) const {
auto search = b_color.find(l.colors[cidx]);
if (search != b_color.cend())
return search->second;
return UINT32_MAX;
}
uint32_t MeshOptimizer::get_uv_idx(const Loop& l, uint32_t uidx) const {
if (use_luvs && uidx == 0 && material_is_lightmapped(materials[faces[l.face].material_index])) {
auto search = b_luv.find(l.uvs[0]);
if (search != b_luv.cend())
return search->second;
return UINT32_MAX;
}
auto search = b_uv.find(l.uvs[uidx]);
if (search != b_uv.cend())
return search->second;
return UINT32_MAX;
}
bool MeshOptimizer::loops_contiguous(const Loop& la, const Loop& lb) const {
if (la.vert != lb.vert)
return false;
if (get_norm_idx(la) != get_norm_idx(lb))
return false;
for (uint32_t i = 0; i < color_count; ++i)
if (get_color_idx(la, i) != get_color_idx(lb, i))
return false;
for (uint32_t i = 0; i < uv_count; ++i)
if (get_uv_idx(la, i) != get_uv_idx(lb, i))
return false;
return true;
}
bool MeshOptimizer::splitable_edge(const Edge& e) const {
if (!e.is_contiguous)
return false;
for (uint32_t vidx : e.verts) {
const Loop* found = nullptr;
for (uint32_t fidx : e.link_faces) {
for (uint32_t lidx : faces[fidx].loops) {
if (loops[lidx].vert == vidx) {
if (!found) {
found = &loops[lidx];
break;
} else {
if (!loops_contiguous(*found, loops[lidx]))
return true;
break;
}
}
}
}
}
return false;
}
void MeshOptimizer::sort_faces_by_skin_group(std::vector<uint32_t>& sfaces) const {
std::vector<uint32_t> faces_out;
faces_out.reserve(sfaces.size());
std::unordered_set<uint32_t> done_sg;
uint32_t ref_sg = UINT32_MAX;
while (faces_out.size() < sfaces.size()) {
for (uint32_t f : sfaces) {
bool found = false;
for (uint32_t l : faces[f].loops) {
uint32_t skin_idx = get_skin_idx(verts[loops[l].vert]);
if (done_sg.find(skin_idx) == done_sg.end()) {
ref_sg = skin_idx;
done_sg.insert(skin_idx);
found = true;
break;
}
}
if (found)
break;
}
for (uint32_t f : sfaces) {
if (std::find(faces_out.begin(), faces_out.end(), f) != faces_out.end())
continue;
for (uint32_t l : faces[f].loops) {
uint32_t skin_idx = get_skin_idx(verts[loops[l].vert]);
if (skin_idx == ref_sg) {
faces_out.push_back(f);
break;
}
}
}
}
sfaces = std::move(faces_out);
}
std::pair<uint32_t, uint32_t> MeshOptimizer::strip_next_loop(uint32_t prev_loop, uint32_t out_count) const {
if (out_count & 0x1) {
uint32_t radial_loop = loops[prev_loop].link_loop_radial_next;
uint32_t loop = loops[radial_loop].link_loop_prev;
return {loop, loop};
} else {
uint32_t radial_loop = loops[prev_loop].link_loop_radial_prev;
uint32_t loop = loops[radial_loop].link_loop_next;
return {loops[loop].link_loop_next, loop};
}
}
static float Magnitude(const Vector3f& v) { return std::sqrt(v.val.simd.dot3(v.val.simd)); }
static void Normalize(Vector3f& v) {
float mag = 1.f / Magnitude(v);
v.val.simd *= athena::simd<float>(mag);
}
Mesh::Surface MeshOptimizer::generate_surface(std::vector<uint32_t>& island_faces, uint32_t mat_idx) const {
Mesh::Surface ret = {};
ret.materialIdx = mat_idx;
/* Centroid of surface */
for (const auto& f : island_faces)
ret.centroid.val.simd += faces[f].centroid.val.simd;
ret.centroid.val.simd /= athena::simd<float>(island_faces.size());
/* AABB of surface */
ret.aabbMin.val.simd = athena::simd<float>(FLT_MAX);
ret.aabbMax.val.simd = athena::simd<float>(-FLT_MAX);
for (const auto& f : island_faces) {
for (const auto l : faces[f].loops) {
const Vertex& v = verts[loops[l].vert];
for (int c = 0; c < 3; ++c) {
if (v.co.val.simd[c] < ret.aabbMin.val.simd[c])
ret.aabbMin.val.simd[c] = v.co.val.simd[c];
if (v.co.val.simd[c] > ret.aabbMax.val.simd[c])
ret.aabbMax.val.simd[c] = v.co.val.simd[c];
}
}
}
/* Average normal of surface */
for (const auto& f : island_faces)
ret.reflectionNormal.val.simd += faces[f].normal.val.simd;
Normalize(ret.reflectionNormal);
/* Verts themselves */
uint32_t prev_loop_emit = UINT32_MAX;
std::vector<std::pair<std::vector<uint32_t>, std::vector<uint32_t>>> sel_lists_local;
sel_lists_local.reserve(loops.size());
while (island_faces.size()) {
sel_lists_local.clear();
for (uint32_t start_face : island_faces) {
for (uint32_t l : faces[start_face].loops) {
std::vector<uint32_t> island_local(island_faces);
uint32_t prev_loop = loops[l].link_loop_next;
uint32_t loop = loops[prev_loop].link_loop_next;
std::vector<uint32_t> sel_list;
sel_list.reserve(64);
sel_list.push_back(l);
sel_list.push_back(prev_loop);
sel_list.push_back(loop);
island_local.erase(std::find(island_local.begin(), island_local.end(), start_face));
while (true) {
const Edge& prev_edge = edges[loops[prev_loop].edge];
if (!prev_edge.is_contiguous || prev_edge.tag)
break;
std::tie(loop, prev_loop) = strip_next_loop(prev_loop, sel_list.size());
uint32_t face = loops[loop].face;
auto search = std::find(island_local.begin(), island_local.end(), face);
if (search == island_local.end())
break;
sel_list.push_back(loop);
island_local.erase(search);
}
sel_lists_local.emplace_back(std::move(sel_list), std::move(island_local));
}
}
uint32_t max_count = 0;
const std::vector<uint32_t>* max_sl = nullptr;
const std::vector<uint32_t>* max_island_faces = nullptr;
for (const auto& sl : sel_lists_local) {
if (sl.first.size() > max_count) {
max_count = sl.first.size();
max_sl = &sl.first;
max_island_faces = &sl.second;
}
}
assert(max_island_faces && "Should not be null");
assert(max_island_faces->size() < island_faces.size() && "Infinite loop condition");
island_faces = std::move(*max_island_faces);
if (prev_loop_emit != UINT32_MAX)
ret.verts.emplace_back();
for (uint32_t loop : *max_sl) {
ret.verts.emplace_back();
const auto& l = loops[loop];
auto& vert = ret.verts.back();
vert.iPos = get_pos_idx(verts[l.vert]);
vert.iNorm = get_norm_idx(l);
for (uint32_t i = 0; i < color_count; ++i)
vert.iColor[i] = get_color_idx(l, i);
for (uint32_t i = 0; i < uv_count; ++i)
vert.iUv[i] = get_uv_idx(l, i);
vert.iSkin = get_skin_idx(verts[l.vert]);
prev_loop_emit = loop;
}
}
return ret;
}
void MeshOptimizer::optimize(Mesh& mesh, int max_skin_banks) const {
mesh.topology = HMDLTopology::TriStrips;
mesh.pos = sort_unordered_map(b_pos);
mesh.norm = sort_unordered_map(b_norm);
mesh.colorLayerCount = color_count;
mesh.color = sort_unordered_map(b_color);
mesh.uvLayerCount = uv_count;
mesh.uv = sort_unordered_map(b_uv);
mesh.luv = sort_unordered_map(b_luv);
mesh.skins = sort_unordered_map(b_skin);
/* Sort materials by pass index */
std::vector<uint32_t> sorted_material_idxs(materials.size());
std::iota(sorted_material_idxs.begin(), sorted_material_idxs.end(), 0);
std::sort(sorted_material_idxs.begin(), sorted_material_idxs.end(),
[this](uint32_t a, uint32_t b) { return materials[a].passIndex < materials[b].passIndex; });
/* Generate island surfaces */
std::vector<uint32_t> mat_faces_rem, the_list;
mat_faces_rem.reserve(faces.size());
the_list.reserve(faces.size());
std::unordered_set<uint32_t> skin_slot_set;
skin_slot_set.reserve(b_skin.size());
for (uint32_t mat_idx : sorted_material_idxs) {
const auto& mat = materials[mat_idx];
mat_faces_rem.clear();
for (auto B = faces.begin(), I = B, E = faces.end(); I != E; ++I) {
if (I->material_index == mat_idx)
mat_faces_rem.push_back(I - B);
}
if (b_skin.size())
sort_faces_by_skin_group(mat_faces_rem);
size_t rem_count = mat_faces_rem.size();
while (rem_count) {
the_list.clear();
skin_slot_set.clear();
for (uint32_t& f : mat_faces_rem) {
if (f == UINT32_MAX)
continue;
if (b_skin.size()) {
bool brk = false;
for (const auto l : faces[f].loops) {
const Vertex& v = verts[loops[l].vert];
uint32_t skin_idx = get_skin_idx(v);
if (skin_slot_set.find(skin_idx) == skin_slot_set.end()) {
if (max_skin_banks > 0 && skin_slot_set.size() == size_t(max_skin_banks)) {
brk = true;
break;
}
skin_slot_set.insert(skin_idx);
}
}
if (brk)
break;
}
the_list.push_back(f);
f = UINT32_MAX;
--rem_count;
}
mesh.surfaces.push_back(generate_surface(the_list, mat_idx));
}
}
}
MeshOptimizer::MeshOptimizer(Connection& conn, const std::vector<Material>& materials, bool use_luvs)
: materials(materials), use_luvs(use_luvs) {
conn._readValue(color_count);
if (color_count > MaxColorLayers)
Log.report(logvisor::Fatal, FMT_STRING("Color layer overflow {}/{}"), color_count, MaxColorLayers);
conn._readValue(uv_count);
if (uv_count > MaxUVLayers)
Log.report(logvisor::Fatal, FMT_STRING("UV layer overflow {}/{}"), uv_count, MaxUVLayers);
/* Simultaneously load topology objects and build unique mapping indices */
uint32_t vert_count;
conn._readValue(vert_count);
verts.reserve(vert_count);
b_pos.reserve(vert_count);
b_skin.reserve(vert_count * 4);
for (uint32_t i = 0; i < vert_count; ++i) {
verts.emplace_back(conn);
insert_unique_attr(b_pos, verts.back().co);
if (verts.back().skin_ents[0].valid())
insert_unique_attr(b_skin, verts.back().skin_ents);
}
uint32_t loop_count;
conn._readValue(loop_count);
loops.reserve(loop_count);
b_norm.reserve(loop_count);
if (use_luvs) {
b_uv.reserve(std::max(int(loop_count) - 1, 0) * uv_count);
b_luv.reserve(loop_count);
} else {
b_uv.reserve(loop_count * uv_count);
}
for (uint32_t i = 0; i < loop_count; ++i) {
loops.emplace_back(conn, color_count, uv_count);
insert_unique_attr(b_norm, loops.back().normal);
for (const auto& c : loops.back().colors)
insert_unique_attr(b_color, c);
if (use_luvs && material_is_lightmapped(materials[faces[loops.back().face].material_index])) {
insert_unique_attr(b_luv, loops.back().uvs[0]);
for (auto I = std::begin(loops.back().uvs) + 1, E = std::end(loops.back().uvs); I != E; ++I)
insert_unique_attr(b_uv, *I);
} else {
for (const auto& c : loops.back().uvs)
insert_unique_attr(b_uv, c);
}
}
conn._readVector(edges);
conn._readVector(faces);
/* Cache edges that should block tristrip traversal */
for (auto& e : edges)
e.tag = splitable_edge(e);
}
}