Merge remote-tracking branch 'origin/master' into channel_target_extensions

This commit is contained in:
Selmar Kok 2020-01-21 18:46:58 +01:00
commit e2c3fe1c0b
11 changed files with 650 additions and 51 deletions

View File

@ -34,8 +34,11 @@ If you are looking for old, C++03 version, please use `devel-picojson` branch.
* Moderate parsing time and memory consumption. * Moderate parsing time and memory consumption.
* glTF specification v2.0.0 * glTF specification v2.0.0
* [x] ASCII glTF * [x] ASCII glTF
* [x] Load
* [x] Save
* [x] Binary glTF(GLB) * [x] Binary glTF(GLB)
* [x] PBR material description * [x] Load
* [x] Save(.bin embedded .glb)
* Buffers * Buffers
* [x] Parse BASE64 encoded embedded buffer data(DataURI). * [x] Parse BASE64 encoded embedded buffer data(DataURI).
* [x] Load `.bin` file. * [x] Load `.bin` file.
@ -55,6 +58,7 @@ If you are looking for old, C++03 version, please use `devel-picojson` branch.
* [x] Image save * [x] Image save
* Extensions * Extensions
* [x] Draco mesh decoding * [x] Draco mesh decoding
* [ ] Draco mesh encoding
## Note on extension property ## Note on extension property
@ -78,6 +82,7 @@ In extension(`ExtensionMap`), JSON number value is parsed as int or float(number
* Lighthouse 2: a rendering framework for real-time ray tracing / path tracing experiments. https://github.com/jbikker/lighthouse2 * Lighthouse 2: a rendering framework for real-time ray tracing / path tracing experiments. https://github.com/jbikker/lighthouse2
* [QuickLook GLTF](https://github.com/toshiks/glTF-quicklook) - quicklook plugin for macos. Also SceneKit wrapper for tinygltf. * [QuickLook GLTF](https://github.com/toshiks/glTF-quicklook) - quicklook plugin for macos. Also SceneKit wrapper for tinygltf.
* [GlslViewer](https://github.com/patriciogonzalezvivo/glslViewer) - live GLSL coding for MacOS and Linux * [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.
* Your projects here! (Please send PR) * Your projects here! (Please send PR)
## TODOs ## TODOs
@ -159,14 +164,17 @@ if (!ret) {
### Saving gltTF 2.0 model ### Saving gltTF 2.0 model
* [ ] Buffers.
* Buffers.
* [x] To file * [x] To file
* [x] Embedded * [x] Embedded
* [ ] Draco compressed? * [ ] Draco compressed?
* [x] Images * [x] Images
* [x] To file * [x] To file
* [x] Embedded * [x] Embedded
* [ ] Binary(.glb) * Binary(.glb)
* [x] .bin embedded single .glb
* [ ] External .bin
## Running tests. ## Running tests.
@ -194,6 +202,10 @@ $ ./tester
$ ./tester_noexcept $ ./tester_noexcept
``` ```
### Fuzzing tests
See `tests/fuzzer` for details.
## Third party licenses ## Third party licenses
* json.hpp : Licensed under the MIT License <http://opensource.org/licenses/MIT>. Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>. * json.hpp : Licensed under the MIT License <http://opensource.org/licenses/MIT>. Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

View File

@ -0,0 +1,171 @@
{
"asset": {
"version": "2.0"
},
"scenes": [
{
"nodes": [
0
]
}
],
"scene": 0,
"nodes": [
{
"mesh": 0
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 2,
"POSITION": 1,
"TEXCOORD_0": 3
},
"indices": 0,
"mode": 4,
"material": 0
}
]
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0,
"texCoord": 0
},
"baseColorFactor": [
1,
1,
1,
1
],
"metallicFactor": 1,
"roughnessFactor": 1
},
"emissiveFactor": [
0,
0,
0
],
"alphaMode": "OPAQUE"
}
],
"textures": [
{
"source": 0,
"sampler": 0
}
],
"samplers": [
{
"wrapS": 33071,
"wrapT": 33071
}
],
"images": [
{
"uri": " 2x2 image has multiple spaces.png"
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5121,
"count": 36,
"normalized": false,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"normalized": false,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
},
{
"bufferView": 2,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"normalized": false,
"max": [
1,
1,
1
],
"min": [
-1,
-1,
-1
],
"type": "VEC3"
},
{
"bufferView": 3,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"normalized": false,
"max": [
1,
1
],
"min": [
0,
0
],
"type": "VEC2"
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 36
},
{
"buffer": 0,
"byteOffset": 36,
"byteLength": 288
},
{
"buffer": 0,
"byteOffset": 324,
"byteLength": 288
},
{
"buffer": 0,
"byteOffset": 612,
"byteLength": 192
}
],
"buffers": [
{
"byteLength": 804,
"uri": "CubeImageUriSpaces.bin"
}
]
}

Binary file not shown.

View File

@ -0,0 +1,171 @@
{
"asset": {
"version": "2.0"
},
"scenes": [
{
"nodes": [
0
]
}
],
"scene": 0,
"nodes": [
{
"mesh": 0
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 2,
"POSITION": 1,
"TEXCOORD_0": 3
},
"indices": 0,
"mode": 4,
"material": 0
}
]
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0,
"texCoord": 0
},
"baseColorFactor": [
1,
1,
1,
1
],
"metallicFactor": 1,
"roughnessFactor": 1
},
"emissiveFactor": [
0,
0,
0
],
"alphaMode": "OPAQUE"
}
],
"textures": [
{
"source": 0,
"sampler": 0
}
],
"samplers": [
{
"wrapS": 33071,
"wrapT": 33071
}
],
"images": [
{
"uri": "2x2 image has spaces.png"
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5121,
"count": 36,
"normalized": false,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"normalized": false,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
},
{
"bufferView": 2,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"normalized": false,
"max": [
1,
1,
1
],
"min": [
-1,
-1,
-1
],
"type": "VEC3"
},
{
"bufferView": 3,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"normalized": false,
"max": [
1,
1
],
"min": [
0,
0
],
"type": "VEC2"
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 36
},
{
"buffer": 0,
"byteOffset": 36,
"byteLength": 288
},
{
"buffer": 0,
"byteOffset": 324,
"byteLength": 288
},
{
"buffer": 0,
"byteOffset": 612,
"byteLength": 192
}
],
"buffers": [
{
"byteLength": 804,
"uri": "CubeImageUriSpaces.bin"
}
]
}

46
tests/fuzzer/README.md Normal file
View File

@ -0,0 +1,46 @@
# Fuzzing test
Do fuzzing test for TinyGLTF API.
## Supported API
* [x] LoadASCIIFromMemory
* [ ] LoadBinaryFromMemory
## Requirements
* meson
* clang with fuzzer support(`-fsanitize=fuzzer`. at least clang 8.0 should work)
## Setup
### Ubuntu 18.04
```
$ sudo apt install clang++-8
$ sudo apt install libfuzzer-8-dev
```
Optionally, if you didn't set `update-alternatives` you can set `clang++` to point to `clang++8`
```
$ sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-8 10
$ sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-8 10
```
## How to compile
```
$ CXX=clang++ CC=clang meson build
$ cd build
$ ninja
```
## How to run
Increase memory limit. e.g. `-rss_limit_mb=50000`
```
$ ./fuzz_gltf -rss_limit_mb=20000 -jobs 4
```

33
tests/fuzzer/fuzz_gltf.cc Normal file
View File

@ -0,0 +1,33 @@
#include <cstdint>
#include <cstring>
#include <memory>
#include <vector>
#include <iostream>
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define TINYGLTF_IMPLEMENTATION
#include "tiny_gltf.h"
static void parse_intCoding4(const uint8_t *data, size_t size)
{
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
const char *str = reinterpret_cast<const char *>(data);
bool ret = ctx.LoadASCIIFromString(&model, &err, &warn, str, size, /* base_dir */"" );
(void)ret;
}
extern "C"
int LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size)
{
parse_intCoding4(data, size);
return 0;
}

9
tests/fuzzer/meson.build Normal file
View File

@ -0,0 +1,9 @@
project('fuzz_tinygltf', 'cpp', default_options : ['cpp_std=c++11'])
incdirs = include_directories('../../')
executable('fuzz_gltf',
'fuzz_gltf.cc',
include_directories : incdirs,
cpp_args : '-fsanitize=address,fuzzer',
link_args : '-fsanitize=address,fuzzer' )

View File

@ -333,3 +333,29 @@ TEST_CASE("pbr-khr-texture-transform", "[material]") {
REQUIRE(scale[1] == Approx(-1.0)); REQUIRE(scale[1] == Approx(-1.0));
} }
TEST_CASE("image-uri-spaces", "[issue-236]") {
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
// Test image file with single spaces.
bool ret = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/CubeImageUriSpaces/CubeImageUriSpaces.gltf");
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(true == ret);
// Test image file with a beginning space, trailing space, and greater than
// one consecutive spaces.
ret = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/CubeImageUriSpaces/CubeImageUriMultipleSpaces.gltf");
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(true == ret);
}

View File

@ -799,12 +799,12 @@ struct Material {
struct BufferView { struct BufferView {
std::string name; std::string name;
int buffer; // Required int buffer{-1}; // Required
size_t byteOffset; // minimum 0, default 0 size_t byteOffset{0}; // minimum 0, default 0
size_t byteLength; // required, minimum 1 size_t byteLength{0}; // required, minimum 1. 0 = invalid
size_t byteStride; // minimum 4, maximum 252 (multiple of 4), default 0 = size_t byteStride{0}; // minimum 4, maximum 252 (multiple of 4), default 0 =
// understood to be tightly packed // understood to be tightly packed
int target; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] int target{0}; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] for vertex indices or atttribs. Could be 0 for other data
Value extras; Value extras;
ExtensionMap extensions; ExtensionMap extensions;
@ -812,9 +812,9 @@ struct BufferView {
std::string extras_json_string; std::string extras_json_string;
std::string extensions_json_string; std::string extensions_json_string;
bool dracoDecoded; // Flag indicating this has been draco decoded bool dracoDecoded{false}; // Flag indicating this has been draco decoded
BufferView() : byteOffset(0), byteStride(0), dracoDecoded(false) {} BufferView() : buffer(-1), byteOffset(0), byteLength(0), byteStride(0), target(0), dracoDecoded(false) {}
DEFAULT_METHODS(BufferView) DEFAULT_METHODS(BufferView)
bool operator==(const BufferView &) const; bool operator==(const BufferView &) const;
}; };
@ -889,8 +889,13 @@ struct Accessor {
// unreachable return 0; // unreachable return 0;
} }
Accessor() { Accessor() :
bufferView = -1; bufferView(-1),
byteOffset(0),
normalized(false),
componentType(-1),
count(0),
type(-1){
sparse.isSparse = false; sparse.isSparse = false;
} }
DEFAULT_METHODS(Accessor) DEFAULT_METHODS(Accessor)
@ -1138,7 +1143,7 @@ class Model {
std::vector<Scene> scenes; std::vector<Scene> scenes;
std::vector<Light> lights; std::vector<Light> lights;
int defaultScene; int defaultScene = -1;
std::vector<std::string> extensionsUsed; std::vector<std::string> extensionsUsed;
std::vector<std::string> extensionsRequired; std::vector<std::string> extensionsRequired;
@ -1417,6 +1422,7 @@ class TinyGLTF {
#include <algorithm> #include <algorithm>
//#include <cassert> //#include <cassert>
#ifndef TINYGLTF_NO_FS #ifndef TINYGLTF_NO_FS
#include <cstdio>
#include <fstream> #include <fstream>
#endif #endif
#include <sstream> #include <sstream>
@ -1543,6 +1549,13 @@ class TinyGLTF {
#undef NOMINMAX #undef NOMINMAX
#endif #endif
#if defined(__GLIBCXX__) // mingw
#include <ext/stdio_filebuf.h> // fstream (all sorts of IO stuff) + stdio_filebuf (=streambuf)
#include <fcntl.h> // _O_RDONLY
#endif
#elif !defined(__ANDROID__) #elif !defined(__ANDROID__)
#include <wordexp.h> #include <wordexp.h>
#endif #endif
@ -2394,11 +2407,20 @@ bool FileExists(const std::string &abs_filename, void *) {
} }
#else #else
#ifdef _WIN32 #ifdef _WIN32
FILE *fp; #if defined(_MSC_VER) || defined(__GLIBCXX__)
FILE *fp = nullptr;
errno_t err = _wfopen_s(&fp, UTF8ToWchar(abs_filename).c_str(), L"rb"); errno_t err = _wfopen_s(&fp, UTF8ToWchar(abs_filename).c_str(), L"rb");
if (err != 0) { if (err != 0) {
return false; return false;
} }
#else
FILE *fp = nullptr;
errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb");
if (err != 0) {
return false;
}
#endif
#else #else
FILE *fp = fopen(abs_filename.c_str(), "rb"); FILE *fp = fopen(abs_filename.c_str(), "rb");
#endif #endif
@ -2438,8 +2460,10 @@ std::string ExpandFilePath(const std::string &filepath, void *) {
return ""; return "";
} }
// Quote the string to keep any spaces in filepath intact.
std::string quoted_path = "\"" + filepath + "\"";
// char** w; // char** w;
int ret = wordexp(filepath.c_str(), &p, 0); int ret = wordexp(quoted_path.c_str(), &p, 0);
if (ret) { if (ret) {
// err // err
s = filepath; s = filepath;
@ -2491,7 +2515,15 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
} }
#else #else
#ifdef _WIN32 #ifdef _WIN32
#if defined(__GLIBCXX__) // mingw
int file_descriptor = _wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY);
__gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
std::istream f(&wfile_buf);
#elif defined(_MSC_VER)
std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary); std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary);
#else // clang?
std::ifstream f(filepath.c_str(), std::ifstream::binary);
#endif
#else #else
std::ifstream f(filepath.c_str(), std::ifstream::binary); std::ifstream f(filepath.c_str(), std::ifstream::binary);
#endif #endif
@ -2522,7 +2554,6 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
out->resize(sz); out->resize(sz);
f.read(reinterpret_cast<char *>(&out->at(0)), f.read(reinterpret_cast<char *>(&out->at(0)),
static_cast<std::streamsize>(sz)); static_cast<std::streamsize>(sz));
f.close();
return true; return true;
#endif #endif
@ -2531,7 +2562,15 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
bool WriteWholeFile(std::string *err, const std::string &filepath, bool WriteWholeFile(std::string *err, const std::string &filepath,
const std::vector<unsigned char> &contents, void *) { const std::vector<unsigned char> &contents, void *) {
#ifdef _WIN32 #ifdef _WIN32
#if defined(__GLIBCXX__) // mingw
int file_descriptor = _wopen(UTF8ToWchar(filepath).c_str(), _O_WRONLY | _O_BINARY);
__gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
std::ostream f(&wfile_buf);
#elif defined(_MSC_VER)
std::ofstream f(UTF8ToWchar(filepath).c_str(), std::ofstream::binary); std::ofstream f(UTF8ToWchar(filepath).c_str(), std::ofstream::binary);
#else // clang?
std::ofstream f(filepath.c_str(), std::ofstream::binary);
#endif
#else #else
std::ofstream f(filepath.c_str(), std::ofstream::binary); std::ofstream f(filepath.c_str(), std::ofstream::binary);
#endif #endif
@ -2551,7 +2590,6 @@ bool WriteWholeFile(std::string *err, const std::string &filepath,
return false; return false;
} }
f.close();
return true; return true;
} }
@ -2694,6 +2732,7 @@ bool DecodeDataURI(std::vector<unsigned char> *out, std::string &mime_type,
} }
} }
// TODO(syoyo): Allow empty buffer? #229
if (data.empty()) { if (data.empty()) {
return false; return false;
} }
@ -5225,7 +5264,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \ #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \
defined(_CPPUNWIND)) && \ defined(_CPPUNWIND)) && \
not defined(TINYGLTF_NOEXCEPTION) !defined(TINYGLTF_NOEXCEPTION)
try { try {
JsonParse(v, json_str, json_str_length, true); JsonParse(v, json_str, json_str_length, true);
@ -5498,7 +5537,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
// Assign missing bufferView target types // Assign missing bufferView target types
// - Look for missing Mesh indices // - Look for missing Mesh indices
// - Look for missing bufferView targets // - Look for missing Mesh attributes
for (auto &mesh : model->meshes) { for (auto &mesh : model->meshes) {
for (auto &primitive : mesh.primitives) { for (auto &primitive : mesh.primitives) {
if (primitive.indices > if (primitive.indices >
@ -5526,15 +5565,12 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
// we could optionally check if acessors' bufferView type is Scalar, as // we could optionally check if acessors' bufferView type is Scalar, as
// it should be // it should be
} }
for (auto &attribute : primitive.attributes) {
model->bufferViews[size_t(model->accessors[size_t(attribute.second)].bufferView)]
.target = TINYGLTF_TARGET_ARRAY_BUFFER;
} }
} }
// find any missing targets, must be an array buffer type if not fulfilled
// from previous check
for (auto &bufferView : model->bufferViews) {
if (bufferView.target == 0) // missing target type
{
bufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
}
} }
// 7. Parse Node // 7. Parse Node
@ -6251,22 +6287,44 @@ static void SerializeValue(const std::string &key, const Value &value,
static void SerializeGltfBufferData(const std::vector<unsigned char> &data, static void SerializeGltfBufferData(const std::vector<unsigned char> &data,
json &o) { json &o) {
std::string header = "data:application/octet-stream;base64,"; std::string header = "data:application/octet-stream;base64,";
if (data.size() > 0) {
std::string encodedData = std::string encodedData =
base64_encode(&data[0], static_cast<unsigned int>(data.size())); base64_encode(&data[0], static_cast<unsigned int>(data.size()));
SerializeStringProperty("uri", header + encodedData, o); SerializeStringProperty("uri", header + encodedData, o);
} else {
// Issue #229
// size 0 is allowd. Just emit mime header.
SerializeStringProperty("uri", header, o);
}
} }
static bool SerializeGltfBufferData(const std::vector<unsigned char> &data, static bool SerializeGltfBufferData(const std::vector<unsigned char> &data,
const std::string &binFilename) { const std::string &binFilename) {
#ifdef _WIN32 #ifdef _WIN32
#if defined(__GLIBCXX__) // mingw
int file_descriptor = _wopen(UTF8ToWchar(binFilename).c_str(), _O_WRONLY | _O_BINARY);
__gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
std::ostream output(&wfile_buf);
if (!wfile_buf.is_open()) return false;
#elif defined(_MSC_VER)
std::ofstream output(UTF8ToWchar(binFilename).c_str(), std::ofstream::binary); std::ofstream output(UTF8ToWchar(binFilename).c_str(), std::ofstream::binary);
if (!output.is_open()) return false;
#else #else
std::ofstream output(binFilename.c_str(), std::ofstream::binary); std::ofstream output(binFilename.c_str(), std::ofstream::binary);
#endif
if (!output.is_open()) return false; if (!output.is_open()) return false;
#endif
#else
std::ofstream output(binFilename.c_str(), std::ofstream::binary);
if (!output.is_open()) return false;
#endif
if (data.size() > 0) {
output.write(reinterpret_cast<const char *>(&data[0]), output.write(reinterpret_cast<const char *>(&data[0]),
std::streamsize(data.size())); std::streamsize(data.size()));
output.close(); } else {
// Issue #229
// size 0 will be still valid buffer data.
// write empty file.
}
return true; return true;
} }
@ -6337,6 +6395,7 @@ static void SerializeGltfAccessor(Accessor &accessor, json &o) {
SerializeNumberProperty<size_t>("count", accessor.count, o); SerializeNumberProperty<size_t>("count", accessor.count, o);
SerializeNumberArrayProperty<double>("min", accessor.minValues, o); SerializeNumberArrayProperty<double>("min", accessor.minValues, o);
SerializeNumberArrayProperty<double>("max", accessor.maxValues, o); SerializeNumberArrayProperty<double>("max", accessor.maxValues, o);
if (accessor.normalized)
SerializeValue("normalized", Value(accessor.normalized), o); SerializeValue("normalized", Value(accessor.normalized), o);
std::string type; std::string type;
switch (accessor.type) { switch (accessor.type) {
@ -6419,6 +6478,7 @@ static void SerializeGltfAnimation(Animation &animation, json &o) {
{ {
json samplers; json samplers;
JsonReserveArray(samplers, animation.samplers.size());
for (unsigned int i = 0; i < animation.samplers.size(); ++i) { for (unsigned int i = 0; i < animation.samplers.size(); ++i) {
json sampler; json sampler;
AnimationSampler gltfSampler = animation.samplers[i]; AnimationSampler gltfSampler = animation.samplers[i];
@ -6455,6 +6515,18 @@ static void SerializeGltfAsset(Asset &asset, json &o) {
SerializeExtensionMap(asset.extensions, o); SerializeExtensionMap(asset.extensions, o);
} }
static void SerializeGltfBufferBin(Buffer &buffer, json &o,
std::vector<unsigned char> &binBuffer) {
SerializeNumberProperty("byteLength", buffer.data.size(), o);
binBuffer=buffer.data;
if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o);
if (buffer.extras.Type() != NULL_TYPE) {
SerializeValue("extras", buffer.extras, o);
}
}
static void SerializeGltfBuffer(Buffer &buffer, json &o) { static void SerializeGltfBuffer(Buffer &buffer, json &o) {
SerializeNumberProperty("byteLength", buffer.data.size(), o); SerializeNumberProperty("byteLength", buffer.data.size(), o);
SerializeGltfBufferData(buffer.data, o); SerializeGltfBufferData(buffer.data, o);
@ -6734,7 +6806,7 @@ static void SerializeGltfMesh(Mesh &mesh, json &o) {
JsonAddMember(primitive, "targets", std::move(targets)); JsonAddMember(primitive, "targets", std::move(targets));
} }
SerializeExtensionMap(gltfPrimitive.extensions, o); SerializeExtensionMap(gltfPrimitive.extensions, primitive);
if (gltfPrimitive.extras.Type() != NULL_TYPE) { if (gltfPrimitive.extras.Type() != NULL_TYPE) {
SerializeValue("extras", gltfPrimitive.extras, primitive); SerializeValue("extras", gltfPrimitive.extras, primitive);
@ -7124,31 +7196,57 @@ static bool WriteGltfStream(std::ostream &stream, const std::string &content) {
static bool WriteGltfFile(const std::string &output, static bool WriteGltfFile(const std::string &output,
const std::string &content) { const std::string &content) {
#ifdef _WIN32 #ifdef _WIN32
#if defined(_MSC_VER)
std::ofstream gltfFile(UTF8ToWchar(output).c_str()); std::ofstream gltfFile(UTF8ToWchar(output).c_str());
#elif defined(__GLIBCXX__)
int file_descriptor = _wopen(UTF8ToWchar(output).c_str(), _O_WRONLY | _O_BINARY);
__gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
std::ostream gltfFile(&wfile_buf);
if (!wfile_buf.is_open()) return false;
#else #else
std::ofstream gltfFile(output.c_str()); std::ofstream gltfFile(output.c_str());
#endif
if (!gltfFile.is_open()) return false; if (!gltfFile.is_open()) return false;
#endif
#else
std::ofstream gltfFile(output.c_str());
if (!gltfFile.is_open()) return false;
#endif
return WriteGltfStream(gltfFile, content); return WriteGltfStream(gltfFile, content);
} }
static void WriteBinaryGltfStream(std::ostream &stream, static void WriteBinaryGltfStream(std::ostream &stream,
const std::string &content) { const std::string &content,
const std::vector<unsigned char> &binBuffer) {
const std::string header = "glTF"; const std::string header = "glTF";
const int version = 2; const int version = 2;
const int padding_size = content.size() % 4;
// 12 bytes for header, JSON content length, 8 bytes for JSON chunk info, // https://stackoverflow.com/questions/3407012/c-rounding-up-to-the-nearest-multiple-of-a-number
// padding auto roundUp = [](uint32_t numToRound, uint32_t multiple)
const int length = 12 + 8 + int(content.size()) + padding_size; {
if (multiple == 0)
return numToRound;
uint32_t remainder = numToRound % multiple;
if (remainder == 0)
return numToRound;
return numToRound + multiple - remainder;
};
const uint32_t padding_size = roundUp(uint32_t(content.size()), 4) - uint32_t(content.size());
// 12 bytes for header, JSON content length, 8 bytes for JSON chunk info.
// Chunk data must be located at 4-byte boundary.
const uint32_t length = 12 + 8 + roundUp(uint32_t(content.size()), 4)+
(binBuffer.size()?(8+roundUp(uint32_t(binBuffer.size()),4)) : 0);
stream.write(header.c_str(), std::streamsize(header.size())); stream.write(header.c_str(), std::streamsize(header.size()));
stream.write(reinterpret_cast<const char *>(&version), sizeof(version)); stream.write(reinterpret_cast<const char *>(&version), sizeof(version));
stream.write(reinterpret_cast<const char *>(&length), sizeof(length)); stream.write(reinterpret_cast<const char *>(&length), sizeof(length));
// JSON chunk info, then JSON data // JSON chunk info, then JSON data
const int model_length = int(content.size()) + padding_size; const uint32_t model_length = uint32_t(content.size()) + padding_size;
const int model_format = 0x4E4F534A; const uint32_t model_format = 0x4E4F534A;
stream.write(reinterpret_cast<const char *>(&model_length), stream.write(reinterpret_cast<const char *>(&model_length),
sizeof(model_length)); sizeof(model_length));
stream.write(reinterpret_cast<const char *>(&model_format), stream.write(reinterpret_cast<const char *>(&model_format),
@ -7160,16 +7258,41 @@ static void WriteBinaryGltfStream(std::ostream &stream,
const std::string padding = std::string(size_t(padding_size), ' '); const std::string padding = std::string(size_t(padding_size), ' ');
stream.write(padding.c_str(), std::streamsize(padding.size())); stream.write(padding.c_str(), std::streamsize(padding.size()));
} }
if (binBuffer.size() > 0){
const uint32_t bin_padding_size = roundUp(uint32_t(binBuffer.size()), 4) - uint32_t(binBuffer.size());
// BIN chunk info, then BIN data
const uint32_t bin_length = uint32_t(binBuffer.size()) + bin_padding_size;
const uint32_t bin_format = 0x004e4942;
stream.write(reinterpret_cast<const char *>(&bin_length),
sizeof(bin_length));
stream.write(reinterpret_cast<const char *>(&bin_format),
sizeof(bin_format));
stream.write(reinterpret_cast<const char *>(binBuffer.data()), std::streamsize(binBuffer.size()));
// Chunksize must be multiplies of 4, so pad with zeroes
if (bin_padding_size > 0) {
const std::vector<unsigned char> padding = std::vector<unsigned char>(size_t(bin_padding_size), 0);
stream.write(reinterpret_cast<const char *>(padding.data()), std::streamsize(padding.size()));
}
}
} }
static void WriteBinaryGltfFile(const std::string &output, static void WriteBinaryGltfFile(const std::string &output,
const std::string &content) { const std::string &content,
const std::vector<unsigned char> &binBuffer) {
#ifdef _WIN32 #ifdef _WIN32
#if defined(_MSC_VER)
std::ofstream gltfFile(UTF8ToWchar(output).c_str(), std::ios::binary); std::ofstream gltfFile(UTF8ToWchar(output).c_str(), std::ios::binary);
#elif defined(__GLIBCXX__)
int file_descriptor = _wopen(UTF8ToWchar(output).c_str(), _O_WRONLY | _O_BINARY);
__gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
std::ostream gltfFile(&wfile_buf);
#else #else
std::ofstream gltfFile(output.c_str(), std::ios::binary); std::ofstream gltfFile(output.c_str(), std::ios::binary);
#endif #endif
WriteBinaryGltfStream(gltfFile, content); #else
std::ofstream gltfFile(output.c_str(), std::ios::binary);
#endif
WriteBinaryGltfStream(gltfFile, content,binBuffer);
} }
bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream, bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream,
@ -7182,11 +7305,16 @@ bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream,
// BUFFERS // BUFFERS
std::vector<std::string> usedUris; std::vector<std::string> usedUris;
std::vector<unsigned char> binBuffer;
json buffers; json buffers;
JsonReserveArray(buffers, model->buffers.size()); JsonReserveArray(buffers, model->buffers.size());
for (unsigned int i = 0; i < model->buffers.size(); ++i) { for (unsigned int i = 0; i < model->buffers.size(); ++i) {
json buffer; json buffer;
if (writeBinary && i==0 && model->buffers[i].uri.empty()){
SerializeGltfBufferBin(model->buffers[i], buffer,binBuffer);
} else {
SerializeGltfBuffer(model->buffers[i], buffer); SerializeGltfBuffer(model->buffers[i], buffer);
}
JsonPushBack(buffers, std::move(buffer)); JsonPushBack(buffers, std::move(buffer));
} }
JsonAddMember(output, "buffers", std::move(buffers)); JsonAddMember(output, "buffers", std::move(buffers));
@ -7211,7 +7339,7 @@ bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream,
} }
if (writeBinary) { if (writeBinary) {
WriteBinaryGltfStream(stream, JsonToString(output)); WriteBinaryGltfStream(stream, JsonToString(output),binBuffer);
} else { } else {
WriteGltfStream(stream, JsonToString(output, prettyPrint ? 2 : -1)); WriteGltfStream(stream, JsonToString(output, prettyPrint ? 2 : -1));
} }
@ -7242,11 +7370,14 @@ bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename,
// BUFFERS // BUFFERS
std::vector<std::string> usedUris; std::vector<std::string> usedUris;
std::vector<unsigned char> binBuffer;
json buffers; json buffers;
JsonReserveArray(buffers, model->buffers.size()); JsonReserveArray(buffers, model->buffers.size());
for (unsigned int i = 0; i < model->buffers.size(); ++i) { for (unsigned int i = 0; i < model->buffers.size(); ++i) {
json buffer; json buffer;
if (embedBuffers) { if (writeBinary && i==0 && model->buffers[i].uri.empty()){
SerializeGltfBufferBin(model->buffers[i], buffer,binBuffer);
} else if (embedBuffers) {
SerializeGltfBuffer(model->buffers[i], buffer); SerializeGltfBuffer(model->buffers[i], buffer);
} else { } else {
std::string binSavePath; std::string binSavePath;
@ -7295,7 +7426,7 @@ bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename,
} }
if (writeBinary) { if (writeBinary) {
WriteBinaryGltfFile(filename, JsonToString(output)); WriteBinaryGltfFile(filename, JsonToString(output),binBuffer);
} else { } else {
WriteGltfFile(filename, JsonToString(output, (prettyPrint ? 2 : -1))); WriteGltfFile(filename, JsonToString(output, (prettyPrint ? 2 : -1)));
} }