From e08ef2fbd58ad96aa4527298bb84245a8c8a90f5 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 14 Oct 2016 18:50:33 +0900 Subject: [PATCH] Initial working abc2gltf converter. --- examples/alembic_to_gltf/Makefile | 2 +- examples/alembic_to_gltf/README.md | 31 +++++ .../alembic_to_gltf/{main.cc => abc2gltf.cc} | 125 +++++++++++++++++- examples/alembic_to_gltf/suzanne.abc | Bin 0 -> 24609 bytes 4 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 examples/alembic_to_gltf/README.md rename examples/alembic_to_gltf/{main.cc => abc2gltf.cc} (75%) create mode 100644 examples/alembic_to_gltf/suzanne.abc diff --git a/examples/alembic_to_gltf/Makefile b/examples/alembic_to_gltf/Makefile index 99f2d83..e7321b2 100644 --- a/examples/alembic_to_gltf/Makefile +++ b/examples/alembic_to_gltf/Makefile @@ -7,7 +7,7 @@ ILMBASE_INC := -I/usr/local/include/OpenEXR EXTRA_CXXFLAGS := -Wno-deprecated-register -Weverything all: - clang++ -std=c++11 -g -o abc2gltf $(ABC_INC) $(ILMBASE_INC) $(EXTRA_CXXFLAGS) main.cc $(ABC_LDFLAGS) + clang++ -std=c++11 -g -o abc2gltf $(ABC_INC) $(ILMBASE_INC) $(EXTRA_CXXFLAGS) abc2gltf.cc $(ABC_LDFLAGS) run: ./abc2gltf suzanne.abc suzanne.gltf diff --git a/examples/alembic_to_gltf/README.md b/examples/alembic_to_gltf/README.md new file mode 100644 index 0000000..3909e09 --- /dev/null +++ b/examples/alembic_to_gltf/README.md @@ -0,0 +1,31 @@ +# Simple Alembic to glTF converter example + +## Features + +* Polygon mesh. + +## Limitations + +* Alembic data with Ogawa backend only +* Simple poly mesh only +* Static mesh only(Use first time sample. no animation) + +## Compile + +OpenEXR(ilmbase), and Alembic 1.6 or later are equired to compile the converter. + +Edit include and lib paths for Alembic OpenEXR(ilmbase) in `Makefile`, then simply: + + $ make + +## Alembic data + +I am testing with Alembic data using Blender's Alembic exporter(feature available from Blender 2.78) + +## TODO + +* [ ] Support normals and texcoords +* [ ] Support mutiple meshes +* [ ] Support animation(time samples) +* [ ] Support scene graph(node hierarchy) +* [ ] Support Point, Curve and SubD? diff --git a/examples/alembic_to_gltf/main.cc b/examples/alembic_to_gltf/abc2gltf.cc similarity index 75% rename from examples/alembic_to_gltf/main.cc rename to examples/alembic_to_gltf/abc2gltf.cc index fae638b..d198b8c 100644 --- a/examples/alembic_to_gltf/main.cc +++ b/examples/alembic_to_gltf/abc2gltf.cc @@ -31,12 +31,15 @@ #include #include +#define PICOJSON_USE_INT64 #include "../../picojson.h" #ifdef __clang__ #pragma clang diagnostic pop #endif +#include "../../tiny_gltf_loader.h" // To import some TINYGLTF_*** macros. + // @todo { texcoords, normals } typedef struct { @@ -337,8 +340,8 @@ static bool SaveGLTF(const std::string& output_filename, root["assets"] = picojson::value(asset); } - picojson::object buffers; { + picojson::object buffers; { std::string vertices_b64data = base64_encode(reinterpret_cast(mesh.vertices.data()), mesh.vertices.size() * sizeof(float)); picojson::object buf; @@ -359,12 +362,122 @@ static bool SaveGLTF(const std::string& output_filename, buf["uri"] = picojson::value( std::string("data:application/octet-stream;base64,") + faces_b64data); buf["byteLength"] = - picojson::value(static_cast(mesh.vertices.size() * sizeof(unsigned int))); + picojson::value(static_cast(mesh.faces.size() * sizeof(unsigned int))); buffers["indices"] = picojson::value(buf); } + root["buffers"] = picojson::value(buffers); } - root["buffers"] = picojson::value(buffers); + + { + picojson::object buffer_views; + { + picojson::object buffer_view_vertices; + buffer_view_vertices["buffer"] = picojson::value(std::string("vertices")); + buffer_view_vertices["byteLength"] = picojson::value(static_cast(mesh.vertices.size() * sizeof(float))); + buffer_view_vertices["byteOffset"] = picojson::value(static_cast(0)); + buffer_view_vertices["target"] = picojson::value(static_cast(TINYGLTF_TARGET_ARRAY_BUFFER)); + + buffer_views["bufferView_vertices"] = picojson::value(buffer_view_vertices); + } + + { + picojson::object buffer_view_indices; + buffer_view_indices["buffer"] = picojson::value(std::string("indices")); + buffer_view_indices["byteLength"] = picojson::value(static_cast(mesh.faces.size() * sizeof(unsigned int))); + buffer_view_indices["byteOffset"] = picojson::value(static_cast(0)); + buffer_view_indices["target"] = picojson::value(static_cast(TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)); + + buffer_views["bufferView_indices"] = picojson::value(buffer_view_indices); + } + + root["bufferViews"] = picojson::value(buffer_views); + } + + { + picojson::object attributes; + + attributes["POSITION"] = picojson::value(std::string("accessor_vertices")); + + picojson::object primitive; + primitive["attributes"] = picojson::value(attributes); + primitive["indices"] = picojson::value("accessor_indices"); + primitive["material"] = picojson::value("material_1"); + primitive["mode"] = picojson::value(static_cast(TINYGLTF_MODE_TRIANGLES)); + + picojson::array primitive_array; + primitive_array.push_back(picojson::value(primitive)); + + picojson::object m; + m["primitives"] = picojson::value(primitive_array); + + picojson::object meshes; + meshes["mesh_1"] = picojson::value(m); + + + root["meshes"] = picojson::value(meshes); + } + + { + picojson::object accessors; + picojson::object accessor_vertices; + picojson::object accessor_indices; + + accessor_vertices["bufferView"] = picojson::value(std::string("bufferView_vertices")); + accessor_vertices["byteOffset"] = picojson::value(static_cast(0)); + accessor_vertices["byteStride"] = picojson::value(static_cast(3 * sizeof(float))); + accessor_vertices["componentType"] = picojson::value(static_cast(TINYGLTF_COMPONENT_TYPE_FLOAT)); + accessor_vertices["count"] = picojson::value(static_cast(mesh.vertices.size())); + accessor_vertices["type"] = picojson::value(std::string("VEC3")); + accessors["accessor_vertices"] = picojson::value(accessor_vertices); + + accessor_indices["bufferView"] = picojson::value(std::string("bufferView_indices")); + accessor_indices["byteOffset"] = picojson::value(static_cast(0)); + accessor_indices["componentType"] = picojson::value(static_cast(TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT)); + accessor_indices["count"] = picojson::value(static_cast(mesh.faces.size())); + accessor_indices["type"] = picojson::value(std::string("SCALAR")); + accessors["accessor_indices"] = picojson::value(accessor_indices); + root["accessors"] = picojson::value(accessors); + } + + { + // Use Default Material(Do not supply `material.technique`) + picojson::object default_material; + picojson::object materials; + + materials["material_1"] = picojson::value(default_material); + + root["materials"] = picojson::value(materials); + + } + + { + picojson::object nodes; + picojson::object node; + picojson::array meshes; + + meshes.push_back(picojson::value(std::string("mesh_1"))); + + node["meshes"] = picojson::value(meshes); + + nodes["node_1"] = picojson::value(node); + root["nodes"] = picojson::value(nodes); + } + + { + picojson::object defaultScene; + picojson::array nodes; + + nodes.push_back(picojson::value(std::string("node_1"))); + + defaultScene["nodes"] = picojson::value(nodes); + + root["scene"] = picojson::value("defaultScene"); + picojson::object scenes; + scenes["defaultScene"] = picojson::value(defaultScene); + root["scenes"] = picojson::value(scenes); + } + // @todo {} picojson::object shaders; @@ -422,7 +535,11 @@ int main(int argc, char** argv) { if (foundMesh) { bool ret = SaveGLTF(gltf_filename, mesh); - return ret ? EXIT_SUCCESS : EXIT_FAILURE; + if (ret) { + std::cout << "Wrote " << gltf_filename << std::endl; + } else { + return EXIT_FAILURE; + } } else { std::cout << "No polygon mesh found in Alembic file" << std::endl; } diff --git a/examples/alembic_to_gltf/suzanne.abc b/examples/alembic_to_gltf/suzanne.abc new file mode 100644 index 0000000000000000000000000000000000000000..1825f764410df48286b20b40ab218d5688f01f90 GIT binary patch literal 24609 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 Vj=nh`MBkhR{0BRI{X89Q{}2C{ACmw8 literal 0 HcmV?d00001