#include "MeshOptimizer.hpp" #include #include #include #include #include #include namespace hecl::blender { logvisor::Module Log("MeshOptimizer"); template static void insert_unique_attr(std::unordered_map& set, const T& attr) { if (set.find(attr) == set.cend()) { size_t sz = set.size(); set.insert(std::make_pair(attr, sz)); } } template static std::vector sort_unordered_map(const std::unordered_map& map) { struct SortableIterator { typename std::unordered_map::const_iterator it; bool operator<(const SortableIterator& other) const { return it->second < other.it->second; } explicit SortableIterator(typename std::unordered_map::const_iterator i) : it(i) {} }; std::vector 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 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& sfaces) const { std::vector faces_out; faces_out.reserve(sfaces.size()); std::unordered_set 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 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(mag); } Mesh::Surface MeshOptimizer::generate_surface(std::vector& 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(island_faces.size()); /* AABB of surface */ ret.aabbMin.val.simd = athena::simd(FLT_MAX); ret.aabbMax.val.simd = athena::simd(-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::vector>> 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 island_local(island_faces); uint32_t prev_loop = loops[l].link_loop_next; uint32_t loop = loops[prev_loop].link_loop_next; std::vector 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* max_sl = nullptr; const std::vector* 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 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 mat_faces_rem, the_list; mat_faces_rem.reserve(faces.size()); the_list.reserve(faces.size()); std::unordered_set 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& 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); } }