diff --git a/BUILD_INSTRUCTIONS.md b/BUILD_INSTRUCTIONS.md index 31b475e..acb5f34 100644 --- a/BUILD_INSTRUCTIONS.md +++ b/BUILD_INSTRUCTIONS.md @@ -29,7 +29,7 @@ $ cmake --build . -- -j8 ```bash $ cd your_toolset_build_directory -$ ./model_converter/model_converter +$ ./model_converter/model2mesh ``` ## * Links diff --git a/model_converter/CMakeLists.txt b/model_converter/CMakeLists.txt index bf5d0ae..a2e9e5f 100644 --- a/model_converter/CMakeLists.txt +++ b/model_converter/CMakeLists.txt @@ -8,22 +8,43 @@ set(ASSIMP_BUILD_ASSIMP_TOOLS OFF CACHE BOOL "" FORCE) add_subdirectory(modules/assimp) # -# model converter executable +# model2mesh executable # -file(GLOB MODEL_CONVERTER_SOURCES - sources/*.*) +file(GLOB MODEL2MESH_SOURCES + sources/model2mesh.cpp) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES - ${MODEL_CONVERTER_SOURCES}) + ${MODEL2MESH_SOURCES}) -add_executable(model_converter - ${MODEL_CONVERTER_SOURCES}) +add_executable(model2mesh + ${MODEL2MESH_SOURCES}) -target_link_libraries(model_converter +target_link_libraries(model2mesh assimp) -set_target_properties(model_converter PROPERTIES +set_target_properties(model2mesh PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO) + +# +# model2shape executable +# + +file(GLOB MODEL2SHAPE_SOURCES + sources/model2shape.cpp) + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES + ${MODEL2SHAPE_SOURCES}) + +add_executable(model2shape + ${MODEL2SHAPE_SOURCES}) + +target_link_libraries(model2shape + assimp) + +set_target_properties(model2shape PROPERTIES CXX_STANDARD 14 CXX_STANDARD_REQUIRED YES CXX_EXTENSIONS NO) diff --git a/model_converter/sources/main.cpp b/model_converter/sources/model2mesh.cpp similarity index 99% rename from model_converter/sources/main.cpp rename to model_converter/sources/model2mesh.cpp index e59c3a6..a327834 100644 --- a/model_converter/sources/main.cpp +++ b/model_converter/sources/model2mesh.cpp @@ -399,7 +399,7 @@ namespace int main(int argc, char *argv[]) { if ( argc < 2 ) { - std::cout << "USAGE: model_converter mesh.obj" << std::endl; + std::cout << "USAGE: model2mesh mesh.obj" << std::endl; return 0; } return convert(argv[1], opts(argc, argv)) ? 0 : 1; diff --git a/model_converter/sources/model2shape.cpp b/model_converter/sources/model2shape.cpp new file mode 100644 index 0000000..4c8277c --- /dev/null +++ b/model_converter/sources/model2shape.cpp @@ -0,0 +1,342 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018-2019 Matvey Cherevko + ******************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace +{ + const std::uint32_t shape_file_version = 1; + const std::string shape_file_signature = "e2d_shape"; + + struct opts { + bool timers = false; + bool verbose = false; + + opts(int argc, char *argv[]) { + timers = has_flag("-t", argc, argv) || has_flag("--timers", argc, argv); + verbose = has_flag("-v", argc, argv) || has_flag("--verbose", argc, argv); + } + + private: + static bool has_flag(const char* flag, int argc, char *argv[]) noexcept { + for ( int i = 0; i < argc; ++i ) { + if ( 0 == std::strcmp(argv[i], flag) ) { + return true; + } + } + return false; + } + }; + + class timer { + public: + timer() + : tp_(std::chrono::high_resolution_clock::now()) {} + + void done() const { + const auto duration_us = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - tp_); + std::cout << duration_us.count() << "us" << std::endl; + } + private: + std::chrono::high_resolution_clock::time_point tp_; + }; + + struct v2f { + float x = 0.f; + float y = 0.f; + + v2f(float nx, float ny) + : x(nx), y(ny) {} + }; + + struct shape { + std::vector vertices; + std::vector indices; + + std::vector> uvs_channels; + std::vector> colors_channels; + }; + + template < typename T > + T saturate(T v) noexcept { + return std::min(std::max(v, T(0)), T(1)); + } + + std::uint8_t pack_color_component(float c) noexcept { + return static_cast(std::round(saturate(c) * 255.f)); + } + + std::uint32_t pack_color(float r, float g, float b, float a) noexcept { + std::uint8_t rr = pack_color_component(r); + std::uint8_t gg = pack_color_component(g); + std::uint8_t bb = pack_color_component(b); + std::uint8_t aa = pack_color_component(a); + return + static_cast(aa) << 24 | + static_cast(rr) << 16 | + static_cast(gg) << 8 | + static_cast(bb) << 0; + } + + void write_u32_to_ofstream(std::ofstream& s, const std::uint32_t v) { + s.write( + reinterpret_cast(&v), + sizeof(v)); + } + + void write_str_to_ofstream(std::ofstream& s, const std::string& v) { + s.write(v.data(), static_cast(v.length())); + } + + template < typename T > + std::size_t write_vector_to_ofstream(std::ofstream& s, const std::vector& v) { + if ( !v.empty() ) { + std::size_t data_size = v.size() * sizeof(T); + s.write( + reinterpret_cast(v.data()), + static_cast(data_size)); + return data_size; + } + return 0; + } + + bool validate_shape(const shape& shape) noexcept { + if ( shape.vertices.empty() ) { + return false; + } + if ( shape.indices.empty() ) { + return false; + } + for ( const auto& uvs : shape.uvs_channels ) { + if ( uvs.size() != shape.vertices.size() ) { + return false; + } + } + for ( const auto& colors : shape.colors_channels ) { + if ( colors.size() != shape.vertices.size() ) { + return false; + } + } + return true; + } + + bool save_shape(const shape& shape, const std::string& out_path, const opts& opts) { + timer save_timer; + + if ( !validate_shape(shape) ) { + std::cerr << "Failed to validate out shape: " << out_path << std::endl; + return false; + } + + std::ofstream stream(out_path, std::ofstream::out | std::ofstream::binary); + if ( !stream.is_open() ) { + std::cerr << "Failed to open out file stream: " << out_path << std::endl; + return false; + } + + write_str_to_ofstream(stream, shape_file_signature); + write_u32_to_ofstream(stream, shape_file_version); + + write_u32_to_ofstream(stream, static_cast(shape.vertices.size())); + write_u32_to_ofstream(stream, static_cast(shape.indices.size())); + + write_u32_to_ofstream(stream, static_cast(shape.uvs_channels.size())); + write_u32_to_ofstream(stream, static_cast(shape.colors_channels.size())); + + std::size_t vertices_bytes = write_vector_to_ofstream(stream, shape.vertices); + std::size_t indices_bytes = write_vector_to_ofstream(stream, shape.indices); + + std::size_t uvs_bytes = 0; + for ( const auto& uvs : shape.uvs_channels ) { + uvs_bytes += write_vector_to_ofstream(stream, uvs); + } + + std::size_t colors_bytes = 0; + for ( const auto& colors : shape.colors_channels ) { + colors_bytes += write_vector_to_ofstream(stream, colors); + } + + if ( opts.timers ) { + std::cout << "> save mesh: "; + save_timer.done(); + std::cout << " - " << out_path << std::endl; + } + + if ( opts.verbose ) { + std::cout + << std::endl + << "> mesh info:" << std::endl + << "-> vertices: " << shape.vertices.size() << ", " << vertices_bytes << " B" << std::endl + << "-> indices: " << shape.indices.size() << ", " << indices_bytes << " B" << std::endl + << "-> uvs: " << shape.uvs_channels.size() << ", " << uvs_bytes << " B" << std::endl + << "-> colors: " << shape.colors_channels.size() << ", " << colors_bytes << " B" << std::endl; + } + + return true; + } + + bool convert_shape(const aiMesh* ai_mesh, const std::string& out_path, const opts& opts) { + shape out_shape; + timer convert_timer; + + if ( ai_mesh->HasPositions() ) { + out_shape.vertices.reserve(ai_mesh->mNumVertices); + std::transform( + ai_mesh->mVertices, + ai_mesh->mVertices + ai_mesh->mNumVertices, + std::back_inserter(out_shape.vertices), + [](const aiVector3D& v) noexcept { + return v2f{v.x, v.y}; + }); + } + + if ( ai_mesh->HasFaces() ) { + out_shape.indices.reserve(ai_mesh->mNumFaces * 3u); + std::for_each( + ai_mesh->mFaces, + ai_mesh->mFaces + ai_mesh->mNumFaces, + [&out_shape](const aiFace& f) { + if ( f.mNumIndices != 3 ) { + throw std::logic_error("invalide face index count"); + } + out_shape.indices.insert( + out_shape.indices.end(), + f.mIndices, + f.mIndices + f.mNumIndices); + }); + } + + for ( unsigned int channel = 0; channel < ai_mesh->GetNumUVChannels(); ++channel ) { + std::vector uvs; + uvs.reserve(ai_mesh->mNumVertices); + std::transform( + ai_mesh->mTextureCoords[channel], + ai_mesh->mTextureCoords[channel] + ai_mesh->mNumVertices, + std::back_inserter(uvs), + [](const aiVector3D& v) noexcept { + return v2f{v.x, v.y}; + }); + out_shape.uvs_channels.emplace_back(std::move(uvs)); + } + + for ( unsigned int channel = 0; channel < ai_mesh->GetNumColorChannels(); ++channel ) { + std::vector colors; + colors.reserve(ai_mesh->mNumVertices); + std::transform( + ai_mesh->mColors[channel], + ai_mesh->mColors[channel] + ai_mesh->mNumVertices, + std::back_inserter(colors), + [](const aiColor4D& v) noexcept { + return pack_color(v.r, v.g, v.b, v.a); + }); + out_shape.colors_channels.emplace_back(std::move(colors)); + } + + if ( opts.timers ) { + std::cout << std::endl << "> convert shape: "; + convert_timer.done(); + std::cout << " - " << out_path << std::endl; + } + + return save_shape(out_shape, out_path, opts); + } + + bool convert(const std::string& path, const opts& opts) { + timer convert_timer; + timer importer_timer; + + Assimp::Importer importer; + + if ( opts.timers ) { + std::cout << "> prepare importer: "; + importer_timer.done(); + } + + const unsigned int importer_flags = + aiProcess_Triangulate | + aiProcess_MakeLeftHanded | + aiProcess_OptimizeMeshes | + aiProcess_JoinIdenticalVertices; + + timer import_timer; + + const aiScene* scene = importer.ReadFile(path, importer_flags); + if ( !scene ) { + std::cerr << "Failed to import model: " << path << std::endl; + std::cerr << "Error: " << importer.GetErrorString() << std::endl; + return false; + } + + if ( opts.timers ) { + std::cout << "> import model: "; + import_timer.done(); + } + + for ( unsigned int mesh_index = 0; mesh_index < scene->mNumMeshes; ++mesh_index ) { + const aiMesh* mesh = scene->mMeshes[mesh_index]; + + const std::string shape_name = mesh->mName.length + ? mesh->mName.C_Str() + : "shape_" + std::to_string(mesh_index); + + std::string shape_out_path = std::string() + .append(path) + .append(".") + .append(shape_name) + .append(".e2d_shape"); + + if ( opts.verbose ) { + std::cout + << std::endl + << ">> Shape(" + << shape_name + << ") converting..." + << std::endl; + } + + if ( !convert_shape(mesh, shape_out_path, opts) ) { + std::cerr << "Failed!" << std::endl; + return false; + } + + if ( opts.verbose ) { + std::cout << "OK. " << std::endl; + } + } + + if ( opts.timers ) { + std::cout << std::endl << "=====" << std::endl; + convert_timer.done(); + } + + return true; + } +} + +int main(int argc, char *argv[]) { + if ( argc < 2 ) { + std::cout << "USAGE: model2shape mesh.obj" << std::endl; + return 0; + } + return convert(argv[1], opts(argc, argv)) ? 0 : 1; +}