diff --git a/CMakeLists.txt b/CMakeLists.txt index 180ba03a..1249e62d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1357,6 +1357,7 @@ add_executable(wowee_editor tools/editor/cli_check.cpp tools/editor/cli_introspect.cpp tools/editor/cli_texture_helpers.cpp + tools/editor/cli_mesh_info.cpp tools/editor/editor_app.cpp tools/editor/editor_camera.cpp tools/editor/editor_viewport.cpp diff --git a/tools/editor/cli_mesh_info.cpp b/tools/editor/cli_mesh_info.cpp new file mode 100644 index 00000000..eacb5700 --- /dev/null +++ b/tools/editor/cli_mesh_info.cpp @@ -0,0 +1,335 @@ +#include "cli_mesh_info.hpp" + +#include "pipeline/wowee_model.hpp" +#include "pipeline/wowee_building.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace wowee { +namespace editor { +namespace cli { + +namespace { + +int handleInfoZoneModelsTotal(int& i, int argc, char** argv) { + // Aggregate WOM/WOB stats across every model in a zone. + // Useful for capacity planning ('how many bones across all + // my creatures?') and perf budgeting ('total triangles + // per frame if all loaded?'). + std::string zoneDir = argv[++i]; + bool jsonOut = (i + 1 < argc && + std::strcmp(argv[i + 1], "--json") == 0); + if (jsonOut) i++; + namespace fs = std::filesystem; + if (!fs::exists(zoneDir)) { + std::fprintf(stderr, + "info-zone-models-total: %s does not exist\n", zoneDir.c_str()); + return 1; + } + int womCount = 0, wobCount = 0; + uint64_t womVerts = 0, womIndices = 0; + uint64_t womBones = 0, womAnims = 0, womBatches = 0; + uint64_t wobGroups = 0, wobVerts = 0, wobIndices = 0; + uint64_t wobDoodads = 0, wobPortals = 0; + std::error_code ec; + for (const auto& e : fs::recursive_directory_iterator(zoneDir, ec)) { + if (!e.is_regular_file()) continue; + std::string ext = e.path().extension().string(); + std::string base = e.path().string(); + if (base.size() > ext.size()) + base = base.substr(0, base.size() - ext.size()); + if (ext == ".wom") { + womCount++; + auto wom = wowee::pipeline::WoweeModelLoader::load(base); + womVerts += wom.vertices.size(); + womIndices += wom.indices.size(); + womBones += wom.bones.size(); + womAnims += wom.animations.size(); + womBatches += wom.batches.size(); + } else if (ext == ".wob") { + wobCount++; + auto wob = wowee::pipeline::WoweeBuildingLoader::load(base); + wobGroups += wob.groups.size(); + for (const auto& g : wob.groups) { + wobVerts += g.vertices.size(); + wobIndices += g.indices.size(); + } + wobDoodads += wob.doodads.size(); + wobPortals += wob.portals.size(); + } + } + if (jsonOut) { + nlohmann::json j; + j["zone"] = zoneDir; + j["wom"] = {{"count", womCount}, + {"vertices", womVerts}, + {"indices", womIndices}, + {"triangles", womIndices / 3}, + {"bones", womBones}, + {"animations", womAnims}, + {"batches", womBatches}}; + j["wob"] = {{"count", wobCount}, + {"groups", wobGroups}, + {"vertices", wobVerts}, + {"indices", wobIndices}, + {"triangles", wobIndices / 3}, + {"doodads", wobDoodads}, + {"portals", wobPortals}}; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("Zone models total: %s\n", zoneDir.c_str()); + std::printf("\n WOM (open M2):\n"); + std::printf(" files : %d\n", womCount); + std::printf(" vertices : %llu\n", static_cast(womVerts)); + std::printf(" triangles : %llu\n", static_cast(womIndices / 3)); + std::printf(" bones : %llu\n", static_cast(womBones)); + std::printf(" anims : %llu\n", static_cast(womAnims)); + std::printf(" batches : %llu\n", static_cast(womBatches)); + std::printf("\n WOB (open WMO):\n"); + std::printf(" files : %d\n", wobCount); + std::printf(" groups : %llu\n", static_cast(wobGroups)); + std::printf(" vertices : %llu\n", static_cast(wobVerts)); + std::printf(" triangles : %llu\n", static_cast(wobIndices / 3)); + std::printf(" doodads : %llu\n", static_cast(wobDoodads)); + std::printf(" portals : %llu\n", static_cast(wobPortals)); + std::printf("\n Combined :\n"); + std::printf(" vertices : %llu\n", static_cast(womVerts + wobVerts)); + std::printf(" triangles : %llu\n", static_cast((womIndices + wobIndices) / 3)); + return 0; +} + +int handleListZoneMeshesDetail(int& i, int argc, char** argv) { + // Per-mesh breakdown of every .wom file in , + // sorted by triangle count descending so the heaviest + // meshes float to the top. Complements + // --list-zone-meshes (per-zone summary) by surfacing + // individual mesh metrics — useful for spotting + // outliers ("which mesh is using 80% of my triangle + // budget?") and for content audits. + std::string zoneDir = argv[++i]; + bool jsonOut = (i + 1 < argc && + std::strcmp(argv[i + 1], "--json") == 0); + if (jsonOut) i++; + namespace fs = std::filesystem; + if (!fs::exists(zoneDir)) { + std::fprintf(stderr, + "list-zone-meshes-detail: %s does not exist\n", zoneDir.c_str()); + return 1; + } + struct Row { + std::string path; + size_t verts; + size_t tris; + size_t bones; + size_t batches; + size_t textures; + uint64_t bytes; + uint32_t version; + }; + std::vector rows; + std::error_code ec; + for (const auto& e : fs::recursive_directory_iterator(zoneDir, ec)) { + if (!e.is_regular_file()) continue; + if (e.path().extension() != ".wom") continue; + std::string base = e.path().string(); + if (base.size() >= 4) base = base.substr(0, base.size() - 4); + auto wom = wowee::pipeline::WoweeModelLoader::load(base); + Row r; + r.path = fs::relative(e.path(), zoneDir, ec).string(); + if (ec) r.path = e.path().filename().string(); + r.verts = wom.vertices.size(); + r.tris = wom.indices.size() / 3; + r.bones = wom.bones.size(); + r.batches = wom.batches.size(); + r.textures = wom.texturePaths.size(); + r.bytes = e.file_size(ec); + if (ec) r.bytes = 0; + r.version = wom.version; + rows.push_back(r); + } + std::sort(rows.begin(), rows.end(), + [](const Row& a, const Row& b) { return a.tris > b.tris; }); + uint64_t totVerts = 0, totTris = 0, totBones = 0, totBytes = 0; + for (const auto& r : rows) { + totVerts += r.verts; totTris += r.tris; + totBones += r.bones; totBytes += r.bytes; + } + if (jsonOut) { + nlohmann::json j; + j["zone"] = zoneDir; + j["meshCount"] = rows.size(); + j["totals"] = {{"vertices", totVerts}, + {"triangles", totTris}, + {"bones", totBones}, + {"bytes", totBytes}}; + nlohmann::json arr = nlohmann::json::array(); + for (const auto& r : rows) { + arr.push_back({{"path", r.path}, + {"version", r.version}, + {"vertices", r.verts}, + {"triangles", r.tris}, + {"bones", r.bones}, + {"batches", r.batches}, + {"textures", r.textures}, + {"bytes", r.bytes}}); + } + j["meshes"] = arr; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("Zone meshes: %s\n", zoneDir.c_str()); + std::printf(" meshes : %zu\n", rows.size()); + std::printf(" totals : %llu verts, %llu tris, %llu bones, %.1f KB\n", + static_cast(totVerts), + static_cast(totTris), + static_cast(totBones), + totBytes / 1024.0); + if (rows.empty()) { + std::printf("\n *no .wom files in this zone*\n"); + return 0; + } + std::printf("\n v verts tris bones batch tex bytes path\n"); + for (const auto& r : rows) { + std::printf(" v%u %6zu %6zu %5zu %5zu %3zu %7llu %s\n", + r.version, r.verts, r.tris, r.bones, + r.batches, r.textures, + static_cast(r.bytes), + r.path.c_str()); + } + return 0; +} + +int handleInfoMesh(int& i, int argc, char** argv) { + // Single-mesh detail view aggregating bounds, version, + // batches, bones, animations, and texture slots into one + // report. Composite of what --info-batches / --info-bones + // / --info-batches show separately. Useful authoring + // command: pass a WOM and see everything about it without + // running three sub-commands. + std::string base = argv[++i]; + bool jsonOut = (i + 1 < argc && + std::strcmp(argv[i + 1], "--json") == 0); + if (jsonOut) i++; + if (base.size() >= 4 && base.substr(base.size() - 4) == ".wom") { + base = base.substr(0, base.size() - 4); + } + if (!wowee::pipeline::WoweeModelLoader::exists(base)) { + std::fprintf(stderr, + "info-mesh: %s.wom does not exist\n", base.c_str()); + return 1; + } + auto wom = wowee::pipeline::WoweeModelLoader::load(base); + if (!wom.isValid()) { + std::fprintf(stderr, + "info-mesh: failed to load %s.wom\n", base.c_str()); + return 1; + } + // Per-batch material summary. + static const char* blendNames[] = { + "opaque", "alpha-test", "alpha", "additive", "?", "?", "?", "?" + }; + if (jsonOut) { + nlohmann::json j; + j["base"] = base; + j["name"] = wom.name; + j["version"] = wom.version; + j["bounds"] = {{"min", {wom.boundMin.x, wom.boundMin.y, wom.boundMin.z}}, + {"max", {wom.boundMax.x, wom.boundMax.y, wom.boundMax.z}}, + {"radius", wom.boundRadius}}; + j["counts"] = {{"vertices", wom.vertices.size()}, + {"indices", wom.indices.size()}, + {"triangles", wom.indices.size() / 3}, + {"bones", wom.bones.size()}, + {"animations", wom.animations.size()}, + {"batches", wom.batches.size()}, + {"textures", wom.texturePaths.size()}}; + nlohmann::json bs = nlohmann::json::array(); + for (const auto& b : wom.batches) { + std::string tex; + if (b.textureIndex < wom.texturePaths.size()) + tex = wom.texturePaths[b.textureIndex]; + bs.push_back({{"indexStart", b.indexStart}, + {"indexCount", b.indexCount}, + {"triangles", b.indexCount / 3}, + {"textureIndex", b.textureIndex}, + {"texture", tex}, + {"blendMode", b.blendMode}, + {"flags", b.flags}}); + } + j["batchDetail"] = bs; + j["texturePaths"] = wom.texturePaths; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("Mesh: %s.wom\n", base.c_str()); + std::printf(" name : %s\n", wom.name.c_str()); + std::printf(" version : v%u\n", wom.version); + std::printf("\n Counts:\n"); + std::printf(" vertices : %zu\n", wom.vertices.size()); + std::printf(" triangles : %zu\n", wom.indices.size() / 3); + std::printf(" bones : %zu\n", wom.bones.size()); + std::printf(" anims : %zu\n", wom.animations.size()); + std::printf(" batches : %zu\n", wom.batches.size()); + std::printf(" textures : %zu\n", wom.texturePaths.size()); + std::printf("\n Bounds:\n"); + std::printf(" min : (%.3f, %.3f, %.3f)\n", + wom.boundMin.x, wom.boundMin.y, wom.boundMin.z); + std::printf(" max : (%.3f, %.3f, %.3f)\n", + wom.boundMax.x, wom.boundMax.y, wom.boundMax.z); + std::printf(" radius : %.3f\n", wom.boundRadius); + if (!wom.batches.empty()) { + std::printf("\n Batches:\n"); + std::printf(" idx iStart iCount tris blend texture\n"); + for (size_t k = 0; k < wom.batches.size(); ++k) { + const auto& b = wom.batches[k]; + std::string tex = ""; + if (b.textureIndex < wom.texturePaths.size()) + tex = wom.texturePaths[b.textureIndex]; + if (tex.empty()) tex = "(empty)"; + int blend = b.blendMode < 8 ? b.blendMode : 0; + std::printf(" %3zu %6u %6u %4u %-10s %s\n", + k, b.indexStart, b.indexCount, + b.indexCount / 3, blendNames[blend], + tex.c_str()); + } + } + if (!wom.texturePaths.empty()) { + std::printf("\n Texture slots:\n"); + for (size_t k = 0; k < wom.texturePaths.size(); ++k) { + std::printf(" [%zu] %s\n", k, + wom.texturePaths[k].empty() + ? "(empty placeholder)" + : wom.texturePaths[k].c_str()); + } + } + return 0; +} + + +} // namespace + +bool handleMeshInfo(int& i, int argc, char** argv, int& outRc) { + if (std::strcmp(argv[i], "--info-zone-models-total") == 0 && i + 1 < argc) { + outRc = handleInfoZoneModelsTotal(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--list-zone-meshes-detail") == 0 && i + 1 < argc) { + outRc = handleListZoneMeshesDetail(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--info-mesh") == 0 && i + 1 < argc) { + outRc = handleInfoMesh(i, argc, argv); return true; + } + return false; +} + +} // namespace cli +} // namespace editor +} // namespace wowee diff --git a/tools/editor/cli_mesh_info.hpp b/tools/editor/cli_mesh_info.hpp new file mode 100644 index 00000000..7644797d --- /dev/null +++ b/tools/editor/cli_mesh_info.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace wowee { +namespace editor { +namespace cli { + +// Dispatch the mesh-aggregate info handlers — surface +// vertex/triangle/bone/animation totals across WOM/WOB +// files, plus per-mesh detail views. +// --info-zone-models-total aggregate WOM/WOB across a zone +// --list-zone-meshes-detail per-mesh table sorted by tris +// --info-mesh single-WOM detail dump +// +// All three support --json output. +// +// Returns true if matched; outRc holds the exit code. +bool handleMeshInfo(int& i, int argc, char** argv, int& outRc); + +} // namespace cli +} // namespace editor +} // namespace wowee diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index 4891787d..3ad5d553 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -58,6 +58,7 @@ #include "cli_check.hpp" #include "cli_introspect.hpp" #include "cli_texture_helpers.hpp" +#include "cli_mesh_info.hpp" #include "content_pack.hpp" #include "npc_spawner.hpp" #include "object_placer.hpp" @@ -573,6 +574,9 @@ int main(int argc, char* argv[]) { if (wowee::editor::cli::handleTextureHelpers(i, argc, argv, outRc)) { return outRc; } + if (wowee::editor::cli::handleMeshInfo(i, argc, argv, outRc)) { + return outRc; + } } if (std::strcmp(argv[i], "--data") == 0 && i + 1 < argc) { dataPath = argv[++i]; @@ -580,295 +584,6 @@ int main(int argc, char* argv[]) { adtMap = argv[++i]; adtX = std::atoi(argv[++i]); adtY = std::atoi(argv[++i]); - } else if (std::strcmp(argv[i], "--info-zone-models-total") == 0 && i + 1 < argc) { - // Aggregate WOM/WOB stats across every model in a zone. - // Useful for capacity planning ('how many bones across all - // my creatures?') and perf budgeting ('total triangles - // per frame if all loaded?'). - std::string zoneDir = argv[++i]; - bool jsonOut = (i + 1 < argc && - std::strcmp(argv[i + 1], "--json") == 0); - if (jsonOut) i++; - namespace fs = std::filesystem; - if (!fs::exists(zoneDir)) { - std::fprintf(stderr, - "info-zone-models-total: %s does not exist\n", zoneDir.c_str()); - return 1; - } - int womCount = 0, wobCount = 0; - uint64_t womVerts = 0, womIndices = 0; - uint64_t womBones = 0, womAnims = 0, womBatches = 0; - uint64_t wobGroups = 0, wobVerts = 0, wobIndices = 0; - uint64_t wobDoodads = 0, wobPortals = 0; - std::error_code ec; - for (const auto& e : fs::recursive_directory_iterator(zoneDir, ec)) { - if (!e.is_regular_file()) continue; - std::string ext = e.path().extension().string(); - std::string base = e.path().string(); - if (base.size() > ext.size()) - base = base.substr(0, base.size() - ext.size()); - if (ext == ".wom") { - womCount++; - auto wom = wowee::pipeline::WoweeModelLoader::load(base); - womVerts += wom.vertices.size(); - womIndices += wom.indices.size(); - womBones += wom.bones.size(); - womAnims += wom.animations.size(); - womBatches += wom.batches.size(); - } else if (ext == ".wob") { - wobCount++; - auto wob = wowee::pipeline::WoweeBuildingLoader::load(base); - wobGroups += wob.groups.size(); - for (const auto& g : wob.groups) { - wobVerts += g.vertices.size(); - wobIndices += g.indices.size(); - } - wobDoodads += wob.doodads.size(); - wobPortals += wob.portals.size(); - } - } - if (jsonOut) { - nlohmann::json j; - j["zone"] = zoneDir; - j["wom"] = {{"count", womCount}, - {"vertices", womVerts}, - {"indices", womIndices}, - {"triangles", womIndices / 3}, - {"bones", womBones}, - {"animations", womAnims}, - {"batches", womBatches}}; - j["wob"] = {{"count", wobCount}, - {"groups", wobGroups}, - {"vertices", wobVerts}, - {"indices", wobIndices}, - {"triangles", wobIndices / 3}, - {"doodads", wobDoodads}, - {"portals", wobPortals}}; - std::printf("%s\n", j.dump(2).c_str()); - return 0; - } - std::printf("Zone models total: %s\n", zoneDir.c_str()); - std::printf("\n WOM (open M2):\n"); - std::printf(" files : %d\n", womCount); - std::printf(" vertices : %llu\n", static_cast(womVerts)); - std::printf(" triangles : %llu\n", static_cast(womIndices / 3)); - std::printf(" bones : %llu\n", static_cast(womBones)); - std::printf(" anims : %llu\n", static_cast(womAnims)); - std::printf(" batches : %llu\n", static_cast(womBatches)); - std::printf("\n WOB (open WMO):\n"); - std::printf(" files : %d\n", wobCount); - std::printf(" groups : %llu\n", static_cast(wobGroups)); - std::printf(" vertices : %llu\n", static_cast(wobVerts)); - std::printf(" triangles : %llu\n", static_cast(wobIndices / 3)); - std::printf(" doodads : %llu\n", static_cast(wobDoodads)); - std::printf(" portals : %llu\n", static_cast(wobPortals)); - std::printf("\n Combined :\n"); - std::printf(" vertices : %llu\n", static_cast(womVerts + wobVerts)); - std::printf(" triangles : %llu\n", static_cast((womIndices + wobIndices) / 3)); - return 0; - } else if (std::strcmp(argv[i], "--list-zone-meshes-detail") == 0 && i + 1 < argc) { - // Per-mesh breakdown of every .wom file in , - // sorted by triangle count descending so the heaviest - // meshes float to the top. Complements - // --list-zone-meshes (per-zone summary) by surfacing - // individual mesh metrics — useful for spotting - // outliers ("which mesh is using 80% of my triangle - // budget?") and for content audits. - std::string zoneDir = argv[++i]; - bool jsonOut = (i + 1 < argc && - std::strcmp(argv[i + 1], "--json") == 0); - if (jsonOut) i++; - namespace fs = std::filesystem; - if (!fs::exists(zoneDir)) { - std::fprintf(stderr, - "list-zone-meshes-detail: %s does not exist\n", zoneDir.c_str()); - return 1; - } - struct Row { - std::string path; - size_t verts; - size_t tris; - size_t bones; - size_t batches; - size_t textures; - uint64_t bytes; - uint32_t version; - }; - std::vector rows; - std::error_code ec; - for (const auto& e : fs::recursive_directory_iterator(zoneDir, ec)) { - if (!e.is_regular_file()) continue; - if (e.path().extension() != ".wom") continue; - std::string base = e.path().string(); - if (base.size() >= 4) base = base.substr(0, base.size() - 4); - auto wom = wowee::pipeline::WoweeModelLoader::load(base); - Row r; - r.path = fs::relative(e.path(), zoneDir, ec).string(); - if (ec) r.path = e.path().filename().string(); - r.verts = wom.vertices.size(); - r.tris = wom.indices.size() / 3; - r.bones = wom.bones.size(); - r.batches = wom.batches.size(); - r.textures = wom.texturePaths.size(); - r.bytes = e.file_size(ec); - if (ec) r.bytes = 0; - r.version = wom.version; - rows.push_back(r); - } - std::sort(rows.begin(), rows.end(), - [](const Row& a, const Row& b) { return a.tris > b.tris; }); - uint64_t totVerts = 0, totTris = 0, totBones = 0, totBytes = 0; - for (const auto& r : rows) { - totVerts += r.verts; totTris += r.tris; - totBones += r.bones; totBytes += r.bytes; - } - if (jsonOut) { - nlohmann::json j; - j["zone"] = zoneDir; - j["meshCount"] = rows.size(); - j["totals"] = {{"vertices", totVerts}, - {"triangles", totTris}, - {"bones", totBones}, - {"bytes", totBytes}}; - nlohmann::json arr = nlohmann::json::array(); - for (const auto& r : rows) { - arr.push_back({{"path", r.path}, - {"version", r.version}, - {"vertices", r.verts}, - {"triangles", r.tris}, - {"bones", r.bones}, - {"batches", r.batches}, - {"textures", r.textures}, - {"bytes", r.bytes}}); - } - j["meshes"] = arr; - std::printf("%s\n", j.dump(2).c_str()); - return 0; - } - std::printf("Zone meshes: %s\n", zoneDir.c_str()); - std::printf(" meshes : %zu\n", rows.size()); - std::printf(" totals : %llu verts, %llu tris, %llu bones, %.1f KB\n", - static_cast(totVerts), - static_cast(totTris), - static_cast(totBones), - totBytes / 1024.0); - if (rows.empty()) { - std::printf("\n *no .wom files in this zone*\n"); - return 0; - } - std::printf("\n v verts tris bones batch tex bytes path\n"); - for (const auto& r : rows) { - std::printf(" v%u %6zu %6zu %5zu %5zu %3zu %7llu %s\n", - r.version, r.verts, r.tris, r.bones, - r.batches, r.textures, - static_cast(r.bytes), - r.path.c_str()); - } - return 0; - } else if (std::strcmp(argv[i], "--info-mesh") == 0 && i + 1 < argc) { - // Single-mesh detail view aggregating bounds, version, - // batches, bones, animations, and texture slots into one - // report. Composite of what --info-batches / --info-bones - // / --info-batches show separately. Useful authoring - // command: pass a WOM and see everything about it without - // running three sub-commands. - std::string base = argv[++i]; - bool jsonOut = (i + 1 < argc && - std::strcmp(argv[i + 1], "--json") == 0); - if (jsonOut) i++; - if (base.size() >= 4 && base.substr(base.size() - 4) == ".wom") { - base = base.substr(0, base.size() - 4); - } - if (!wowee::pipeline::WoweeModelLoader::exists(base)) { - std::fprintf(stderr, - "info-mesh: %s.wom does not exist\n", base.c_str()); - return 1; - } - auto wom = wowee::pipeline::WoweeModelLoader::load(base); - if (!wom.isValid()) { - std::fprintf(stderr, - "info-mesh: failed to load %s.wom\n", base.c_str()); - return 1; - } - // Per-batch material summary. - static const char* blendNames[] = { - "opaque", "alpha-test", "alpha", "additive", "?", "?", "?", "?" - }; - if (jsonOut) { - nlohmann::json j; - j["base"] = base; - j["name"] = wom.name; - j["version"] = wom.version; - j["bounds"] = {{"min", {wom.boundMin.x, wom.boundMin.y, wom.boundMin.z}}, - {"max", {wom.boundMax.x, wom.boundMax.y, wom.boundMax.z}}, - {"radius", wom.boundRadius}}; - j["counts"] = {{"vertices", wom.vertices.size()}, - {"indices", wom.indices.size()}, - {"triangles", wom.indices.size() / 3}, - {"bones", wom.bones.size()}, - {"animations", wom.animations.size()}, - {"batches", wom.batches.size()}, - {"textures", wom.texturePaths.size()}}; - nlohmann::json bs = nlohmann::json::array(); - for (const auto& b : wom.batches) { - std::string tex; - if (b.textureIndex < wom.texturePaths.size()) - tex = wom.texturePaths[b.textureIndex]; - bs.push_back({{"indexStart", b.indexStart}, - {"indexCount", b.indexCount}, - {"triangles", b.indexCount / 3}, - {"textureIndex", b.textureIndex}, - {"texture", tex}, - {"blendMode", b.blendMode}, - {"flags", b.flags}}); - } - j["batchDetail"] = bs; - j["texturePaths"] = wom.texturePaths; - std::printf("%s\n", j.dump(2).c_str()); - return 0; - } - std::printf("Mesh: %s.wom\n", base.c_str()); - std::printf(" name : %s\n", wom.name.c_str()); - std::printf(" version : v%u\n", wom.version); - std::printf("\n Counts:\n"); - std::printf(" vertices : %zu\n", wom.vertices.size()); - std::printf(" triangles : %zu\n", wom.indices.size() / 3); - std::printf(" bones : %zu\n", wom.bones.size()); - std::printf(" anims : %zu\n", wom.animations.size()); - std::printf(" batches : %zu\n", wom.batches.size()); - std::printf(" textures : %zu\n", wom.texturePaths.size()); - std::printf("\n Bounds:\n"); - std::printf(" min : (%.3f, %.3f, %.3f)\n", - wom.boundMin.x, wom.boundMin.y, wom.boundMin.z); - std::printf(" max : (%.3f, %.3f, %.3f)\n", - wom.boundMax.x, wom.boundMax.y, wom.boundMax.z); - std::printf(" radius : %.3f\n", wom.boundRadius); - if (!wom.batches.empty()) { - std::printf("\n Batches:\n"); - std::printf(" idx iStart iCount tris blend texture\n"); - for (size_t k = 0; k < wom.batches.size(); ++k) { - const auto& b = wom.batches[k]; - std::string tex = ""; - if (b.textureIndex < wom.texturePaths.size()) - tex = wom.texturePaths[b.textureIndex]; - if (tex.empty()) tex = "(empty)"; - int blend = b.blendMode < 8 ? b.blendMode : 0; - std::printf(" %3zu %6u %6u %4u %-10s %s\n", - k, b.indexStart, b.indexCount, - b.indexCount / 3, blendNames[blend], - tex.c_str()); - } - } - if (!wom.texturePaths.empty()) { - std::printf("\n Texture slots:\n"); - for (size_t k = 0; k < wom.texturePaths.size(); ++k) { - std::printf(" [%zu] %s\n", k, - wom.texturePaths[k].empty() - ? "(empty placeholder)" - : wom.texturePaths[k].c_str()); - } - } - return 0; } else if (std::strcmp(argv[i], "--info-mesh-storage-budget") == 0 && i + 1 < argc) { // Estimated bytes-per-category breakdown for a WOM. // Numbers are based on the in-memory struct sizes, not