Compare commits

...

55 Commits

Author SHA1 Message Date
Syoyo Fujita
cb0a8794c3 Keep up with tinyobj's API update. 2020-03-11 19:30:57 +09:00
Syoyo Fujita
60e22f3281 Initial success to convert .obj to .glTF mesh. 2020-03-11 17:58:02 +09:00
Syoyo Fujita
4cfd2849fb Implement .obj to glTF like mesh data converter 2020-03-10 22:19:14 +09:00
Syoyo Fujita
a5416707c9 update tinyobjloader.
mesh-modify experiment.
2020-03-09 22:34:50 +09:00
Syoyo Fujita
8ec9af7f2e Initial mesh-dump example. 2020-03-08 22:13:39 +09:00
Syoyo Fujita
40982716f9 Merge branch 'uri-decode' 2020-03-03 18:49:27 +09:00
Syoyo Fujita
4bdc96fb9b Update copyright year.
Add note on the result from fuzzer test.
2020-02-24 01:36:24 +09:00
Syoyo Fujita
96ecea080a Merge pull request #243 from mfpgs/reading_copyright
Reading copyright field in ParseAsset()
2020-02-20 23:42:14 +09:00
Miguel Sousa
22bfc843ee Reading copyright field in ParseAsset() 2020-02-20 14:16:58 +01:00
Syoyo Fujita
eaa25a307a Merge pull request #242 from Selmar/some_empty_array_fixes
Some empty array fixes
2020-01-29 01:27:44 +09:00
Selmar Kok
ab600b8e72 Merge remote-tracking branch 'origin/master' into some_empty_array_fixes 2020-01-28 13:46:22 +01:00
Selmar Kok
eb9d29c06e dont serialize a couple of empty arrays 2020-01-28 13:45:38 +01:00
Syoyo Fujita
57356933c5 Merge pull request #240 from kacprzak/master
Set target for bufferView used by MorphTargets
2020-01-22 22:32:41 +09:00
Marcin Kacprzak
c3d6716c56 Set target for bufferView used by MorphTargets 2020-01-22 13:13:35 +01:00
Syoyo Fujita
ac5e09f8c3 Merge pull request #239 from Selmar/channel_target_extensions
Channel target extensions
2020-01-22 03:52:20 +09:00
Selmar Kok
e2c3fe1c0b Merge remote-tracking branch 'origin/master' into channel_target_extensions 2020-01-21 18:46:58 +01:00
Selmar Kok
973d9b3394 add channel.target extensions 2020-01-21 18:45:24 +01:00
Syoyo Fujita
28ad4ab7b8 Add clang fuzzer tester. 2020-01-18 20:31:54 +09:00
Syoyo Fujita
0227ae5d9b Merge pull request #238 from sammyKhan/patch-1
Initialize defaultScene to -1
2020-01-17 21:58:00 +09:00
sammyKhan
a0a62bde1c Initialize defaultScene to -1
To prevent undefined behavior if the model is serialized without defaultScene being set explicitly.
2020-01-17 13:41:16 +01:00
Syoyo Fujita
23d1ff2880 Possible fix for error: invalid path 'models/CubeImageUriSpaces/ 2x2 image has multiple spaces.png ' on Windows(NTFS? dislikes extra white space after the file extension ) 2020-01-17 16:15:03 +09:00
Syoyo Fujita
bcf2ce586e Merge pull request #237 from FrankGalligan/fix_image_uri_with_spaces
Fix loading images with spaces on Linux
2020-01-14 18:00:48 +09:00
Frank Galligan
aa3c5a1cad Fix loading images with spaces on Linux
This change quotes the string before it is passed into wordexp.

This addresses issue https://github.com/syoyo/tinygltf/issues/236
2020-01-13 15:06:56 -08:00
Syoyo Fujita
db2fbeec9d Add dlib license. 2020-01-08 02:39:46 +09:00
Syoyo Fujita
c4166e4c60 Initial support of decoding percent-encoding URI. 2020-01-08 02:38:01 +09:00
Syoyo Fujita
1cf179d377 Merge pull request #235 from ziocleto/master
BufferView ctor now uses correct members order.
2020-01-08 00:52:09 +09:00
Syoyo Fujita
ddc14f8ba6 Merge pull request #233 from 8i/fix-animation-sampler-serialization
Reserve space for animation samplers in serialization
2020-01-08 00:47:41 +09:00
Syoyo Fujita
72f4a55edd Suppress clang warnings. Fixes #234 2020-01-08 00:40:41 +09:00
Dado
379bb612f1 BufferView ctor now uses correct members order.
tiny_gltf.h line 815 
(Moved target(0) to second to last)
2020-01-07 14:41:12 +00:00
Jacek
1da4e5d633 Reserve space for animation samplers in serialization 2020-01-06 15:15:21 -06:00
Syoyo Fujita
4e47bc7799 Fill BufferView variables with initial/invalid values just in case. 2020-01-02 22:07:25 +09:00
Syoyo Fujita
9cc3e91968 Merge pull request #232 from kacprzak/master
Do not set target on bufferView pointing to animation data
2020-01-02 21:53:53 +09:00
Marcin Kacprzak
d09788d10f Do not set target on bufferView pointing to animation data 2020-01-02 13:00:48 +01:00
Syoyo Fujita
a11f6e1939 Allow empty buffer when serializing glTF buffer data. 2019-12-19 14:20:01 +09:00
Syoyo Fujita
8ced108571 Merge pull request #230 from itainter/fix-primitive-extension
Fix the issue that the extension of primitive written as extension of mesh.
2019-12-18 13:33:46 +09:00
zhaozhiquan
3eb65e269a Fix the issue that the extension of primitive written as extension of mesh. 2019-12-18 11:28:57 +08:00
Syoyo Fujita
1322c7bf09 Merge branch 'master' of github.com:syoyo/tinygltf 2019-11-24 20:50:50 +09:00
Syoyo Fujita
49651fe3d9 Update READE. 2019-11-24 20:50:35 +09:00
Syoyo Fujita
db93a87c4a Merge pull request #226 from epajarre/glb-bin-blob
Glb bin blob
2019-11-19 01:30:10 +09:00
Eero Pajarre
7b0d81bf12 Added support for BIN chunk when saving in glb format
If saving in glb (binary) format the first buffer in
model is saved as gbl chunk number 1 in binary format.
This operation is not performed if the first buffer
as an url specified.
2019-11-18 14:15:48 +02:00
Eero Pajarre
2e8a115d7e Only serialize Accessor.normalized if it is true 2019-11-18 13:09:25 +02:00
Eero Pajarre
ae9364902a Added Accessor initializers
Added Accessor intializers
Now byteOffset and normalized are initialized to their default
values. componentType and Type are initialized to bad values
on purpose, they must be set when loading or creating the model.
2019-11-18 12:59:05 +02:00
Syoyo Fujita
06d2fbdae6 not is alternative keyword and not recommended to use. Fixes #225 2019-11-17 02:11:59 +09:00
Syoyo Fujita
d5ff5cbfb1 Merge branch 'master' of github.com:syoyo/tinygltf 2019-11-16 17:00:42 +09:00
Syoyo Fujita
1d20520680 Correct computation of padding size. Fixes #224. 2019-11-16 17:00:17 +09:00
Syoyo Fujita
4ab0386d09 Fix MinGW code path reused linux code path. 2019-11-10 15:31:17 +09:00
Syoyo Fujita
a471770f38 Merge pull request #223 from syoyo/mingw-fix
Mingw fix
2019-11-10 02:03:18 +09:00
Syoyo Fujita
125d8e50a9 fopen_s -> fopen in linux(posix) code path. 2019-11-09 20:52:56 +09:00
Syoyo Fujita
45cac78709 Fix utf8 filepath on MinGW based on PR 222. 2019-11-09 20:42:55 +09:00
Syoyo Fujita
a9d86c1af4 Add URL of Vulkan-Samples. 2019-11-08 14:45:16 +09:00
Syoyo Fujita
2f26eddac4 Merge pull request #220 from Selmar/doublesided_default_serialization
Doublesided default serialization
2019-11-01 13:39:36 +09:00
Selmar Kok
a0939550ca Merge branch 'master' of github.com:syoyo/tinygltf into doublesided_default_serialization 2019-10-31 15:10:03 +01:00
Selmar Kok
58ab95be2f only serialize doublesided if it is not the default value 2019-10-31 15:08:03 +01:00
Syoyo Fujita
bf0e4f8e45 Merge pull request #219 from syoyo/travis-gcc-4.8
Travis gcc 4.8
2019-10-31 19:34:33 +09:00
Syoyo Fujita
419162716e Introduce TINYGLTF_BUILD_EXAMPLES option to CMakeLists.txt. Fixes #218 2019-10-31 01:55:57 +09:00
20 changed files with 5016 additions and 561 deletions

View File

@@ -4,14 +4,21 @@ PROJECT (tinygltf)
SET(CMAKE_CXX_STANDARD 11)
ADD_EXECUTABLE ( loader_example
loader_example.cc
)
option(TINYGLTF_BUILD_EXAMPLES "Build examples" ON)
ADD_SUBDIRECTORY ( examples/gltfutil )
ADD_SUBDIRECTORY ( examples/glview )
ADD_SUBDIRECTORY ( examples/validator )
if (TINYGLTF_BUILD_EXAMPLES)
ADD_EXECUTABLE ( loader_example
loader_example.cc
)
ADD_SUBDIRECTORY ( examples/gltfutil )
ADD_SUBDIRECTORY ( examples/glview )
ADD_SUBDIRECTORY ( examples/validator )
endif (TINYGLTF_BUILD_EXAMPLES)
#
# TinuGLTF is a header-only library, so no library build. just install header files.
#
INSTALL ( FILES
json.hpp
stb_image.h

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.
* glTF specification v2.0.0
* [x] ASCII glTF
* [x] Load
* [x] Save
* [x] Binary glTF(GLB)
* [x] PBR material description
* [x] Load
* [x] Save(.bin embedded .glb)
* Buffers
* [x] Parse BASE64 encoded embedded buffer data(DataURI).
* [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
* Extensions
* [x] Draco mesh decoding
* [ ] Draco mesh encoding
## Note on extension property
@@ -75,9 +79,10 @@ In extension(`ExtensionMap`), JSON number value is parsed as int or float(number
* GLTF loader plugin for OGRE 2.1. Support for PBR materials via HLMS/PBS https://github.com/Ybalrid/Ogre_glTF
* [TinyGltfImporter](http://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html) plugin for [Magnum](https://github.com/mosra/magnum), a lightweight and modular C++11/C++14 graphics middleware for games and data visualization.
* [Diligent Engine](https://github.com/DiligentGraphics/DiligentEngine) - A modern cross-platform low-level graphics library and rendering framework
* 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.
* [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)
## TODOs
@@ -159,14 +164,17 @@ if (!ret) {
### Saving gltTF 2.0 model
* [ ] Buffers.
* Buffers.
* [x] To file
* [x] Embedded
* [ ] Draco compressed?
* [x] Images
* [x] To file
* [x] Embedded
* [ ] Binary(.glb)
* Binary(.glb)
* [x] .bin embedded single .glb
* [ ] External .bin
## Running tests.
@@ -194,9 +202,17 @@ $ ./tester
$ ./tester_noexcept
```
### Fuzzing tests
See `tests/fuzzer` for details.
After running fuzzer on Ryzen9 3950X a week, at least `LoadASCIIFromString` looks safe except for out-of-memory error in Fuzzer.
We may be better to introduce bounded memory size checking when parsing glTF data.
## Third party licenses
* json.hpp : Licensed under the MIT License <http://opensource.org/licenses/MIT>. Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
* stb_image : Public domain.
* catch : Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. Distributed under the Boost Software License, Version 1.0.
* RapidJSON : Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. http://rapidjson.org/
* dlib(uridecode, uriencode) : Copyright (C) 2003 Davis E. King Boost Software License 1.0. http://dlib.net/dlib/server/server_http.cpp.html

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
#EXTRA_CXXFLAGS := -fsanitize=address -fno-omit-frame-pointer -Wall -Werror -Weverything -Wno-c++11-long-long -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded
EXTRA_CXXFLAGS :=
all:
clang++ -std=c++11 -g -O1 -I../../ -I../common $(EXTRA_CXXFLAGS) -o mesh-modify mesh-modify.cc mesh-util.cc tinygltf_impl.cc

View File

@@ -0,0 +1,13 @@
# Mesh modify experiment
Sometimes we want to tweak mesh attributes(e.g. vertex position, uv coord, etc).
glTF itself does not allow ASCII representation of such data.
This example show how to
- Export mesh data from .bin to .obj
- Import mesh data to .bin(update corresponding buffer data) from .obj
## Requirement
Assume Buffer is stored as external file(`.bin`)

View File

@@ -0,0 +1,760 @@
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <limits>
#include <string>
#include <vector>
#include <fstream>
#if !defined(__ANDROID__) && !defined(_WIN32)
#include <wordexp.h>
#endif
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Weverything"
#endif
#include "../../json.hpp"
using json = nlohmann::json;
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#ifdef _WIN32
#include "../../tiny_gltf.h"
#else
#include "tiny_gltf.h"
#endif
#include "mesh-util.hh"
#ifdef __clang__
#pragma clang diagnostic ignored "-Wunused-function"
#endif
namespace {
static std::string PrintMode(int mode) {
if (mode == TINYGLTF_MODE_POINTS) {
return "POINTS";
} else if (mode == TINYGLTF_MODE_LINE) {
return "LINE";
} else if (mode == TINYGLTF_MODE_LINE_LOOP) {
return "LINE_LOOP";
} else if (mode == TINYGLTF_MODE_TRIANGLES) {
return "TRIANGLES";
} else if (mode == TINYGLTF_MODE_TRIANGLE_FAN) {
return "TRIANGLE_FAN";
} else if (mode == TINYGLTF_MODE_TRIANGLE_STRIP) {
return "TRIANGLE_STRIP";
}
return "**UNKNOWN**";
}
static std::string PrintTarget(int target) {
if (target == 34962) {
return "GL_ARRAY_BUFFER";
} else if (target == 34963) {
return "GL_ELEMENT_ARRAY_BUFFER";
} else {
return "**UNKNOWN**";
}
}
static std::string PrintType(int ty) {
if (ty == TINYGLTF_TYPE_SCALAR) {
return "SCALAR";
} else if (ty == TINYGLTF_TYPE_VECTOR) {
return "VECTOR";
} else if (ty == TINYGLTF_TYPE_VEC2) {
return "VEC2";
} else if (ty == TINYGLTF_TYPE_VEC3) {
return "VEC3";
} else if (ty == TINYGLTF_TYPE_VEC4) {
return "VEC4";
} else if (ty == TINYGLTF_TYPE_MATRIX) {
return "MATRIX";
} else if (ty == TINYGLTF_TYPE_MAT2) {
return "MAT2";
} else if (ty == TINYGLTF_TYPE_MAT3) {
return "MAT3";
} else if (ty == TINYGLTF_TYPE_MAT4) {
return "MAT4";
}
return "**UNKNOWN**";
}
static std::string PrintComponentType(int ty) {
if (ty == TINYGLTF_COMPONENT_TYPE_BYTE) {
return "BYTE";
} else if (ty == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) {
return "UNSIGNED_BYTE";
} else if (ty == TINYGLTF_COMPONENT_TYPE_SHORT) {
return "SHORT";
} else if (ty == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) {
return "UNSIGNED_SHORT";
} else if (ty == TINYGLTF_COMPONENT_TYPE_INT) {
return "INT";
} else if (ty == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) {
return "UNSIGNED_INT";
} else if (ty == TINYGLTF_COMPONENT_TYPE_FLOAT) {
return "FLOAT";
} else if (ty == TINYGLTF_COMPONENT_TYPE_DOUBLE) {
return "DOUBLE";
}
return "**UNKNOWN**";
}
#if 0
// TODO(syoyo): Support sparse accessor(sparse vertex attribute).
// TODO(syoyo): Support more data type
struct VertexAttrib {
std::string name;
// Value are converted to float type.
std::vector<float> data;
// Corresponding info in binary data
int data_type; // e.g. TINYGLTF_TYPE_VEC2
int component_type; // storage type. e.g.
// TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT
uint64_t buffer_offset{0};
size_t buffer_length{0};
};
struct MeshPrim {
std::string name;
int32_t id{-1};
int mode{TINYGLTF_MODE_TRIANGLES};
VertexAttrib position; // vec3
VertexAttrib normal; // vec3
VertexAttrib tangent; // vec4
VertexAttrib color; // vec3 or vec4
std::map<int, VertexAttrib> texcoords; // <slot, attrib> vec2
std::map<int, VertexAttrib> weights; // <slot, attrib>
std::map<int, VertexAttrib>
joints; // <slot, attrib> store data as float type
int indices_type{-1}; // storage type(componentType) of `indices`.
std::vector<uint32_t> indices; // vertex indices
};
#endif
#if 0
static std::string GetFilePathExtension(const std::string &FileName) {
if (FileName.find_last_of(".") != std::string::npos)
return FileName.substr(FileName.find_last_of(".") + 1);
return "";
}
#endif
static size_t ComponentTypeByteSize(int type) {
switch (type) {
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
case TINYGLTF_COMPONENT_TYPE_BYTE:
return sizeof(char);
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
case TINYGLTF_COMPONENT_TYPE_SHORT:
return sizeof(short);
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
case TINYGLTF_COMPONENT_TYPE_INT:
return sizeof(int);
case TINYGLTF_COMPONENT_TYPE_FLOAT:
return sizeof(float);
case TINYGLTF_COMPONENT_TYPE_DOUBLE:
return sizeof(double);
default:
return 0;
}
}
std::vector<uint8_t> LoadBin(const std::string &filename) {
std::vector<uint8_t> data;
std::ifstream is(filename, std::ios::binary | std::ios::in | std::ios::ate);
if (is.is_open()) {
size_t size = size_t(is.tellg());
is.seekg(0, std::ios::beg);
if (size < 4) {
std::cerr << "File size is zero or too short: " << size << "\n";
return data;
}
data.resize(size);
is.read(reinterpret_cast<char *>(data.data()), std::streamsize(size));
}
return data;
}
// TODO(syoyo): Use C++17 like filesystem library
bool FileExists(const std::string &abs_filename) {
bool ret;
#ifdef _WIN32
// TODO(syoyo): Support utf8 filepath
FILE *fp = nullptr;
errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb");
if (err != 0) {
return false;
}
#else
FILE *fp = fopen(abs_filename.c_str(), "rb");
#endif
if (fp) {
ret = true;
fclose(fp);
} else {
ret = false;
}
return ret;
}
static std::string JoinPath(const std::string &path0,
const std::string &path1) {
if (path0.empty()) {
return path1;
} else {
// check '/'
char lastChar = *path0.rbegin();
if (lastChar != '/') {
return path0 + std::string("/") + path1;
} else {
return path0 + path1;
}
}
}
std::string ExpandFilePath(const std::string &filepath) {
#ifdef _WIN32
DWORD len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0);
char *str = new char[len];
ExpandEnvironmentStringsA(filepath.c_str(), str, len);
std::string s(str);
delete[] str;
return s;
#else
#if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \
defined(__ANDROID__) || defined(__EMSCRIPTEN__)
// no expansion
std::string s = filepath;
#else
std::string s;
wordexp_t p;
if (filepath.empty()) {
return "";
}
// Quote the string to keep any spaces in filepath intact.
std::string quoted_path = "\"" + filepath + "\"";
// char** w;
int ret = wordexp(quoted_path.c_str(), &p, 0);
if (ret) {
// err
s = filepath;
return s;
}
// Use first element only.
if (p.we_wordv) {
s = std::string(p.we_wordv[0]);
wordfree(&p);
} else {
s = filepath;
}
#endif
return s;
#endif
}
static std::string FindFile(const std::vector<std::string> &paths,
const std::string &filepath) {
for (size_t i = 0; i < paths.size(); i++) {
std::string absPath = ExpandFilePath(JoinPath(paths[i], filepath));
if (FileExists(absPath)) {
return absPath;
}
}
return std::string();
}
#if 0
static std::string GetBaseDir(const std::string &filepath) {
if (filepath.find_last_of("/\\") != std::string::npos)
return filepath.substr(0, filepath.find_last_of("/\\"));
return "";
}
#endif
static int GetSlotId(const std::string &name) {
if (name.rfind("TEXCOORD_", 0) == 0) {
int id = 0;
sscanf(name.c_str(), "TEXCOORD_%d", &id);
return id;
} else if (name.rfind("WEIGHTS_", 0) == 0) {
int id = 0;
sscanf(name.c_str(), "WEIGHTS_%d", &id);
return id;
} else if (name.rfind("JOINTS_", 0) == 0) {
int id = 0;
sscanf(name.c_str(), "JOINTS_%d", &id);
return id;
}
return -1;
}
static bool IsAttributeSupported(const std::string &name) {
constexpr int max_slots = 8;
if (name.compare("POSITION") == 0) {
return true;
}
if (name.compare("NORMAL") == 0) {
return true;
}
if (name.compare("TANGENT") == 0) {
return true;
}
for (int i = 0; i < max_slots; i++) {
std::string n = "TEXCOORD_" + std::to_string(i);
if (n.compare(name) == 0) {
return true;
}
}
for (int i = 0; i < max_slots; i++) {
std::string n = "WEIGHTS_" + std::to_string(i);
if (n.compare(name) == 0) {
return true;
}
}
for (int i = 0; i < max_slots; i++) {
std::string n = "JOINTS_" + std::to_string(i);
if (n.compare(name) == 0) {
return true;
}
}
return false;
}
static float Unpack(const unsigned char *ptr, int type) {
if (type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) {
unsigned char data = *ptr;
return float(data);
} else if (type == TINYGLTF_COMPONENT_TYPE_BYTE) {
char data = static_cast<char>(*ptr);
return float(data);
} else if (type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) {
uint16_t data = *reinterpret_cast<const uint16_t *>(ptr);
return float(data);
} else if (type == TINYGLTF_COMPONENT_TYPE_SHORT) {
int16_t data = *reinterpret_cast<const int16_t *>(ptr);
return float(data);
} else if (type == TINYGLTF_COMPONENT_TYPE_FLOAT) {
float data = *reinterpret_cast<const float *>(ptr);
return data;
} else {
std::cerr << "???: Unsupported type: " << PrintComponentType(type) << "\n";
return 0.0f;
}
}
static uint32_t UnpackIndex(const unsigned char *ptr, int type) {
if (type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) {
unsigned char data = *ptr;
return uint32_t(data);
} else if (type == TINYGLTF_COMPONENT_TYPE_BYTE) {
char data = static_cast<char>(*ptr);
return uint32_t(data);
} else if (type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) {
uint16_t data = *reinterpret_cast<const uint16_t *>(ptr);
return uint32_t(data);
} else if (type == TINYGLTF_COMPONENT_TYPE_SHORT) {
int16_t data = *reinterpret_cast<const int16_t *>(ptr);
return uint32_t(data);
} else if (type == TINYGLTF_COMPONENT_TYPE_INT) {
// TODO(syoyo): Check overflow(2G+ index)
int32_t data = *reinterpret_cast<const int32_t *>(ptr);
return uint32_t(data);
} else if (type == TINYGLTF_COMPONENT_TYPE_SHORT) {
uint32_t data = *reinterpret_cast<const uint32_t *>(ptr);
return data;
} else {
std::cerr << "???: Unsupported type: " << PrintComponentType(type) << "\n";
return static_cast<uint32_t>(-1);
}
}
static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
example::MeshPrim *out) {
for (size_t i = 0; i < mesh.primitives.size(); i++) {
const tinygltf::Primitive &primitive = mesh.primitives[i];
if (primitive.indices < 0) {
std::cerr << "Primitive indices must be provided\n";
return false;
}
// indices.
{
const tinygltf::Accessor &indexAccessor =
model.accessors[size_t(primitive.indices)];
size_t num_elements = indexAccessor.count;
std::cout << "index.elements = " << num_elements << "\n";
size_t byte_stride = ComponentTypeByteSize(indexAccessor.componentType);
const tinygltf::BufferView &indexBufferView =
model.bufferViews[size_t(indexAccessor.bufferView)];
// should be 34963(ELEMENT_ARRAY_BUFFER)
std::cout << "index.target = " << PrintTarget(indexBufferView.target) << "\n";
if (indexBufferView.target != TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) {
std::cerr << "indexBufferView.target must be ELEMENT_ARRAY_BUFFER\n";
return false;
}
const tinygltf::Buffer &indexBuffer =
model.buffers[size_t(indexBufferView.buffer)];
std::vector<uint32_t> indices;
for (size_t k = 0; k < num_elements; k++) {
// TODO(syoyo): out-of-bounds check.
const unsigned char *ptr = indexBuffer.data.data() +
indexBufferView.byteOffset + (k * byte_stride) +
indexAccessor.byteOffset;
uint32_t idx = UnpackIndex(ptr, indexAccessor.componentType);
std::cout << "vertex_index[" << k << "] = " << idx << "\n";
indices.push_back(idx);
}
out->indices = indices;
out->indices_type = indexAccessor.componentType;
}
// attributes
{
std::map<std::string, int>::const_iterator it(
primitive.attributes.begin());
std::map<std::string, int>::const_iterator itEnd(
primitive.attributes.end());
for (; it != itEnd; it++) {
// it->first would be "POSITION", "NORMAL", "TEXCOORD_0", ...
if (!IsAttributeSupported(it->first)) {
std::cout << "Unsupported attribute: " << it->first << "\n";
continue;
}
if (size_t(it->second) >= model.accessors.size()) {
std::cerr << "Invalid accessor id: " << it->second << "\n";
return false;
}
const tinygltf::Accessor &accessor =
model.accessors[size_t(it->second)];
size_t elem_size = 1;
if (accessor.type == TINYGLTF_TYPE_SCALAR) {
elem_size = 1;
} else if (accessor.type == TINYGLTF_TYPE_VEC2) {
elem_size = 2;
} else if (accessor.type == TINYGLTF_TYPE_VEC3) {
elem_size = 3;
} else if (accessor.type == TINYGLTF_TYPE_VEC4) {
elem_size = 4;
} else {
std::cerr << "Invalid or unsupported accessor type: "
<< PrintType(accessor.type) << "\n";
return false;
}
std::cout << PrintComponentType(accessor.componentType) << "\n";
size_t byte_stride = ComponentTypeByteSize(accessor.componentType);
std::cout << "attribute: " << it->first << "\n";
// if (gGLProgramState.attribs[it->first] >= 0) {
// Compute byteStride from Accessor + BufferView combination.
int byteStride =
accessor.ByteStride(model.bufferViews[size_t(accessor.bufferView)]);
assert(byteStride != -1);
std::cout << "byteOffset: " << accessor.byteOffset << "\n";
const tinygltf::BufferView &bufferView =
model.bufferViews[size_t(accessor.bufferView)];
const tinygltf::Buffer &buffer =
model.buffers[size_t(bufferView.buffer)];
size_t num_elems = accessor.count * elem_size;
example::VertexAttrib attrib;
for (size_t k = 0; k < num_elems; k++) {
// TODO(syoyo): out-of-bounds check.
const unsigned char *ptr = buffer.data.data() +
bufferView.byteOffset + (k * byte_stride) +
accessor.byteOffset;
float value = Unpack(ptr, accessor.componentType);
std::cout << "[" << k << "] value = " << value << "\n";
attrib.data.push_back(value);
}
attrib.component_type = accessor.componentType;
attrib.data_type = accessor.type;
attrib.name = it->first;
if (attrib.name.compare("POSITION") == 0) {
out->position = attrib;
} else if (attrib.name.compare("NORMAL") == 0) {
out->normal = attrib;
} else if (attrib.name.compare("TANGENT") == 0) {
out->tangent = attrib;
} else if (attrib.name.rfind("TEXCOORD_", 0) == 0) {
int id = GetSlotId(attrib.name);
std::cout << "texcoord[" << id << "]\n";
out->texcoords[id] = attrib;
} else if (attrib.name.rfind("JOINTS_", 0) == 0) {
int id = GetSlotId(attrib.name);
std::cout << "joints[" << id << "]\n";
out->joints[id] = attrib;
} else if (attrib.name.rfind("WEIGHTS_", 0) == 0) {
int id = GetSlotId(attrib.name);
std::cout << "weights[" << id << "]\n";
out->weights[id] = attrib;
} else {
std::cerr << "???: attrib.name = " << attrib.name << "\n";
return false;
}
}
}
const tinygltf::Accessor &indexAccessor =
model.accessors[size_t(primitive.indices)];
(void)indexAccessor;
PrintMode(primitive.mode);
if (primitive.mode != TINYGLTF_MODE_TRIANGLES) {
std::cerr << "Supported Primitive mode is TRIANGLES only at the moment\n";
return false;
}
out->mode = primitive.mode;
out->name = mesh.name;
}
return true;
}
#if 0
static bool ExtractMesh(const std::string &asset_path, tinygltf::Model &model,
std::vector<example::MeshPrim> *outs) {
// Get .bin data
{
if (model.buffers.size() != 1) {
std::cerr << "Single element of `buffers` is supported at the moment.\n";
return false;
}
const tinygltf::Buffer &buffer = model.buffers[0];
if (buffer.uri.empty()) {
std::cerr << "buffer.uri must be a filepath.\n";
return false;
}
if (buffer.data.size() < 4) {
std::cerr << "Invalid buffer.byteLength.\n";
return false;
}
std::vector<std::string> search_paths;
search_paths.push_back(asset_path);
std::string abs_filepath = FindFile(search_paths, buffer.uri);
std::vector<uint8_t> bin = LoadBin(buffer.uri);
if (bin.size() != buffer.data.size()) {
std::cerr << "Byte size mismatch. Failed to load file: " << buffer.uri
<< "\n";
std::cerr << " .bin size = " << bin.size() << ", size in 'buffer.uri' = " << buffer.data.size() << "\n";
return false;
}
}
for (const auto &mesh : model.meshes) {
std::cout << "mesh.name: " << mesh.name << "\n";
example::MeshPrim output;
bool ret = DumpMesh(model, mesh, &output);
if (!ret) {
return false;
}
outs->push_back(output);
}
return true;
}
#endif
} // namespace
int main(int argc, char **argv) {
if (argc < 2) {
std::cout << "mesh-dump input.gltf" << std::endl;
}
#if 0
tinygltf::Model model;
tinygltf::TinyGLTF loader;
std::string err;
std::string warn;
#ifdef _WIN32
std::string input_filename(argv[1] ? argv[1]
: "../../../models/Cube/Cube.gltf");
#else
std::string input_filename(argv[1] ? argv[1] : "../../models/Cube/Cube.gltf");
#endif
std::string ext = GetFilePathExtension(input_filename);
{
bool ret = false;
if (ext.compare("glb") == 0) {
// assume binary glTF.
ret = loader.LoadBinaryFromFile(&model, &err, &warn,
input_filename.c_str());
} else {
// assume ascii glTF.
ret =
loader.LoadASCIIFromFile(&model, &err, &warn, input_filename.c_str());
}
if (!warn.empty()) {
printf("Warn: %s\n", warn.c_str());
}
if (!err.empty()) {
printf("ERR: %s\n", err.c_str());
}
if (!ret) {
printf("Failed to load .glTF : %s\n", argv[1]);
exit(-1);
}
}
json j;
{
std::ifstream i(input_filename);
i >> j;
}
std::cout << "j = " << j << "\n";
json j_patch = R"([
{ "op": "add", "path": "/buffers/-", "value": {
"name": "plane/data",
"byteLength": 480,
"uri": "plane1.bin"
} }
])"_json;
// a JSON value
json j_original = R"({
"baz": ["one", "two", "three"],
"foo": "bar"
})"_json;
//json j_patch = R"([
// { "op": "remove", "path": "/buffers" }
//])"_json;
std::cout << "patch = " << j_patch.dump(2) << "\n";
json j_ret = j.patch(j_patch);
std::cout << "patched = " << j_ret.dump(2) << "\n";
std::string basedir = GetBaseDir(input_filename);
std::vector<example::MeshPrim> meshes;
bool ret = ExtractMesh(basedir, model, &meshes);
size_t n = 0;
for (const auto &mesh : meshes) {
// Assume no duplicated name in .glTF data
std::string filename;
if (mesh.name.empty()) {
filename = "untitled-" + std::to_string(n) + ".obj";
} else {
filename = mesh.name + ".obj";
}
bool flip_y = true; // flip texcoord Y?
bool ok = example::SaveAsObjMesh(filename, mesh,);
if (!ok) {
return EXIT_FAILURE;
}
n++;
}
return ret ? EXIT_SUCCESS : EXIT_FAILURE;
#else
{
std::string input_filename(argv[1]);
// Require facevarying layout?
// false = try to keep GL-like mesh data as much as possible.
// true = reorder vertex data and re-assign vertex indices.
bool facevarying = false;
example::MeshPrim mesh;
bool ok = example::LoadObjMesh(input_filename, facevarying, &mesh);
if (!ok) {
return EXIT_FAILURE;
}
PrintMeshPrim(mesh);
std::string output_filename("output.gltf");
ok = example::SaveAsGLTFMesh(output_filename, mesh);
if (!ok) {
std::cerr << "Failed to save mesh as glTF\n";
return EXIT_FAILURE;
}
std::cout << "Write glTF: " << output_filename << "\n";
}
return EXIT_SUCCESS;
#endif
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
#pragma once
#include <vector>
#include <map>
namespace example {
// TODO(syoyo): Support sparse accessor(sparse vertex attribute).
// TODO(syoyo): Support more data type
struct VertexAttrib {
std::string name;
// Value are converted to float type.
std::vector<float> data;
// Corresponding info in binary data
int data_type; // e.g. TINYGLTF_TYPE_VEC2
int component_type; // storage type. e.g.
// TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT
uint64_t buffer_offset{0};
size_t buffer_length{0};
// Optional.
std::vector<double> minValues;
std::vector<double> maxValues;
size_t numElements() const;
};
struct MeshPrim {
std::string name;
int32_t id{-1};
int mode; // e.g. TRIANGLES
VertexAttrib position; // vec3
VertexAttrib normal; // vec3
VertexAttrib tangent; // vec4
VertexAttrib color; // vec3 or vec4
std::map<int, VertexAttrib> texcoords; // <slot, attrib> vec2
std::map<int, VertexAttrib> weights; // <slot, attrib> vec4
std::map<int, VertexAttrib>
joints; // <slot, attrib> store data as float type
// min/max of index value. -1 = undef
int indices_min{-1};
int indices_max{-1};
int indices_type{-1}; // storage type(componentType) of `indices`.
std::vector<uint32_t> indices; // vertex indices
};
///
/// Save MeshPrim as wavefront .obj
///
bool SaveAsObjMesh(const std::string &filename, const MeshPrim &mesh);
///
/// Save MeshPrim as glTF mesh
///
bool SaveAsGLTFMesh(const std::string &filename, const MeshPrim &mesh);
///
/// Loads .obj and convert to MeshPrim
///
/// @param[in] facevarying Construct mesh with facevarying vertex layout(default false)
///
bool LoadObjMesh(const std::string &filename, bool facevarying, MeshPrim *mesh);
///
/// Print MeshPrim datra
///
void PrintMeshPrim(const MeshPrim &mesh);
} // namespace example

View File

@@ -0,0 +1,7 @@
project('mesh-modify', 'cpp', default_options : ['cpp_std=c++11'])
thread_dep = dependency('threads')
incdir = include_directories(['../../', '../common'])
executable('mesh-modify', ['mesh-modify.cc', 'mesh-util.cc', 'tinygltf_impl.cc'], include_directories : incdir, dependencies : thread_dep)

View File

@@ -150,6 +150,7 @@ bool LoadObj(const std::string &filename, float scale,
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warn;
std::string err;
std::string basedir = GetBaseDir(filename) + "/";
@@ -158,12 +159,16 @@ bool LoadObj(const std::string &filename, float scale,
// auto t_start = std::chrono::system_clock::now();
bool ret =
tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename.c_str(),
tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filename.c_str(),
basepath, /* triangulate */ true);
// auto t_end = std::chrono::system_clock::now();
// std::chrono::duration<double, std::milli> ms = t_end - t_start;
if (!warn.empty()) {
std::cout << warn << std::endl;
}
if (!err.empty()) {
std::cerr << err << std::endl;
}

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));
}
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

@@ -4,7 +4,7 @@
//
// The MIT License (MIT)
//
// Copyright (c) 2015 - 2019 Syoyo Fujita, Aurélien Chatelain and many
// Copyright (c) 2015 - 2020 Syoyo Fujita, Aurélien Chatelain and many
// contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -26,6 +26,7 @@
// THE SOFTWARE.
// Version:
// - v2.4.2 Decode percent-encoded URI.
// - v2.4.1 Fix some glTF object class does not have `extensions` and/or
// `extras` property.
// - v2.4.0 Experimental RapidJSON and C++14 support(Thanks to @jrkoone).
@@ -526,10 +527,12 @@ struct AnimationChannel {
// "weights"]
Value extras;
ExtensionMap extensions;
ExtensionMap target_extensions;
// Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
std::string extras_json_string;
std::string extensions_json_string;
std::string target_extensions_json_string;
AnimationChannel() : sampler(-1), target_node(-1) {}
DEFAULT_METHODS(AnimationChannel)
@@ -637,7 +640,8 @@ struct Image {
int bufferView; // (required if no uri)
std::string mimeType; // (required if no uri) ["image/jpeg", "image/png",
// "image/bmp", "image/gif"]
std::string uri; // (required if no mimeType)
std::string uri; // (required if no mimeType) uri is not decoded(e.g.
// whitespace may be represented as %20)
Value extras;
ExtensionMap extensions;
@@ -797,12 +801,13 @@ struct Material {
struct BufferView {
std::string name;
int buffer; // Required
size_t byteOffset; // minimum 0, default 0
size_t byteLength; // required, minimum 1
size_t byteStride; // minimum 4, maximum 252 (multiple of 4), default 0 =
// understood to be tightly packed
int target; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"]
int buffer{-1}; // Required
size_t byteOffset{0}; // minimum 0, default 0
size_t byteLength{0}; // required, minimum 1. 0 = invalid
size_t byteStride{0}; // minimum 4, maximum 252 (multiple of 4), default 0 =
// understood to be tightly packed
int target{0}; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] for vertex indices
// or atttribs. Could be 0 for other data
Value extras;
ExtensionMap extensions;
@@ -810,9 +815,15 @@ struct BufferView {
std::string extras_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)
bool operator==(const BufferView &) const;
};
@@ -887,8 +898,13 @@ struct Accessor {
// unreachable return 0;
}
Accessor() {
bufferView = -1;
Accessor()
: bufferView(-1),
byteOffset(0),
normalized(false),
componentType(-1),
count(0),
type(-1) {
sparse.isSparse = false;
}
DEFAULT_METHODS(Accessor)
@@ -1032,6 +1048,7 @@ struct Buffer {
std::vector<unsigned char> data;
std::string
uri; // considered as required here but not in the spec (need to clarify)
// uri is not decoded(e.g. whitespace may be represented as %20)
Value extras;
ExtensionMap extensions;
@@ -1136,7 +1153,7 @@ class Model {
std::vector<Scene> scenes;
std::vector<Light> lights;
int defaultScene;
int defaultScene = -1;
std::vector<std::string> extensionsUsed;
std::vector<std::string> extensionsRequired;
@@ -1415,6 +1432,7 @@ class TinyGLTF {
#include <algorithm>
//#include <cassert>
#ifndef TINYGLTF_NO_FS
#include <cstdio>
#include <fstream>
#endif
#include <sstream>
@@ -1541,6 +1559,14 @@ class TinyGLTF {
#undef NOMINMAX
#endif
#if defined(__GLIBCXX__) // mingw
#include <fcntl.h> // _O_RDONLY
#include <ext/stdio_filebuf.h> // fstream (all sorts of IO stuff) + stdio_filebuf (=streambuf)
#endif
#elif !defined(__ANDROID__)
#include <wordexp.h>
#endif
@@ -2106,6 +2132,88 @@ std::string base64_decode(std::string const &encoded_string) {
#pragma clang diagnostic pop
#endif
// https://github.com/syoyo/tinygltf/issues/228
// TODO(syoyo): Use uriparser https://uriparser.github.io/ for stricter Uri
// decoding?
//
// https://stackoverflow.com/questions/18307429/encode-decode-url-in-c
// http://dlib.net/dlib/server/server_http.cpp.html
// --- dlib beign ------------------------------------------------------------
// Copyright (C) 2003 Davis E. King (davis@dlib.net)
// License: Boost Software License See LICENSE.txt for the full license.
namespace dlib {
#if 0
inline unsigned char to_hex( unsigned char x )
{
return x + (x > 9 ? ('A'-10) : '0');
}
const std::string urlencode( const std::string& s )
{
std::ostringstream os;
for ( std::string::const_iterator ci = s.begin(); ci != s.end(); ++ci )
{
if ( (*ci >= 'a' && *ci <= 'z') ||
(*ci >= 'A' && *ci <= 'Z') ||
(*ci >= '0' && *ci <= '9') )
{ // allowed
os << *ci;
}
else if ( *ci == ' ')
{
os << '+';
}
else
{
os << '%' << to_hex(static_cast<unsigned char>(*ci >> 4)) << to_hex(static_cast<unsigned char>(*ci % 16));
}
}
return os.str();
}
#endif
inline unsigned char from_hex(unsigned char ch) {
if (ch <= '9' && ch >= '0')
ch -= '0';
else if (ch <= 'f' && ch >= 'a')
ch -= 'a' - 10;
else if (ch <= 'F' && ch >= 'A')
ch -= 'A' - 10;
else
ch = 0;
return ch;
}
static const std::string urldecode(const std::string &str) {
using namespace std;
string result;
string::size_type i;
for (i = 0; i < str.size(); ++i) {
if (str[i] == '+') {
result += ' ';
} else if (str[i] == '%' && str.size() > i + 2) {
const unsigned char ch1 =
from_hex(static_cast<unsigned char>(str[i + 1]));
const unsigned char ch2 =
from_hex(static_cast<unsigned char>(str[i + 2]));
const unsigned char ch = static_cast<unsigned char>((ch1 << 4) | ch2);
result += static_cast<char>(ch);
i += 2;
} else {
result += str[i];
}
}
return result;
}
} // namespace dlib
// --- dlib end --------------------------------------------------------------
static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
std::string *warn, const std::string &filename,
const std::string &basedir, bool required,
@@ -2366,9 +2474,11 @@ void TinyGLTF::SetFsCallbacks(FsCallbacks callbacks) { fs = callbacks; }
#ifdef _WIN32
static inline std::wstring UTF8ToWchar(const std::string &str) {
int wstr_size = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
int wstr_size =
MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
std::wstring wstr(wstr_size, 0);
MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstr[0], (int)wstr.size());
MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstr[0],
(int)wstr.size());
return wstr;
}
#endif
@@ -2392,11 +2502,20 @@ bool FileExists(const std::string &abs_filename, void *) {
}
#else
#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");
if (err != 0) {
return false;
}
#else
FILE *fp = nullptr;
errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb");
if (err != 0) {
return false;
}
#endif
#else
FILE *fp = fopen(abs_filename.c_str(), "rb");
#endif
@@ -2436,8 +2555,10 @@ std::string ExpandFilePath(const std::string &filepath, void *) {
return "";
}
// Quote the string to keep any spaces in filepath intact.
std::string quoted_path = "\"" + filepath + "\"";
// char** w;
int ret = wordexp(filepath.c_str(), &p, 0);
int ret = wordexp(quoted_path.c_str(), &p, 0);
if (ret) {
// err
s = filepath;
@@ -2489,7 +2610,16 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
}
#else
#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);
#else // clang?
std::ifstream f(filepath.c_str(), std::ifstream::binary);
#endif
#else
std::ifstream f(filepath.c_str(), std::ifstream::binary);
#endif
@@ -2520,7 +2650,6 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
out->resize(sz);
f.read(reinterpret_cast<char *>(&out->at(0)),
static_cast<std::streamsize>(sz));
f.close();
return true;
#endif
@@ -2529,7 +2658,16 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
bool WriteWholeFile(std::string *err, const std::string &filepath,
const std::vector<unsigned char> &contents, void *) {
#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);
#else // clang?
std::ofstream f(filepath.c_str(), std::ofstream::binary);
#endif
#else
std::ofstream f(filepath.c_str(), std::ofstream::binary);
#endif
@@ -2549,7 +2687,6 @@ bool WriteWholeFile(std::string *err, const std::string &filepath,
return false;
}
f.close();
return true;
}
@@ -2692,6 +2829,7 @@ bool DecodeDataURI(std::vector<unsigned char> *out, std::string &mime_type,
}
}
// TODO(syoyo): Allow empty buffer? #229
if (data.empty()) {
return false;
}
@@ -3472,6 +3610,7 @@ static bool ParseAsset(Asset *asset, std::string *err, const json &o,
ParseStringProperty(&asset->version, err, o, "version", true, "Asset");
ParseStringProperty(&asset->generator, err, o, "generator", false, "Asset");
ParseStringProperty(&asset->minVersion, err, o, "minVersion", false, "Asset");
ParseStringProperty(&asset->copyright, err, o, "copyright", false, "Asset");
ParseExtensionsProperty(&asset->extensions, err, o);
@@ -3610,7 +3749,10 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err,
#ifdef TINYGLTF_NO_EXTERNAL_IMAGE
return true;
#endif
if (!LoadExternalFile(&img, err, warn, uri, basedir, false, 0, false, fs)) {
std::string decoded_uri = dlib::urldecode(uri);
if (!LoadExternalFile(&img, err, warn, decoded_uri, basedir,
/* required */ false, /* required bytes */ 0,
/* checksize */ false, fs)) {
if (warn) {
(*warn) += "Failed to load external 'uri' for image[" +
std::to_string(image_idx) + "] name = [" + image->name +
@@ -3832,9 +3974,10 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
}
} else {
// External .bin file.
std::string decoded_uri = dlib::urldecode(buffer->uri);
if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr,
buffer->uri, basedir, true, byteLength, true,
fs)) {
decoded_uri, basedir, /* required */ true,
byteLength, /* checkSize */ true, fs)) {
return false;
}
}
@@ -3876,8 +4019,10 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
}
} else {
// Assume external .bin file.
if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, buffer->uri,
basedir, true, byteLength, true, fs)) {
std::string decoded_uri = dlib::urldecode(buffer->uri);
if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, decoded_uri,
basedir, /* required */ true, byteLength,
/* checkSize */ true, fs)) {
return false;
}
}
@@ -4715,6 +4860,13 @@ static bool ParseAnimationChannel(
}
return false;
}
ParseExtensionsProperty(&channel->target_extensions, err, target_object);
if (store_original_json_for_extras_and_extensions) {
json_const_iterator it;
if (FindMember(target_object, "extensions", it)) {
channel->target_extensions_json_string = JsonToString(GetValue(it));
}
}
}
channel->sampler = samplerIndex;
@@ -5216,7 +5368,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \
defined(_CPPUNWIND)) && \
not defined(TINYGLTF_NOEXCEPTION)
!defined(TINYGLTF_NOEXCEPTION)
try {
JsonParse(v, json_str, json_str_length, true);
@@ -5489,7 +5641,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
// Assign missing bufferView target types
// - Look for missing Mesh indices
// - Look for missing bufferView targets
// - Look for missing Mesh attributes
for (auto &mesh : model->meshes) {
for (auto &primitive : mesh.primitives) {
if (primitive.indices >
@@ -5517,14 +5669,20 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
// we could optionally check if acessors' bufferView type is Scalar, as
// it should be
}
}
}
// 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;
for (auto &attribute : primitive.attributes) {
model
->bufferViews[size_t(
model->accessors[size_t(attribute.second)].bufferView)]
.target = TINYGLTF_TARGET_ARRAY_BUFFER;
}
for(auto &target : primitive.targets) {
for(auto &attribute : target) {
model->bufferViews[size_t(model->accessors[size_t(attribute.second)].bufferView)]
.target = TINYGLTF_TARGET_ARRAY_BUFFER;
}
}
}
}
@@ -6242,22 +6400,45 @@ static void SerializeValue(const std::string &key, const Value &value,
static void SerializeGltfBufferData(const std::vector<unsigned char> &data,
json &o) {
std::string header = "data:application/octet-stream;base64,";
std::string encodedData =
base64_encode(&data[0], static_cast<unsigned int>(data.size()));
SerializeStringProperty("uri", header + encodedData, o);
if (data.size() > 0) {
std::string encodedData =
base64_encode(&data[0], static_cast<unsigned int>(data.size()));
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,
const std::string &binFilename) {
#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);
if (!output.is_open()) return false;
#else
std::ofstream output(binFilename.c_str(), std::ofstream::binary);
#endif
if (!output.is_open()) return false;
output.write(reinterpret_cast<const char *>(&data[0]),
std::streamsize(data.size()));
output.close();
#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]),
std::streamsize(data.size()));
} else {
// Issue #229
// size 0 will be still valid buffer data.
// write empty file.
}
return true;
}
@@ -6328,7 +6509,8 @@ static void SerializeGltfAccessor(Accessor &accessor, json &o) {
SerializeNumberProperty<size_t>("count", accessor.count, o);
SerializeNumberArrayProperty<double>("min", accessor.minValues, o);
SerializeNumberArrayProperty<double>("max", accessor.maxValues, o);
SerializeValue("normalized", Value(accessor.normalized), o);
if (accessor.normalized)
SerializeValue("normalized", Value(accessor.normalized), o);
std::string type;
switch (accessor.type) {
case TINYGLTF_TYPE_SCALAR:
@@ -6369,6 +6551,8 @@ static void SerializeGltfAnimationChannel(AnimationChannel &channel, json &o) {
SerializeNumberProperty("node", channel.target_node, target);
SerializeStringProperty("path", channel.target_path, target);
SerializeExtensionMap(channel.target_extensions, target);
JsonAddMember(o, "target", std::move(target));
}
@@ -6408,6 +6592,7 @@ static void SerializeGltfAnimation(Animation &animation, json &o) {
{
json samplers;
JsonReserveArray(samplers, animation.samplers.size());
for (unsigned int i = 0; i < animation.samplers.size(); ++i) {
json sampler;
AnimationSampler gltfSampler = animation.samplers[i];
@@ -6444,6 +6629,18 @@ static void SerializeGltfAsset(Asset &asset, json &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) {
SerializeNumberProperty("byteLength", buffer.data.size(), o);
SerializeGltfBufferData(buffer.data, o);
@@ -6502,6 +6699,7 @@ static void SerializeGltfImage(Image &image, json &o) {
SerializeStringProperty("mimeType", image.mimeType, o);
SerializeNumberProperty<int>("bufferView", image.bufferView, o);
} else {
// TODO(syoyo): dlib::urilencode?
SerializeStringProperty("uri", image.uri, o);
}
@@ -6618,7 +6816,8 @@ static void SerializeGltfMaterial(Material &material, json &o) {
SerializeStringProperty("alphaMode", material.alphaMode, o);
}
JsonAddMember(o, "doubleSided", json(material.doubleSided));
if (material.doubleSided != false)
JsonAddMember(o, "doubleSided", json(material.doubleSided));
if (material.normalTexture.index > -1) {
json texinfo;
@@ -6722,7 +6921,7 @@ static void SerializeGltfMesh(Mesh &mesh, json &o) {
JsonAddMember(primitive, "targets", std::move(targets));
}
SerializeExtensionMap(gltfPrimitive.extensions, o);
SerializeExtensionMap(gltfPrimitive.extensions, primitive);
if (gltfPrimitive.extras.Type() != NULL_TYPE) {
SerializeValue("extras", gltfPrimitive.extras, primitive);
@@ -6919,14 +7118,16 @@ static void SerializeGltfTexture(Texture &texture, json &o) {
///
static void SerializeGltfModel(Model *model, json &o) {
// ACCESSORS
json accessors;
JsonReserveArray(accessors, model->accessors.size());
for (unsigned int i = 0; i < model->accessors.size(); ++i) {
json accessor;
SerializeGltfAccessor(model->accessors[i], accessor);
JsonPushBack(accessors, std::move(accessor));
if (model->accessors.size()) {
json accessors;
JsonReserveArray(accessors, model->accessors.size());
for (unsigned int i = 0; i < model->accessors.size(); ++i) {
json accessor;
SerializeGltfAccessor(model->accessors[i], accessor);
JsonPushBack(accessors, std::move(accessor));
}
JsonAddMember(o, "accessors", std::move(accessors));
}
JsonAddMember(o, "accessors", std::move(accessors));
// ANIMATIONS
if (model->animations.size()) {
@@ -6949,14 +7150,16 @@ static void SerializeGltfModel(Model *model, json &o) {
JsonAddMember(o, "asset", std::move(asset));
// BUFFERVIEWS
json bufferViews;
JsonReserveArray(bufferViews, model->bufferViews.size());
for (unsigned int i = 0; i < model->bufferViews.size(); ++i) {
json bufferView;
SerializeGltfBufferView(model->bufferViews[i], bufferView);
JsonPushBack(bufferViews, std::move(bufferView));
if(model->bufferViews.size()) {
json bufferViews;
JsonReserveArray(bufferViews, model->bufferViews.size());
for (unsigned int i = 0; i < model->bufferViews.size(); ++i) {
json bufferView;
SerializeGltfBufferView(model->bufferViews[i], bufferView);
JsonPushBack(bufferViews, std::move(bufferView));
}
JsonAddMember(o, "bufferViews", std::move(bufferViews));
}
JsonAddMember(o, "bufferViews", std::move(bufferViews));
// Extensions used
if (model->extensionsUsed.size()) {
@@ -7112,31 +7315,57 @@ static bool WriteGltfStream(std::ostream &stream, const std::string &content) {
static bool WriteGltfFile(const std::string &output,
const std::string &content) {
#ifdef _WIN32
#if defined(_MSC_VER)
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
std::ofstream gltfFile(output.c_str());
#endif
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);
}
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 int version = 2;
const int padding_size = content.size() % 4;
// 12 bytes for header, JSON content length, 8 bytes for JSON chunk info,
// padding
const int length = 12 + 8 + int(content.size()) + padding_size;
// https://stackoverflow.com/questions/3407012/c-rounding-up-to-the-nearest-multiple-of-a-number
auto roundUp = [](uint32_t numToRound, uint32_t multiple) {
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(reinterpret_cast<const char *>(&version), sizeof(version));
stream.write(reinterpret_cast<const char *>(&length), sizeof(length));
// JSON chunk info, then JSON data
const int model_length = int(content.size()) + padding_size;
const int model_format = 0x4E4F534A;
const uint32_t model_length = uint32_t(content.size()) + padding_size;
const uint32_t model_format = 0x4E4F534A;
stream.write(reinterpret_cast<const char *>(&model_length),
sizeof(model_length));
stream.write(reinterpret_cast<const char *>(&model_format),
@@ -7148,16 +7377,46 @@ static void WriteBinaryGltfStream(std::ostream &stream,
const std::string padding = std::string(size_t(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,
const std::string &content) {
const std::string &content,
const std::vector<unsigned char> &binBuffer) {
#ifdef _WIN32
#if defined(_MSC_VER)
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
std::ofstream gltfFile(output.c_str(), std::ios::binary);
#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,
@@ -7169,15 +7428,21 @@ bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream,
SerializeGltfModel(model, output);
// BUFFERS
std::vector<std::string> usedUris;
json buffers;
JsonReserveArray(buffers, model->buffers.size());
for (unsigned int i = 0; i < model->buffers.size(); ++i) {
json buffer;
SerializeGltfBuffer(model->buffers[i], buffer);
JsonPushBack(buffers, std::move(buffer));
std::vector<unsigned char> binBuffer;
if(model->buffers.size()) {
json buffers;
JsonReserveArray(buffers, model->buffers.size());
for (unsigned int i = 0; i < model->buffers.size(); ++i) {
json buffer;
if (writeBinary && i==0 && model->buffers[i].uri.empty()){
SerializeGltfBufferBin(model->buffers[i], buffer,binBuffer);
} else {
SerializeGltfBuffer(model->buffers[i], buffer);
}
JsonPushBack(buffers, std::move(buffer));
}
JsonAddMember(output, "buffers", std::move(buffers));
}
JsonAddMember(output, "buffers", std::move(buffers));
// IMAGES
if (model->images.size()) {
@@ -7199,7 +7464,7 @@ bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream,
}
if (writeBinary) {
WriteBinaryGltfStream(stream, JsonToString(output));
WriteBinaryGltfStream(stream, JsonToString(output), binBuffer);
} else {
WriteGltfStream(stream, JsonToString(output, prettyPrint ? 2 : -1));
}
@@ -7230,42 +7495,47 @@ bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename,
// BUFFERS
std::vector<std::string> usedUris;
json buffers;
JsonReserveArray(buffers, model->buffers.size());
for (unsigned int i = 0; i < model->buffers.size(); ++i) {
json buffer;
if (embedBuffers) {
SerializeGltfBuffer(model->buffers[i], buffer);
} else {
std::string binSavePath;
std::string binUri;
if (!model->buffers[i].uri.empty() && !IsDataURI(model->buffers[i].uri)) {
binUri = model->buffers[i].uri;
std::vector<unsigned char> binBuffer;
if (model->buffers.size()) {
json buffers;
JsonReserveArray(buffers, model->buffers.size());
for (unsigned int i = 0; i < model->buffers.size(); ++i) {
json buffer;
if (writeBinary && i==0 && model->buffers[i].uri.empty()){
SerializeGltfBufferBin(model->buffers[i], buffer,binBuffer);
} else if (embedBuffers) {
SerializeGltfBuffer(model->buffers[i], buffer);
} else {
binUri = defaultBinFilename + defaultBinFileExt;
bool inUse = true;
int numUsed = 0;
while (inUse) {
inUse = false;
for (const std::string &usedName : usedUris) {
if (binUri.compare(usedName) != 0) continue;
inUse = true;
binUri = defaultBinFilename + std::to_string(numUsed++) +
defaultBinFileExt;
break;
std::string binSavePath;
std::string binUri;
if (!model->buffers[i].uri.empty() && !IsDataURI(model->buffers[i].uri)) {
binUri = model->buffers[i].uri;
} else {
binUri = defaultBinFilename + defaultBinFileExt;
bool inUse = true;
int numUsed = 0;
while (inUse) {
inUse = false;
for (const std::string &usedName : usedUris) {
if (binUri.compare(usedName) != 0) continue;
inUse = true;
binUri = defaultBinFilename + std::to_string(numUsed++) +
defaultBinFileExt;
break;
}
}
}
usedUris.push_back(binUri);
binSavePath = JoinPath(baseDir, binUri);
if (!SerializeGltfBuffer(model->buffers[i], buffer, binSavePath,
binUri)) {
return false;
}
}
usedUris.push_back(binUri);
binSavePath = JoinPath(baseDir, binUri);
if (!SerializeGltfBuffer(model->buffers[i], buffer, binSavePath,
binUri)) {
return false;
}
JsonPushBack(buffers, std::move(buffer));
}
JsonPushBack(buffers, std::move(buffer));
}
JsonAddMember(output, "buffers", std::move(buffers));
}
// IMAGES
if (model->images.size()) {
@@ -7283,7 +7553,7 @@ bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename,
}
if (writeBinary) {
WriteBinaryGltfFile(filename, JsonToString(output));
WriteBinaryGltfFile(filename, JsonToString(output), binBuffer);
} else {
WriteGltfFile(filename, JsonToString(output, (prettyPrint ? 2 : -1)));
}