diff --git a/src/format.h b/src/format.h new file mode 100644 index 0000000..c4b864d --- /dev/null +++ b/src/format.h @@ -0,0 +1,12 @@ +#include + +template <> struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template + auto format(const Vector3& v, FormatContext& ctx) const -> decltype(ctx.out()) { + return format_to(ctx.out(), "{} {} {}", v.x, v.y, v.z); + } +}; diff --git a/src/main.cpp b/src/main.cpp index 8f4bb5f..0e369d8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,18 @@ +#include #include #include #include #include #include -#include +#include #include #include #include #include -#include +#include "format.h" namespace fs = std::filesystem; @@ -19,66 +20,39 @@ namespace fs = std::filesystem; #define RED "\033[31m" #define GREEN "\033[32m" -int transfer_vertices(const std::string &obj_filename, const std::string &nif_filename) { - tinyobj::attrib_t attributes; - std::vector shapes; +Vector3 transform_vertice(Vector3 v, MatTransform t, float w) { + return t.rotation * (v * t.scale) + t.translation * w; +} - std::string warn, err; +std::vector skin_vertices(NifFile &nifile, NiShape &shape, bool inverse = false) { + auto vertices = *shape.get_vertices(); - if (!tinyobj::LoadObj(&attributes, &shapes, nullptr, &warn, &err, obj_filename.c_str(), NULL, true)) { - std::cerr << err << std::endl; + std::vector transforms; + transforms.resize(vertices.size()); - return 1; - } + std::vector bones; + nifile.GetShapeBoneIDList(&shape, bones); - auto nifile = NifFile(nif_filename); - auto nishapes = nifile.GetShapes(); + for (int id : bones) { + MatTransform transform; + nifile.GetShapeTransformSkinToBone(&shape, id - 1, transform); - std::vector identical_shapes; + std::unordered_map weights; + nifile.GetShapeBoneWeights(&shape, id - 1, weights); - for (auto nishape : nishapes) { - auto vertices = nishape->get_vertices(); + for (auto w : weights) { + auto v = vertices[w.first]; - if (vertices && vertices->size() == attributes.vertices.size() / 3) { - identical_shapes.emplace_back(nishape); + vertices[w.first] = transform_vertice(v, transform, w.second); + transforms[w.first] += vertices[w.first] - v; } } - NiShape* nishape; - - // TODO: Sideeffects - if (identical_shapes.size() == 0) - { - fmt::print("Couldn't find a shape with the same ammount of vertices. Expected: {}", attributes.vertices.size()); - - return 1; - } - else if (identical_shapes.size() > 1) - { - fmt::print("\n"); - - for (size_t i = 0; i < identical_shapes.size(); i++) { - fmt::print(GREEN "{}" WHITE ": {}\n", i, identical_shapes[i]->GetName()); - } - - fmt::print("\nMultiple shapes with the same amount of vertices where found.\nEnter the number of shape, which will acquire all data.\n"); - - int index; - std::cin >> index; - - if (index >= 0 && index < identical_shapes.size()) - nishape = identical_shapes[index]; - else - return 1; - } - else if (identical_shapes.size() == 1) - { - nishape = identical_shapes[0]; - } - - // TODO: Separate + return transforms; +} - auto vertices = nishape->get_vertices(); +void transfer_attributes(const tinyobj::attrib_t &attributes, NiShape &shape) { + auto vertices = shape.get_vertices(); if (vertices && vertices->size() == attributes.vertices.size() / 3) { @@ -92,10 +66,10 @@ int transfer_vertices(const std::string &obj_filename, const std::string &nif_fi attributes.vertices[i + 2]); } - nishape->set_vertices(vertices); + shape.set_vertices(vertices); } - auto normals = nishape->get_normals(false); + auto normals = shape.get_normals(false); if (normals && normals->size() == attributes.normals.size() / 3) { std::vector normals; @@ -108,10 +82,10 @@ int transfer_vertices(const std::string &obj_filename, const std::string &nif_fi attributes.normals[i + 2]); } - nishape->set_normals(normals); + shape.set_normals(normals); } - auto uv = nishape->get_uv(); + auto uv = shape.get_uv(); if (uv && uv->size() == attributes.texcoords.size() / 2) { std::vector uv; @@ -123,48 +97,41 @@ int transfer_vertices(const std::string &obj_filename, const std::string &nif_fi 1.0 - attributes.texcoords[i + 1]); } - nishape->set_uv(uv); + shape.set_uv(uv); } - - nifile.Save(nif_filename + ".nif"); - - return 0; } -void export_shape(std::ostream &stream, NiShape &shape, int v_offset = 1, int vt_offset = 1, int vn_offset = 1) { - const std::vector* vertices = shape.get_vertices(); +void export_shape(NiShape &shape, std::ostream &stream) { + const std::vector vertices = *shape.get_vertices(); const std::vector* uv = shape.get_uv(); const std::vector* normals = shape.get_normals(false); std::vector faces; shape.GetTriangles(faces); - const auto v_size = vertices->size(); - const auto vn_size = normals ? normals->size() : 0; - const auto vt_size = uv ? uv->size() : 0; - fmt::print(stream, "# NifHacks 0.2\n\n" "# {} Vertices\n" "# {} Texture coordinates\n" "# {} Normals\n" "# {} Faces\n", - v_size, vt_size, vn_size, faces.size()); + vertices.size(), + uv ? uv->size() : 0, + normals ? normals->size() : 0, + faces.size()); - const std::string name = shape.GetName(); - - if (!name.empty()) { - fmt::print(stream, "o {}\n", name.c_str()); + if (auto name = shape.GetName(); !name.empty()) { + fmt::print(stream, "\no {}\n\n", name); } - std::string f_format = "{0}"; - - for (auto &v : *vertices) { - fmt::print(stream, "v {} {} {}\n", v.x, v.y, v.z); + for (auto v : vertices) { + fmt::print(stream, "v {}\n", v); } fmt::print(stream, "\n"); + std::string f_zero = "{0}"; + if (uv) { for (auto &p : *uv) { fmt::print(stream, "vt {} {}\n", p.u, 1.0 - p.v); @@ -172,142 +139,211 @@ void export_shape(std::ostream &stream, NiShape &shape, int v_offset = 1, int vt fmt::print(stream, "\n"); - f_format += "/{0}"; + f_zero += "/{0}"; } if (normals) { for (auto &p : *normals) { - fmt::print(stream, "vn {} {} {}\n", p.x, p.y, p.z); + fmt::print(stream, "vn {}\n", p); } fmt::print(stream, "\n"); - f_format += "/{0}"; + f_zero += "/{0}"; } - f_format = fmt::format("f {} {} {}\n", - f_format, - boost::replace_all_copy(f_format, "0", "1"), - boost::replace_all_copy(f_format, "0", "2")); + auto f_one = f_zero; + auto f_two = f_zero; + + std::replace(f_one.begin(), f_one.end(), '0', '1'); + std::replace(f_two.begin(), f_two.end(), '0', '2'); + + auto f_format = fmt::format("f {} {} {}\n", f_zero, f_one, f_two); for (auto &f : faces) { - fmt::print(stream, f_format, - f.p1 + v_offset, - f.p2 + vt_offset, - f.p3 + vn_offset); + fmt::print(stream, f_format, f.p1+1, f.p2+1, f.p3+1); } fmt::print(stream, "\n"); } -int export_shape(const std::string &nif_filename, const std::string &obj_filename, const std::string &shape_name) { +int obj_to_nif(const std::string &obj_filename, const std::string &nif_filename, bool skin = false) { + tinyobj::attrib_t attributes; + std::vector shapes; + + std::string err; + + if (!tinyobj::LoadObj(&attributes, &shapes, nullptr, &err, obj_filename.c_str())) { + std::cerr << err << std::endl; + + return 1; + } + auto nifile = NifFile(nif_filename); + auto nishapes = nifile.GetShapes(); + + std::vector identical_shapes; - auto shape = nifile.FindBlockByName(shape_name); + for (auto nishape : nishapes) { + auto vertices = nishape->get_vertices(); - if (shape) { - std::ofstream obj_stream(obj_filename, std::ios::out); + if (vertices && vertices->size() == attributes.vertices.size() / 3) { + identical_shapes.emplace_back(nishape); + } + } - export_shape(obj_stream, *shape, 1, 1, 1); + if (identical_shapes.size() == 0) + { + fmt::print("Couldn't find a shape with the same ammount of vertices. Expected: {}", attributes.vertices.size()); - return 0; + return 1; } - return 1; -} + NiShape* shape = identical_shapes[0]; -#ifdef _WIN32 + if (identical_shapes.size() > 1) + { + fmt::print("\n"); -void enable_virtual_terminal() { - HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); - if (hOut == INVALID_HANDLE_VALUE) return; + for (size_t i = 0; i < identical_shapes.size(); i++) { + fmt::print(GREEN "{}" WHITE ": {}\n", i, identical_shapes[i]->GetName()); + } - DWORD dwMode = 0; - if (!GetConsoleMode(hOut, &dwMode)) return; + fmt::print("\nMultiple shapes with the same amount of vertices where found.\nEnter the number of shape, which will acquire all data.\n"); - dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - if (!SetConsoleMode(hOut, dwMode)) return; -} + int index; + std::cin >> index; -#endif + if (index >= 0 && index < identical_shapes.size()) + shape = identical_shapes[index]; + else + return 1; + } -int main(int argc, char *argv[]) { + auto transforms = skin_vertices(nifile, *shape); + transfer_attributes(attributes, *shape); -#ifdef _WIN32 - enable_virtual_terminal(); -#endif + // TODO: Optimize with SIMD + if (skin) { + auto vertices = *shape->get_vertices(); - if (argc < 3) { - fmt::print("USAGE:\n nifhack [source] [target]\n"); + for (size_t i = 0; i < transforms.size(); i++) { + vertices[i] -= transforms[i]; + } - return 1; + shape->set_vertices(vertices); } - fs::path source(argv[1]); - fs::path target(argv[2]); + nifile.Save(nif_filename + ".nif"); - if (!fs::exists(source)) { - fmt::print("File doesn't exist\n"); + return 0; +} - return 1; +int nif_to_obj(const std::string &nif_filename, const std::string &obj_filename, bool skin = false) { + if (fs::exists(obj_filename)) { + fmt::print(RED "Warning! Target already exists and will be overwritten.\n" WHITE); } - auto source_ext = source.extension(); - auto target_ext = target.extension(); - - if (source_ext == ".obj" && target_ext == ".nif") - { - transfer_vertices(source.string(), target.string()); + auto nifile = NifFile(nif_filename); + + auto shapes = nifile.GetShapes(); + + if (shapes.size() == 0) { + fmt::print("No shapes to export.\n"); + return 1; } - else if (source_ext == ".nif" && target_ext == ".obj") - { - if (fs::exists(target)) { - fmt::print(RED "Warning! Target already exists and will be overwritten.\n" WHITE); - } - auto nifile = NifFile(source.string()); + fmt::print("\n"); + + int index = 0; + if (shapes.size() > 1) { + for (size_t i = 0; i < shapes.size(); i++) { + fmt::print(GREEN "{}" WHITE ": {}\n", i, shapes[i]->GetName()); + } - NiShape* shape; + fmt::print("\nEnter the number of shape you want to export.\n"); - auto shapes = nifile.GetShapes(); + std::cin >> index; - if (shapes.size() == 0) { - fmt::print("No shapes to export.\n"); + if (index < 0 || index >= shapes.size()) { return 1; } + } - fmt::print("\n"); + NiShape* shape = shapes[index]; - int index = 0; - if (shapes.size() > 1) { - for (size_t i = 0; i < shapes.size(); i++) { - fmt::print(GREEN "{}" WHITE ": {}\n", i, shapes[i]->GetName()); - } + if (!shape) { + fmt::print("Couldn't find shape in source file.\n"); - fmt::print("\nEnter the number of shape you want to export.\n"); + return 1; + } - std::cin >> index; + // TODO: Optimize with SIMD + if (skin) { + auto transforms = skin_vertices(nifile, *shape); + auto vertices = *shape->get_vertices(); - if (index < 0 || index >= shapes.size()) { - return 1; - } + for (size_t i = 0; i < transforms.size(); i++) { + vertices[i] += transforms[i]; } - shape = shapes[index]; + shape->set_vertices(vertices); + } - // TODO: Add cli argument - // shape = nifile.FindBlockByName(argv[3]); + std::ofstream obj_stream(obj_filename, std::ios::out); + export_shape(*shape, obj_stream); - if (!shape) { - fmt::print("Couldn't find shape in source file.\n"); + return 0; +} - return 1; - } +int main(int argc, char *argv[]) { - std::ofstream obj_stream(target, std::ios::out); + CLI::App app { "Janky tool that (sometimes) get things done for your nif-needs\n" }; - export_shape(obj_stream, *shape); + fs::path input_file; + app.add_option("INPUT", input_file) + ->option_text(" *.nif *.obj") + ->required(true) + ->check(CLI::ExistingFile); + + fs::path output_file; + app.add_option("OUTPUT", output_file) + ->option_text(" *.obj *.nif") + ->required(true); + + bool transform { false }; + app.add_flag("-s,--skin", transform, "Apply skin transforms to shape"); + + // TODO: Add overwrite option + // bool overwrite { false }; + // app.add_flag("-i,--in-place", overwrite, "Modify file in place"); + + // TODO: Add version flag + // bool version { false } + // app.add_flag("-v,--version", version, ""); + + CLI11_PARSE(app, argc, argv); + + auto input_ext = input_file.extension(); + auto output_ext = output_file.extension(); + + if (input_ext == ".obj" && output_ext == ".nif") + { + obj_to_nif(input_file.string(), output_file.string(), transform); + + fmt::print("Done!"); + + return 0; } + if (input_ext == ".nif" && output_ext == ".obj") + { + nif_to_obj(input_file.string(), output_file.string(), transform); + + fmt::print("Done!"); + + return 0; + } + std::cerr << app.help() << std::flush; }