diff --git a/CMakeLists.txt b/CMakeLists.txt index fea36aa..3ed4fd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ SET(CMAKE_CXX_STANDARD 11) option(TINYGLTF_BUILD_LOADER_EXAMPLE "Build loader_example(load glTF and dump infos)" ON) option(TINYGLTF_BUILD_GL_EXAMPLES "Build GL exampels(requires glfw, OpenGL, etc)" OFF) option(TINYGLTF_BUILD_VALIDATOR_EXAMPLE "Build validator exampe" OFF) +option(TINYGLTF_BUILD_BUILDER_EXAMPLE "Build glTF builder example" OFF) option(TINYGLTF_HEADER_ONLY "On: header-only mode. Off: create tinygltf library(No TINYGLTF_IMPLEMENTATION required in your project)" OFF) option(TINYGLTF_INSTALL "Install tinygltf files during install step. Usually set to OFF if you include tinygltf through add_subdirectory()" ON) @@ -25,12 +26,16 @@ if (TINYGLTF_BUILD_VALIDATOR_EXAMPLE) ADD_SUBDIRECTORY ( examples/validator ) endif (TINYGLTF_BUILD_VALIDATOR_EXAMPLE) +if (TINYGLTF_BUILD_BUILDER_EXAMPLE) + ADD_SUBDIRECTORY ( examples/build-gltf ) +endif (TINYGLTF_BUILD_BUILDER_EXAMPLE) + # # for add_subdirectory and standalone build # if (TINYGLTF_HEADER_ONLY) add_library(tinygltf INTERFACE) - + target_include_directories(tinygltf INTERFACE $ @@ -61,7 +66,7 @@ if (TINYGLTF_INSTALL) DESTINATION include ) - + INSTALL ( FILES cmake/TinyGLTFConfig.cmake DESTINATION diff --git a/README.md b/README.md index bcf53fd..6297778 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ In extension(`ExtensionMap`), JSON number value is parsed as int or float(number * [glview](examples/glview) : Simple glTF geometry viewer. * [validator](examples/validator) : Simple glTF validator with JSON schema. * [basic](examples/basic) : Basic glTF viewer with texturing support. +* [build-gltf](examples/build-gltf) : Build simple glTF scene from a scratch. ## Projects using TinyGLTF @@ -90,6 +91,7 @@ In extension(`ExtensionMap`), JSON number value is parsed as int or float(number * [GlslViewer](https://github.com/patriciogonzalezvivo/glslViewer) - live GLSL coding for MacOS and Linux * [Vulkan-Samples](https://github.com/KhronosGroup/Vulkan-Samples) - The Vulkan Samples is collection of resources to help you develop optimized Vulkan applications. * [TDME2](https://github.com/andreasdr/tdme2) - TDME2 - ThreeDeeMiniEngine2 is a lightweight 3D engine including tools suited for 3D game development using C++11 +* [SanityEngine](https://github.com/DethRaid/SanityEngine) - A C++/D3D12 renderer focused on the personal and proessional development of its developer * Your projects here! (Please send PR) ## TODOs @@ -168,6 +170,7 @@ if (!ret) { * `TINYGLTF_ANDROID_LOAD_FROM_ASSETS`: Load all files from packaged app assets instead of the regular file system. **Note:** You must pass a valid asset manager from your android app to `tinygltf::asset_manager` beforehand. * `TINYGLTF_ENABLE_DRACO`: Enable Draco compression. User must provide include path and link correspnding libraries in your project file. * `TINYGLTF_NO_INCLUDE_JSON `: Disable including `json.hpp` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`. +* `TINYGLTF_NO_INCLUDE_RAPIDJSON `: Disable including RapidJson's header files from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`. * `TINYGLTF_NO_INCLUDE_STB_IMAGE `: Disable including `stb_image.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`. * `TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE `: Disable including `stb_image_write.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`. * `TINYGLTF_USE_RAPIDJSON` : Use RapidJSON as a JSON parser/serializer. RapidJSON files are not included in TinyGLTF repo. Please set an include path to RapidJSON if you enable this featrure. diff --git a/examples/build-gltf/Makefile b/examples/build-gltf/Makefile new file mode 100644 index 0000000..51c22f5 --- /dev/null +++ b/examples/build-gltf/Makefile @@ -0,0 +1,2 @@ +all: + $(CXX) -o create_triangle_gltf -I../../ create_triangle_gltf.cpp diff --git a/examples/build-gltf/create_triangle_gltf.cpp b/examples/build-gltf/create_triangle_gltf.cpp new file mode 100644 index 0000000..be92981 --- /dev/null +++ b/examples/build-gltf/create_triangle_gltf.cpp @@ -0,0 +1,121 @@ +// An example of how to generate a gltf file from scratch. This example +// was translated from the pygltlib documentation in the pypi project page, +// which in turn is based on the Khronos Sample Models at: +// +// https://github.com/KhronosGroup/glTF-Sample-Models +// +// This example is released under the MIT license. +// +// 2021-02-25 Thu +// Dov Grobgeld + + +// Define these only in *one* .cc file. +#define TINYGLTF_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION + +// #define TINYGLTF_NOEXCEPTION // optional. disable exception handling. +#include "tiny_gltf.h" + +int main(int argc, char **argv) +{ + // Create a model with a single mesh and save it as a gltf file + tinygltf::Model m; + tinygltf::Scene scene; + tinygltf::Mesh mesh; + tinygltf::Primitive primitive; + tinygltf::Node node; + tinygltf::Buffer buffer; + tinygltf::BufferView bufferView1; + tinygltf::BufferView bufferView2; + tinygltf::Accessor accessor1; + tinygltf::Accessor accessor2; + tinygltf::Asset asset; + + // This is the raw data buffer. + buffer.data = { + // 6 bytes of indices and two bytes of padding + 0x00,0x00,0x01,0x00,0x02,0x00,0x00,0x00, + // 36 bytes of floating point numbers + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x3f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x3f, + 0x00,0x00,0x00,0x00}; + + // "The indices of the vertices (ELEMENT_ARRAY_BUFFER) take up 6 bytes in the + // start of the buffer. + bufferView1.buffer = 0; + bufferView1.byteOffset=0; + bufferView1.byteLength=6; + bufferView1.target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER; + + // The vertices take up 36 bytes (3 vertices * 3 floating points * 4 bytes) + // at position 8 in the buffer and are of type ARRAY_BUFFER + bufferView2.buffer = 0; + bufferView2.byteOffset=8; + bufferView2.byteLength=36; + bufferView2.target = TINYGLTF_TARGET_ARRAY_BUFFER; + + // Describe the layout of bufferView1, the indices of the vertices + accessor1.bufferView = 0; + accessor1.byteOffset = 0; + accessor1.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT; + accessor1.count = 3; + accessor1.type = TINYGLTF_TYPE_SCALAR; + accessor1.maxValues.push_back(2); + accessor1.minValues.push_back(0); + + // Describe the layout of bufferView2, the vertices themself + accessor2.bufferView = 1; + accessor2.byteOffset = 0; + accessor2.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + accessor2.count = 3; + accessor2.type = TINYGLTF_TYPE_VEC3; + accessor2.maxValues = {1.0, 1.0, 0.0}; + accessor2.minValues = {0.0, 0.0, 0.0}; + + // Build the mesh primitive and add it to the mesh + primitive.indices = 0; // The index of the accessor for the vertex indices + primitive.attributes["POSITION"] = 1; // The index of the accessor for positions + primitive.material = 0; + primitive.mode = TINYGLTF_MODE_TRIANGLES; + mesh.primitives.push_back(primitive); + + // Other tie ups + node.mesh = 0; + scene.nodes.push_back(0); // Default scene + + // Define the asset. The version is required + asset.version = "2.0"; + asset.generator = "tinygltf"; + + // Now all that remains is to tie back all the loose objects into the + // our single model. + m.scenes.push_back(scene); + m.meshes.push_back(mesh); + m.nodes.push_back(node); + m.buffers.push_back(buffer); + m.bufferViews.push_back(bufferView1); + m.bufferViews.push_back(bufferView2); + m.accessors.push_back(accessor1); + m.accessors.push_back(accessor2); + m.asset = asset; + + // Create a simple material + tinygltf::Material mat; + mat.pbrMetallicRoughness.baseColorFactor = {1.0f, 0.9f, 0.9f, 1.0f}; + mat.doubleSided = true; + m.materials.push_back(mat); + + // Save it to a file + tinygltf::TinyGLTF gltf; + gltf.WriteGltfSceneToFile(&m, "triangle.gltf", + true, // embedImages + true, // embedBuffers + true, // pretty print + false); // write binary + + exit(0); +} diff --git a/examples/glview/CMakeLists.txt b/examples/glview/CMakeLists.txt index 982bb6c..ad2073d 100644 --- a/examples/glview/CMakeLists.txt +++ b/examples/glview/CMakeLists.txt @@ -29,7 +29,7 @@ if (DEFINED DRACO_DIR) # TODO(syoyo): better CMake script for draco add_definitions(-DTINYGLTF_ENABLE_DRACO) include_directories(${DRACO_DIR}/include) - + link_directories(${DRACO_DIR}/lib) set(DRACO_LIBRARY draco) endif () @@ -49,9 +49,9 @@ add_executable(glview ) target_link_libraries ( glview - ${DRACO_LIBRARY} + ${DRACO_LIBRARY} ${GLFW3_UNIX_LINK_LIBRARIES} - ${GLEW_LIBRARY} + ${GLEW_LIBRARIES} ${GLFW3_glfw_LIBRARY} ${OPENGL_gl_LIBRARY} ${OPENGL_glu_LIBRARY} diff --git a/models/regression/unassigned-skeleton.gltf b/models/regression/unassigned-skeleton.gltf new file mode 100644 index 0000000..6a977e5 --- /dev/null +++ b/models/regression/unassigned-skeleton.gltf @@ -0,0 +1,267 @@ +{ + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": 3, + "type": "MAT4" + }, + { + "bufferView": 1, + "componentType": 5125, + "count": 24, + "type": "SCALAR" + }, + { + "bufferView": 2, + "componentType": 5126, + "count": 10, + "max": [ + 0.5, + 2.0, + 0.0 + ], + "min": [ + -0.5, + 0.0, + 0.0 + ], + "type": "VEC3" + }, + { + "bufferView": 3, + "byteOffset": 24, + "componentType": 5126, + "count": 10, + "type": "VEC4" + }, + { + "bufferView": 4, + "byteOffset": 40, + "componentType": 5126, + "count": 10, + "type": "VEC2" + }, + { + "bufferView": 5, + "byteOffset": 12, + "componentType": 5126, + "count": 10, + "type": "VEC3" + }, + { + "bufferView": 6, + "byteOffset": 48, + "componentType": 5123, + "count": 10, + "type": "VEC4" + }, + { + "bufferView": 7, + "byteOffset": 56, + "componentType": 5126, + "count": 10, + "type": "VEC4" + }, + { + "bufferView": 8, + "componentType": 5126, + "count": 165, + "max": [ + 5.466667175292969 + ], + "min": [ + 0.0 + ], + "type": "SCALAR" + }, + { + "bufferView": 8, + "byteOffset": 4, + "componentType": 5126, + "count": 165, + "type": "VEC4" + }, + { + "bufferView": 8, + "byteOffset": 20, + "componentType": 5126, + "count": 165, + "type": "VEC3" + } + ], + "animations": [ + { + "channels": [ + { + "sampler": 0, + "target": { + "node": 2, + "path": "rotation" + } + }, + { + "sampler": 1, + "target": { + "node": 2, + "path": "scale" + } + } + ], + "samplers": [ + { + "input": 8, + "interpolation": "LINEAR", + "output": 9 + }, + { + "input": 8, + "interpolation": "LINEAR", + "output": 10 + } + ] + } + ], + "asset": { + "version": "2.0" + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 192 + }, + { + "buffer": 1, + "byteLength": 96, + "target": 34963 + }, + { + "buffer": 2, + "byteLength": 720, + "byteStride": 72, + "target": 34962 + }, + { + "buffer": 2, + "byteLength": 720, + "byteStride": 72, + "target": 34962 + }, + { + "buffer": 2, + "byteLength": 720, + "byteStride": 72, + "target": 34962 + }, + { + "buffer": 2, + "byteLength": 720, + "byteStride": 72, + "target": 34962 + }, + { + "buffer": 2, + "byteLength": 720, + "byteStride": 72, + "target": 34962 + }, + { + "buffer": 2, + "byteLength": 720, + "byteStride": 72, + "target": 34962 + }, + { + "buffer": 3, + "byteLength": 5280, + "byteStride": 32, + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 192, + "name": "SkinBuffer", + "uri": "data:application/octet-stream;base64,AAAAAAAAAABBkoddQQUAgEMAOgAvAFAAcgBvAGoAZQBjAHQAcwAvAHMAdgBtADEAXwBkAGEAdABhAC8AZwBsAAAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAIA/" + }, + { + "byteLength": 96, + "name": "IndexBuffer", + "uri": "data:application/octet-stream;base64,AAAAAAEAAAADAAAAAAAAAAMAAAACAAAAAgAAAAMAAAAFAAAAAgAAAAUAAAAEAAAABAAAAAUAAAAHAAAABAAAAAcAAAAGAAAABgAAAAcAAAAJAAAABgAAAAkAAAAIAAAA" + }, + { + "byteLength": 720, + "name": "VertexBuffer", + "uri": "data:application/octet-stream;base64,AAAAvwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAQAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAQAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAvwAAAD8AAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAQACAAAAAAAAAEA/AACAPgAAAAAAAAAAAAAAPwAAAD8AAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAQACAAAAAAAAAEA/AACAPgAAAAAAAAAAAAAAvwAAgD8AAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAQACAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAPwAAgD8AAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAQACAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAvwAAwD8AAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAQACAAAAAAAAAIA+AABAPwAAAAAAAAAAAAAAPwAAwD8AAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAQACAAAAAAAAAIA+AABAPwAAAAAAAAAAAAAAvwAAAEAAAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAACAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAPwAAAEAAAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAACAAAAAAAAAAAAAACAPwAAAAAAAAAA" + }, + { + "byteLength": 5280, + "uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD+JiAg9AAAAAAAAAAA9nNY8gul/PwAAgD8AAIA/AACAP4mIiD0AAAAAAAAAAJ+JVj0Lpn8/AACAPwAAgD8AAIA/zszMPQAAAAAAAAAA9c+gPag1fz8CAIA/AgCAPwAAgD+JiAg+AAAAAAAAAAAzP9Y9a5h+PwYAgD8GAIA/AACAP6uqKj4AAAAAAAAAAKPEBT5xzn0/DwCAPw8AgD8AAIA/zsxMPgAAAAAAAAAAfFIgPtvXfD8gAIA/IACAPwAAgD/w7m4+AAAAAAAAAACExDo+1bR7PzsAgD87AIA/AACAP4mIiD4AAAAAAAAAAC0WVT6SZXo/ZACAP2QAgD8AAIA/mpmZPgAAAAAAAAAA5UJvPkvqeD+eAIA/ngCAPwAAgD+rqqo+AAAAAAAAAAASo4Q+Q0N3P/AAgD/wAIA/AACAP7y7uz4AAAAAAAAAALuNkT7BcHU/XAGAP1wBgD8AAIA/zszMPgAAAAAAAAAAKl+ePhhzcz/nAYA/5wGAPwAAgD/f3d0+AAAAAAAAAAAyFas+n0pxP5cCgD+XAoA/AACAP/Du7j4AAAAAAAAAAJuttz63924/cAOAP3ADgD8AAIA/AAAAPwAAAAAAAAAAQSbEPsV6bD94BIA/eASAPwAAgD+JiAg/AAAAAAAAAAD1cdA+1tVpP2oEgD9qBIA/AACAPxIRET8AAAAAAAAAAO6Y3D76B2c/PASAPzwEgD8AAIA/mpkZPwAAAAAAAAAAD5noPrARZD/uA4A/7gOAPwAAgD8jIiI/AAAAAAAAAAA1cPQ+e/NgP30DgD99A4A/AACAP6uqKj8AAAAAAAAAACgOAD/prV0/6gKAP+oCgD8AAIA/NDMzPwAAAAAAAAAApc0FP4tBWj8yAoA/MgKAPwAAgD+8uzs/AAAAAAAAAACUdQs/+65WP1cBgD9XAYA/AACAP0VERD8AAAAAAAAAAO8EET/a9lI/WQCAP1kAgD8AAIA/zsxMPwAAAAAAAAAAunoWP88ZTz9u/n8/bv5/PwAAgD9WVVU/AAAAAAAAAAAC1hs/hxhLP+r7fz/q+38/AACAP9/dXT8AAAAAAAAAANQVIT+080Y/LPl/Pyz5fz8AAIA/Z2ZmPwAAAAAAAAAAPTkmPxKsQj8y9n8/MvZ/PwAAgD/w7m4/AAAAAAAAAABUPys/YUI+Pwjzfz8I838/AACAP3h3dz8AAAAAAAAAADQnMD9mtzk/su9/P7Lvfz8AAIA/AACAPwAAAAAAAAAA9+80P/ILNT837H8/N+x/PwAAgD9FRIQ/AAAAAAAAAACR8jQ/FAs1P6vufz+r7n8/AACAP4mIiD8AAAAAAAAAAND0ND9VCjU/yfB/P8nwfz8AAIA/zcyMPwAAAAAAAAAAr/Y0P7UJNT+N8n8/jfJ/PwAAgD8SEZE/AAAAAAAAAAAr+DQ/Ngk1P/Lzfz/y838/AACAP1ZVlT8AAAAAAAAAAEn5ND/XCDU/AfV/PwH1fz8AAIA/mpmZPwAAAAAAAAAACvo0P5YINT+29X8/tvV/PwAAgD/e3Z0/AAAAAAAAAABn+jQ/dwg1Pw72fz8O9n8/AACAPyMioj8AAAAAAAAAAGn6ND93CDU/EfZ/PxH2fz8AAIA/Z2amPwAAAAAAAAAACvo0P5YINT+29X8/tvV/PwAAgD+rqqo/AAAAAAAAAABN+TQ/1Qg1PwT1fz8E9X8/AACAP/Durj8AAAAAAAAAACv4ND82CTU/8vN/P/Lzfz8AAIA/NDOzPwAAAAAAAAAAr/Y0P7UJNT+N8n8/jfJ/PwAAgD94d7c/AAAAAAAAAADO9DQ/VQo1P8bwfz/G8H8/AACAP7y7uz8AAAAAAAAAAJTyND8TCzU/ru5/P67ufz8AAIA/AQDAPwAAAAAAAAAA9+80P/ILNT837H8/N+x/PwAAgD9FRMQ/AAAAAAAAAAD37zQ/8gs1Pzfsfz837H8/AACAP4mIyD8AAAAAAAAAAIHOLz+SCzo/8O9/P/Dvfz8AAIA/zszMPwAAAAAAAAAAaYkqP9XlPj99838/ffN/PwAAgD8SEdE/AAAAAAAAAADMISU/v5lDP9v2fz/b9n8/AACAP1ZV1T8AAAAAAAAAAL2YHz9hJkg/+/l/P/v5fz8AAIA/mpnZPwAAAAAAAAAAXe8ZP9GKTD/W/H8/1vx/PwAAgD/f3d0/AAAAAAAAAADeJhQ/KcZQP27/fz9u/38/AACAPyMi4j8AAAAAAAAAAGNADj+S11Q/2wCAP9sAgD8AAIA/Z2bmPwAAAAAAAAAAJD0IPzm+WD/YAYA/2AGAPwAAgD+rquo/AAAAAAAAAABYHgI/VnlcP6wCgD+sAoA/AACAP/Du7j8AAAAAAAAAAHPK9z4pCGA/VgOAP1YDgD8AAIA/NDPzPwAAAAAAAAAAJibrPvlpYz/ZA4A/2QOAPwAAgD94d/c/AAAAAAAAAABHUt4+Gp5mPzMEgD8zBIA/AACAP727+z8AAAAAAAAAAHVR0T7no2k/ZwSAP2cEgD8AAIA/AAAAQAAAAAAAAAAAUCbEPsN6bD95BIA/eQSAPwAAgD8jIgJAAAAAAAAAAABBJsQ+xXpsP3gEgD94BIA/AACAP0VEBEAAAAAAAAAAAFPItj6bI28/XwOAP18DgD8AAIA/Z2YGQAAAAAAAAAAAHEapPieccT97AoA/ewKAPwAAgD+JiAhAAAAAAAAAAABCops+6+NzP8YBgD/GAYA/AACAP6uqCkAAAAAAAAAAAHnfjT50+nU/OQGAPzkBgD8AAIA/zcwMQAAAAAAAAAAAfACAPljfdz/QAIA/0ACAPwAAgD/v7g5AAAAAAAAAAAAREGQ+NpJ5P4MAgD+DAIA/AACAPxIREUAAAAAAAAAAAMnxRz63Ens/TgCAP04AgD8AAIA/NDMTQAAAAAAAAAAAt6srPo9gfD8qAIA/KgCAPwAAgD9WVRVAAAAAAAAAAAB6Qw8+fHt9PxQAgD8UAIA/AACAP3h3F0AAAAAAAAAAAG595T1EY34/CACAPwgAgD8AAIA/mpkZQAAAAAAAAAAAN0asPbsXfz8DAIA/AwCAPwAAgD+8uxtAAAAAAAAAAABl2WU9vJh/PwEAgD8BAIA/AACAP97dHUAAAAAAAAAAABjx5Twu5n8/AACAPwAAgD8AAIA/AQAgQAAAAAAAAAAAzctVNQAAgD8AAIA/AACAPwAAgD8jIiJAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAP0VEJEAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/Z2YmQAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD+JiChAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAP6uqKkAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/zcwsQAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD/w7i5AAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPxIRMUAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/NDMzQAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD9WVTVAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAP3h3N0AAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/mpk5QAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD+8uztAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAP9/dPUAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AQBAQAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8jIkJAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAP0VEREAAAAAAAAAAAE7v5bwu5n8/AACAPwAAgD8AAIA/Z2ZGQAAAAAAAAAAAaNhlvb2Yfz8AAIA/AACAPwAAgD+JiEhAAAAAAAAAAACyRay9vRd/PwMAgD8DAIA/AACAP6uqSkAAAAAAAAAAAON85b1GY34/CQCAPwkAgD8AAIA/zsxMQAAAAAAAAAAAMUMPvn57fT8UAIA/FACAPwAAgD/w7k5AAAAAAAAAAABrqyu+kmB8PyoAgD8qAIA/AACAPxIRUUAAAAAAAAAAAHbxR767Ens/TQCAP00AgD8AAIA/NDNTQAAAAAAAAAAAug9kvjuSeT+DAIA/gwCAPwAAgD9WVVVAAAAAAAAAAABQAIC+Xt93P9AAgD/QAIA/AACAP3h3V0AAAAAAAAAAAEzfjb57+nU/OQGAPzkBgD8AAIA/mplZQAAAAAAAAAAAEqKbvvPjcz/GAYA/xgGAPwAAgD+8u1tAAAAAAAAAAADpRam+L5xxP3oCgD96AoA/AACAP9/dXUAAAAAAAAAAACXItr6kI28/XwOAP18DgD8AAIA/AQBgQAAAAAAAAAAAESbEvtB6bD95BIA/eQSAPwAAgD8jImJAAAAAAAAAAABBJsS+xXpsP3gEgD94BIA/AACAP0VEZEAAAAAAAAAAAGNR0b7ro2k/ZwSAP2cEgD8AAIA/Z2ZmQAAAAAAAAAAAMVLevh+eZj8zBIA/MwSAPwAAgD+JiGhAAAAAAAAAAAAKJuu+AGpjP9gDgD/YA4A/AACAP6uqakAAAAAAAAAAAF3K974wCGA/VwOAP1cDgD8AAIA/zsxsQAAAAAAAAAAARh4Cv2B5XD+rAoA/qwKAPwAAgD/w7m5AAAAAAAAAAAAVPQi/Q75YP9kBgD/ZAYA/AACAPxIRcUAAAAAAAAAAAFNADr+d11Q/3ACAP9wAgD8AAIA/NDNzQAAAAAAAAAAAyyYUvzbGUD9v/38/b/9/PwAAgD9WVXVAAAAAAAAAAABO7xm/34pMP9v8fz/b/H8/AACAP3h3d0AAAAAAAAAAAKiYH79zJkg//fl/P/35fz8AAIA/mpl5QAAAAAAAAAAAtiElv9KZQz/b9n8/2/Z/PwAAgD+9u3tAAAAAAAAAAABViSq/6eU+P4Dzfz+A838/AACAP9/dfUAAAAAAAAAAAGrOL7+oCzo/8e9/P/Hvfz8AAIA/AACAQAAAAAAAAAAA4e80vwoMNT867H8/Oux/PwAAgD8SEYFAAAAAAAAAAAD37zS/8gs1Pzfsfz837H8/AACAPyMigkAAAAAAAAAAAMHyNL8ECzU/2O5/P9jufz8AAIA/NDODQAAAAAAAAAAAHvU0vzoKNT8S8X8/EvF/PwAAgD9FRIRAAAAAAAAAAAAH9zS/lwk1P9/yfz/f8n8/AACAP1ZVhUAAAAAAAAAAAIX4NL8YCTU/R/R/P0f0fz8AAIA/Z2aGQAAAAAAAAAAAmPk0v7wINT9L9X8/S/V/PwAAgD94d4dAAAAAAAAAAAA9+jS/hQg1P+b1fz/m9X8/AACAP4mIiEAAAAAAAAAAAHb6NL9yCDU/HPZ/Pxz2fz8AAIA/mpmJQAAAAAAAAAAAP/o0v4UINT/p9X8/6fV/PwAAgD+rqopAAAAAAAAAAACY+TS/vAg1P0v1fz9L9X8/AACAP7y7i0AAAAAAAAAAAIX4NL8YCTU/R/R/P0f0fz8AAIA/zcyMQAAAAAAAAAAAB/c0v5cJNT/f8n8/3/J/PwAAgD/e3Y1AAAAAAAAAAAAc9TS/Owo1PxDxfz8Q8X8/AACAP+/ujkAAAAAAAAAAAMTyNL8DCzU/2+5/P9vufz8AAIA/AACQQAAAAAAAAAAA9+80v/ILNT837H8/N+x/PwAAgD8SEZFAAAAAAAAAAAD37zS/8gs1Pzfsfz837H8/AACAPyMikkAAAAAAAAAAAILOL7+QCzo/7+9/P+/vfz8AAIA/NDOTQAAAAAAAAAAAbokqv9HlPj9+838/fvN/PwAAgD9FRJRAAAAAAAAAAADQISW/u5lDP9n2fz/Z9n8/AACAP1ZVlUAAAAAAAAAAAMaYH79bJkg//Pl/P/z5fz8AAIA/Z2aWQAAAAAAAAAAAau8Zv8mKTD/Z/H8/2fx/PwAAgD94d5dAAAAAAAAAAADsJhS/H8ZQP2//fz9v/38/AACAP4mImEAAAAAAAAAAAHJADr+I11Q/2wCAP9sAgD8AAIA/mpmZQAAAAAAAAAAANz0Ivy2+WD/YAYA/2AGAPwAAgD+rqppAAAAAAAAAAABtHgK/SnlcP6wCgD+sAoA/AACAP7y7m0AAAAAAAAAAAKXK974bCGA/VgOAP1YDgD8AAIA/zcycQAAAAAAAAAAAWSbrvuxpYz/YA4A/2AOAPwAAgD/e3Z1AAAAAAAAAAACEUt6+DJ5mPzMEgD8zBIA/AACAP+/unkAAAAAAAAAAALdR0b7Yo2k/ZwSAP2cEgD8AAIA/AQCgQAAAAAAAAAAAmCbEvrR6bD95BIA/eQSAPwAAgD8SEaFAAAAAAAAAAABBJsS+xXpsP3gEgD94BIA/AACAPyMiokAAAAAAAAAAAFfItr6aI28/XwOAP18DgD8AAIA/NDOjQAAAAAAAAAAAJkapviWccT97AoA/ewKAPwAAgD9FRKRAAAAAAAAAAABTopu+6ONzP8YBgD/GAYA/AACAP1ZVpUAAAAAAAAAAAI/fjb5x+nU/OgGAPzoBgD8AAIA/Z2amQAAAAAAAAAAAmACAvlXfdz/QAIA/0ACAPwAAgD94d6dAAAAAAAAAAABUEGS+MpJ5P4MAgD+DAIA/AACAP4mIqEAAAAAAAAAAABjyR76zEns/TQCAP00AgD8AAIA/mpmpQAAAAAAAAAAAE6wrvotgfD8qAIA/KgCAPwAAgD+rqqpAAAAAAAAAAADfQw++eHt9PxQAgD8UAIA/AACAP7y7q0AAAAAAAAAAAFJ+5b1BY34/CACAPwgAgD8AAIA/zcysQAAAAAAAAAAAMUesvbkXfz8CAIA/AgCAPwAAgD/e3a1AAAAAAAAAAACL22W9uph/PwEAgD8BAIA/AACAP/DurkAAAAAAAAAAAM315bwt5n8/AACAPwAAgD8AAIA/" + } + ], + "meshes": [ + { + "name": "mesh_0", + "primitives": [ + { + "attributes": { + "COLOR_0": 3, + "JOINTS_0": 6, + "NORMAL": 5, + "POSITION": 2, + "TEXCOORD_0": 4, + "WEIGHTS_0": 7 + }, + "indices": 1, + "mode": 4 + } + ] + } + ], + "nodes": [ + { + "mesh": 0, + "name": "mesh_0", + "skin": 0 + }, + { + "children": [ + 2 + ], + "name": "bone_1", + "translation": [ + 0.0, + 1.0, + 0.0 + ] + }, + { + "name": "bone_2" + }, + { + "name": "bone_3" + } + ], + "scene": 0, + "scenes": [ + { + "nodes": [ + 0, + 1, + 3 + ] + } + ], + "skins": [ + { + "inverseBindMatrices": 0, + "joints": [ + 3, + 1, + 2 + ], + "name": "mesh_0", + "0skeleton": -1 + } + ] +} diff --git a/tests/tester.cc b/tests/tester.cc index e0a174b..c8acf43 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -432,11 +432,40 @@ TEST_CASE("serialize-empty-material", "[issue-294]") { nlohmann::json j = nlohmann::json::parse(os.str()); REQUIRE(1 == j["materials"].size()); - REQUIRE(j["asset"].is_null()); REQUIRE(j["materials"][0].is_object()); } +TEST_CASE("empty-skeleton-id", "[issue-321]") { + + tinygltf::Model model; + tinygltf::TinyGLTF ctx; + std::string err; + std::string warn; + + bool ret = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/regression/unassigned-skeleton.gltf"); + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + + REQUIRE(model.skins.size() == 1); + REQUIRE(model.skins[0].skeleton == -1); // unassigned + + std::stringstream os; + + ctx.WriteGltfSceneToStream(&model, os, false, false); + + // use nlohmann json + nlohmann::json j = nlohmann::json::parse(os.str()); + + // Ensure `skeleton` property is not written to .gltf(was serialized as -1) + REQUIRE(1 == j["skins"].size()); + REQUIRE(j["skins"][0].is_object()); + REQUIRE(j["skins"][0].count("skeleton") == 0); + +} + #ifndef TINYGLTF_NO_FS TEST_CASE("expandpath-utf-8", "[pr-226]") { diff --git a/tiny_gltf.h b/tiny_gltf.h index 87e3fb8..c5fa225 100644 --- a/tiny_gltf.h +++ b/tiny_gltf.h @@ -4,7 +4,7 @@ // // The MIT License (MIT) // -// Copyright (c) 2015 - 2020 Syoyo Fujita, Aurélien Chatelain and many +// Copyright (c) 2015 - Present Syoyo Fujita, Aurélien Chatelain and many // contributors. // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -27,7 +27,8 @@ // Version: // - v2.5.0 Add SetPreserveImageChannels() option to load image data as is. -// - v2.4.3 Fix null object output when when material has all default parameters. +// - v2.4.3 Fix null object output when when material has all default +// parameters. // - v2.4.2 Decode percent-encoded URI. // - v2.4.1 Fix some glTF object class does not have `extensions` and/or // `extras` property. @@ -190,14 +191,14 @@ AAssetManager *asset_manager = nullptr; #endif typedef enum { - NULL_TYPE = 0, - REAL_TYPE = 1, - INT_TYPE = 2, - BOOL_TYPE = 3, - STRING_TYPE = 4, - ARRAY_TYPE = 5, - BINARY_TYPE = 6, - OBJECT_TYPE = 7 + NULL_TYPE, + REAL_TYPE, + INT_TYPE, + BOOL_TYPE, + STRING_TYPE, + ARRAY_TYPE, + BINARY_TYPE, + OBJECT_TYPE } Type; static inline int32_t GetComponentSizeInBytes(uint32_t componentType) { @@ -297,7 +298,7 @@ class Value { DEFAULT_METHODS(Value) - char Type() const { return static_cast(type_); } + char Type() const { return static_cast(type_); } bool IsBool() const { return (type_ == BOOL_TYPE); } @@ -603,7 +604,7 @@ struct Sampler { // `magFilter`. Set -1 in TinyGLTF(issue #186) int minFilter = -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR", - // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_NEAREST", + // "NEAREST_MIPMAP_NEAREST", "LINEAR_MIPMAP_NEAREST", // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_LINEAR"] int magFilter = -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR"] @@ -849,8 +850,10 @@ struct Accessor { std::string extras_json_string; std::string extensions_json_string; - std::vector minValues; // optional - std::vector maxValues; // optional + std::vector + minValues; // optional. integer value is promoted to double + std::vector + maxValues; // optional. integer value is promoted to double struct { int count; @@ -1068,7 +1071,7 @@ struct Buffer { }; struct Asset { - std::string version; // required + std::string version = "2.0"; // required std::string generator; std::string minVersion; std::string copyright; @@ -1190,7 +1193,8 @@ enum SectionCheck { /// typedef bool (*LoadImageDataFunction)(Image *, const int, std::string *, std::string *, int, int, - const unsigned char *, int, void *user_pointer); + const unsigned char *, int, + void *user_pointer); /// /// WriteImageDataFunction type. Signature for custom image writing callbacks. @@ -1397,9 +1401,7 @@ class TinyGLTF { preserve_image_channels_ = onoff; } - bool GetPreserveImageChannels() const { - return preserve_image_channels_; - } + bool GetPreserveImageChannels() const { return preserve_image_channels_; } private: /// @@ -1420,7 +1422,8 @@ class TinyGLTF { bool store_original_json_for_extras_and_extensions_ = false; - bool preserve_image_channels_ = false; /// Default false(expand channels to RGBA) for backward compatibility. + bool preserve_image_channels_ = false; /// Default false(expand channels to + /// RGBA) for backward compatibility. FsCallbacks fs = { #ifndef TINYGLTF_NO_FS @@ -1535,6 +1538,7 @@ class TinyGLTF { #ifndef TINYGLTF_USE_RAPIDJSON #include "json.hpp" #else +#ifndef TINYGLTF_NO_INCLUDE_RAPIDJSON #include "document.h" #include "prettywriter.h" #include "rapidjson.h" @@ -1542,6 +1546,7 @@ class TinyGLTF { #include "writer.h" #endif #endif +#endif #ifdef TINYGLTF_ENABLE_DRACO #include "draco/compression/decode.h" @@ -1707,14 +1712,14 @@ namespace tinygltf { /// /// Internal LoadImageDataOption struct. /// This struct is passed through `user_pointer` in LoadImageData. -/// The struct is not passed when the user supply their own LoadImageData callbacks. +/// The struct is not passed when the user supply their own LoadImageData +/// callbacks. /// -struct LoadImageDataOption -{ - // true: preserve image channels(e.g. load as RGB image if the image has RGB channels) - // default `false`(channels are expanded to RGBA for backward compatiblity). +struct LoadImageDataOption { + // true: preserve image channels(e.g. load as RGB image if the image has RGB + // channels) default `false`(channels are expanded to RGBA for backward + // compatiblity). bool preserve_channels{false}; - }; // Equals function for Value, for recursivity @@ -2363,8 +2368,8 @@ bool LoadImageData(Image *image, const int image_idx, std::string *err, unsigned char *data = nullptr; // preserve_channels true: Use channels stored in the image file. - // false: force 32-bit textures for common Vulkan compatibility. It appears that - // some GPU drivers do not support 24-bit images for Vulkan + // false: force 32-bit textures for common Vulkan compatibility. It appears + // that some GPU drivers do not support 24-bit images for Vulkan req_comp = option.preserve_channels ? 0 : 4; int bits = 8; int pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; @@ -4467,6 +4472,7 @@ static bool GetAttributeForAllPoints(uint32_t componentType, draco::Mesh *mesh, static bool ParseDracoExtension(Primitive *primitive, Model *model, std::string *err, const Value &dracoExtensionValue) { + (void)err; auto bufferViewValue = dracoExtensionValue.Get("bufferView"); if (!bufferViewValue.IsInt()) return false; auto attributesValue = dracoExtensionValue.Get("attributes"); @@ -4527,7 +4533,6 @@ static bool ParseDracoExtension(Primitive *primitive, Model *model, int dracoAttributeIndex = attribute.second.Get(); const auto pAttribute = mesh->GetAttributeByUniqueId(dracoAttributeIndex); - const auto pBuffer = pAttribute->buffer(); const auto componentType = model->accessors[primitiveAttribute->second].componentType; @@ -5827,13 +5832,13 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, { json_const_iterator it; if (FindMember(o, "extensions", it)) { - model->extensions_json_string = JsonToString(GetValue(it)); + scene.extensions_json_string = JsonToString(GetValue(it)); } } { json_const_iterator it; if (FindMember(o, "extras", it)) { - model->extras_json_string = JsonToString(GetValue(it)); + scene.extras_json_string = JsonToString(GetValue(it)); } } } @@ -6621,8 +6626,33 @@ static void SerializeGltfAccessor(Accessor &accessor, json &o) { SerializeNumberProperty("componentType", accessor.componentType, o); SerializeNumberProperty("count", accessor.count, o); - SerializeNumberArrayProperty("min", accessor.minValues, o); - SerializeNumberArrayProperty("max", accessor.maxValues, o); + + if ((accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) || + (accessor.componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE)) { + SerializeNumberArrayProperty("min", accessor.minValues, o); + SerializeNumberArrayProperty("max", accessor.maxValues, o); + } else { + // Issue #301. Serialize as integer. + // Assume int value is within [-2**31-1, 2**31-1] + { + std::vector values; + std::transform(accessor.minValues.begin(), accessor.minValues.end(), + std::back_inserter(values), + [](double v) { return static_cast(v); }); + + SerializeNumberArrayProperty("min", values, o); + } + + { + std::vector values; + std::transform(accessor.maxValues.begin(), accessor.maxValues.end(), + std::back_inserter(values), + [](double v) { return static_cast(v); }); + + SerializeNumberArrayProperty("max", values, o); + } + } + if (accessor.normalized) SerializeValue("normalized", Value(accessor.normalized), o); std::string type; @@ -6732,10 +6762,15 @@ static void SerializeGltfAsset(Asset &asset, json &o) { SerializeStringProperty("copyright", asset.copyright, o); } - if (!asset.version.empty()) { - SerializeStringProperty("version", asset.version, o); + if (asset.version.empty()) { + // Just in case + // `version` must be defined + asset.version = "2.0"; } + // TODO(syoyo): Do we need to check if `version` is greater or equal to 2.0? + SerializeStringProperty("version", asset.version, o); + if (asset.extras.Keys().size()) { SerializeValue("extras", asset.extras, o); } @@ -7208,11 +7243,17 @@ static void SerializeGltfScene(Scene &scene, json &o) { } static void SerializeGltfSkin(Skin &skin, json &o) { - if (skin.inverseBindMatrices != -1) - SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o); - + // required SerializeNumberArrayProperty("joints", skin.joints, o); - SerializeNumberProperty("skeleton", skin.skeleton, o); + + if (skin.inverseBindMatrices >= 0) { + SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o); + } + + if (skin.skeleton >= 0) { + SerializeNumberProperty("skeleton", skin.skeleton, o); + } + if (skin.name.size()) { SerializeStringProperty("name", skin.name, o); } @@ -7299,7 +7340,8 @@ static void SerializeGltfModel(Model *model, json &o) { if (JsonIsNull(material)) { // Issue 294. // `material` does not have any required parameters - // so the result may be null(unmodified) when all material parameters have default value. + // so the result may be null(unmodified) when all material parameters + // have default value. // // null is not allowed thus we create an empty JSON object. JsonSetObject(material); @@ -7442,7 +7484,7 @@ static void SerializeGltfModel(Model *model, json &o) { } // Extensions used - if (model->extensionsUsed.size()) { + if (extensionsUsed.size()) { SerializeStringArrayProperty("extensionsUsed", extensionsUsed, o); }