Compare commits

...

36 Commits

Author SHA1 Message Date
Syoyo Fujita
fb99999c1a Support uint 2020-03-25 17:48:37 +09:00
Syoyo Fujita
be9ff66eeb Added support concatinating materials, images, textures and samplers. 2020-03-13 19:23:49 +09:00
Syoyo Fujita
0a4e8b761f Fix inequality. 2020-03-13 18:42:06 +09:00
Syoyo Fujita
4adeae9709 Skip counting the number of materials in target glTF if materials does not exist. 2020-03-13 15:08:42 +09:00
Syoyo Fujita
6224e021ce Support concatinating multiple glTF files. 2020-03-13 14:23:44 +09:00
Syoyo Fujita
5dcf778b31 Add feature to repace mesh.primitive.indices accessor. 2020-03-12 22:08:32 +09:00
Syoyo Fujita
289cc448ed Fix redundant printf 2020-03-12 21:57:41 +09:00
Syoyo Fujita
0ee2120965 Assign name for accessor.
Improve .obj export.
Use clipp to parse cmd options.
Add some handy python scripts.
2020-03-12 21:31:12 +09:00
Syoyo Fujita
cf30d42cbc Rename.
Add some missing files.
2020-03-11 22:12:55 +09:00
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
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
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
27 changed files with 12387 additions and 505 deletions

View File

@@ -71,6 +71,7 @@ In extension(`ExtensionMap`), JSON number value is parsed as int or float(number
* [glview](examples/glview) : Simple glTF geometry viewer.
* [validator](examples/validator) : Simple glTF validator with JSON schema.
* [basic](examples/basic) : Basic glTF viewer with texturing support.
* [mesh-conv](examples/mesh-conv) : Convert glTF mesh to wavefront .obj, wavefront .obj to glTF mesh.
## Projects using TinyGLTF
@@ -202,6 +203,13 @@ $ ./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>.
@@ -209,3 +217,7 @@ $ ./tester_noexcept
* 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
### Used in examples
* clipp: MIT License. https://github.com/muellan/clipp

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2017 André Müller; foss@andremueller-online.de
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

7024
examples/common/clipp.h Normal file

File diff suppressed because it is too large Load Diff

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-conv mesh-conv.cc mesh-util.cc tinygltf_impl.cc

View File

@@ -0,0 +1,5 @@
all:
ninja
clean:
ninja -t clean

View File

@@ -0,0 +1,58 @@
# 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 so we need to write a converter.
This example show how to
- Export mesh data from .bin to .obj
- Import mesh data to .bin(update corresponding buffer data) from .obj
## Features
* Support skin weights(`JOINTS_N`, `WEIGHTS_N`)
## Supported attributes
* [x] POSITION
* [x] NORMAL
* [x] TANGENT
* [ ] COLOR (vertex color)
* [x] TEXCOORD_N
* Only single texcoord(uv set) is supported
* Specify `--uvset 1` to specify which UV to use.
* [x] WEIGHTS_N, JOINTS_N
## Usage
### Wavefront .obj to glTF
```
$ mesh-modify --op=obj2gltf input.obj
```
All shapes in .obj are concatenated and create single glTF mesh.
(tinyobjloader's skin weight extension `vw` supported)
#### Limitation
Buffer is stored as external file(`.bin`)
### glTF to Wavefront .obj
```
$ mesh-modify --op=gltf2obj input.gltf
```
.obj will be created for each glTF Mesh.
(Skin weight data is exported to .obj using tinyobjloader's skin weight extension `vw`)
#### Limitation
Buffer is stored as external file(`.bin`)
## TODO
* [ ] obj2gltf: Assign glTF material from .mtl
* [ ] modify/patch mesh geometry in glTF
* By using JSON Patch or JSON Merge feature

View File

@@ -0,0 +1,3 @@
rm -rf build
CXX=clang++ CC=clang meson build -Db_sanitize=address -Db_lundef=false --buildtype=debug
cp Makefile.meson build/Makefile

View File

@@ -0,0 +1,798 @@
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <limits>
#include <string>
#include <vector>
#if !defined(__ANDROID__) && !defined(_WIN32)
#include <wordexp.h>
#endif
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Weverything"
#endif
#include "../../json.hpp"
#include "../common/clipp.h"
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
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 "";
}
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();
}
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 "";
}
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_UNSIGNED_INT) {
uint32_t data = *reinterpret_cast<const uint32_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,
bool verbose, example::MeshPrim *out) {
out->prims.clear();
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;
}
example::PrimSet out_prim;
// 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);
if (verbose) {
std::cout << "vertex_index[" << k << "] = " << idx << "\n";
}
indices.push_back(idx);
}
out_prim.indices = indices;
out_prim.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);
if (verbose) {
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_prim.position = attrib;
} else if (attrib.name.compare("NORMAL") == 0) {
out_prim.normal = attrib;
} else if (attrib.name.compare("TANGENT") == 0) {
out_prim.tangent = attrib;
} else if (attrib.name.rfind("TEXCOORD_", 0) == 0) {
int id = GetSlotId(attrib.name);
std::cout << "texcoord[" << id << "]\n";
out_prim.texcoords[id] = attrib;
} else if (attrib.name.rfind("JOINTS_", 0) == 0) {
int id = GetSlotId(attrib.name);
std::cout << "joints[" << id << "]\n";
out_prim.joints[id] = attrib;
} else if (attrib.name.rfind("WEIGHTS_", 0) == 0) {
int id = GetSlotId(attrib.name);
std::cout << "weights[" << id << "]\n";
out_prim.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_prim.mode = primitive.mode;
out->prims.push_back(out_prim);
}
out->name = mesh.name;
return true;
}
static bool ExtractMesh(const std::string &asset_path, tinygltf::Model &model,
bool verbose, 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(abs_filepath);
if (bin.size() != buffer.data.size()) {
std::cerr << "Byte size mismatch. Failed to load file: " << buffer.uri
<< "\n";
std::cerr << " Searched absolute file path: " << abs_filepath << "\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, verbose, &output);
if (!ret) {
return false;
}
outs->push_back(output);
}
return true;
}
} // namespace
int main(int argc, char **argv) {
std::string op;
std::string input_filename;
std::string output_filename = "output.gltf";
int uvset = 0;
bool verbose = false;
bool export_skinweight = true;
bool no_flip_texcoord_y = false;
auto cli =
(clipp::required("-i", "--input") &
clipp::value("input filename", input_filename),
clipp::option("-o", "--outout") &
clipp::value("Output filename(obj2fltf)", output_filename),
clipp::option("-v", "--verbose").set(verbose).doc("Verbose output"),
clipp::option("--export_skinweight") &
clipp::value("Export skin weights(gltf2obj). default true.",
export_skinweight),
clipp::option("--uvset").set(uvset).doc("UV set(TEXCOORD_N) to use"),
clipp::option("--op") &
clipp::value("operation mode(`gltf2obj`, `obj2gltf`", op),
clipp::option("--no-flip-texcoord-y")
.set(no_flip_texcoord_y)
.doc("Do not flip texcoord Y"));
if (!clipp::parse(argc, argv, cli)) {
std::cout << clipp::make_man_page(cli, argv[0]);
return EXIT_FAILURE;
}
if (op == "gltf2obj") {
tinygltf::Model model;
tinygltf::TinyGLTF loader;
std::string err;
std::string warn;
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);
}
}
#if 0
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";
#endif
std::string basedir = GetBaseDir(input_filename);
std::vector<example::MeshPrim> meshes;
bool ret = ExtractMesh(basedir, model, verbose, &meshes);
size_t n = 0;
for (const auto &mesh : meshes) {
// Assume no duplicated name in .glTF data
std::string basename;
if (mesh.name.empty()) {
basename = "untitled-" + std::to_string(n);
} else {
basename = mesh.name;
}
for (size_t primid = 0; primid < mesh.prims.size(); primid++) {
example::ObjExportOption options;
options.primid = int(primid);
options.export_skinweights = export_skinweight;
options.uvset = uvset;
options.flip_texcoord_y = !no_flip_texcoord_y;
bool ok = example::SaveAsObjMesh(basename, mesh, options);
if (!ok) {
std::cout << "Failed to export mesh[" << mesh.name << "].primitives["
<< primid << "]\n";
// may ok;
}
}
n++;
}
return ret ? EXIT_SUCCESS : EXIT_FAILURE;
} else if (op == "obj2gltf") {
// Require facevarying layout?
// facevarying representation is required if a vertex can have multiple
// normal/uv value. drawback of facevarying is mesh data increases. false =
// try to keep shared vertex representation as much as possible. true =
// reorder vertex data and re-assign vertex indices for facevarying data
// layout.
bool facevarying = false;
example::MeshPrim mesh;
bool ok = example::LoadObjMesh(input_filename, facevarying, &mesh);
if (!ok) {
return EXIT_FAILURE;
}
if (verbose) {
PrintMeshPrim(mesh);
}
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;
} else {
std::cerr << "Unknown operation: " << op << "\n";
return EXIT_FAILURE;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,95 @@
#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 PrimSet {
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
};
struct MeshPrim {
std::string name;
int32_t id{-1};
std::vector<PrimSet> prims;
};
struct ObjExportOption
{
bool export_skinweights{true};
int primid{0}; /// Primitive id to export(default 0).
int uvset{0}; /// Tex coord ID to export(default 0).
bool flip_texcoord_y{true}; /// Flip texture coordinate V?(default true).
};
///
/// Save MeshPrim as wavefront .obj
///
/// @param[in] basename Base filename. ".obj" will be appended.
/// @param[in] mesh MeshPrim.
/// @param[in] option Export options
///
bool SaveAsObjMesh(const std::string &basename, const MeshPrim &mesh, const ObjExportOption &option);
//
/// 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-conv', 'cpp', default_options : ['cpp_std=c++11'])
thread_dep = dependency('threads')
incdir = include_directories(['../../', '../common'])
executable('mesh-conv', ['mesh-conv.cc', 'mesh-util.cc', 'tinygltf_impl.cc'], include_directories : incdir, dependencies : thread_dep)

View File

@@ -0,0 +1,27 @@
# concat_mesh.py
Append(merge) mesh of glTF A to glTF B.
`meshes`, `accessors`, `bufferViews`, `materials` of glTF A is appended to glTF B(index to accessor, bufferViews, etc will be recomputed).
`skin`, `nodes`, etc are not appended(to be merged).
## Usage
concatenate sourceN.gltf to target.gltf and save it to merged.gltf
```
$ python concat_mesh.py source0.gltf <source1.gltf source2.gltf ...> target.gltf merged.gltf
```
## TODO
* [x] Support multiple glTFs to merge(concat)
* [ ] Support merging skin
* [ ] Support merging different node hierarchies
* [x] Support `images`, `textures`, `materials`, `samplers`
* [ ] Support other glTF info
# replace_attrib.py
Replace the accessor id of specified attribute.

View File

@@ -0,0 +1,272 @@
# concat mesh to glTF
# support multiple source glTF to me concatenated to target.gltf
# TODO: skins, nodes, scenes
import json
import sys, os
g_prefix = "added/"
def adjustMaterialTextureIndex(mat, texture_index_offset):
# Args: mat(dict)
# texture_index_offset(int) Index offset
if "normalTexture" in mat:
if "index" in mat["normalTexture"]:
mat["normalTexture"]["index"] += texture_index_offset
if "occlusionTexture" in mat:
if "index" in mat["occlusionTexture"]:
mat["occlusionTexture"]["index"] += texture_index_offset
if "emissiveTexture" in mat:
if "index" in mat["emissiveTexture"]:
mat["emissiveTexture"]["index"] += texture_index_offset
if "pbrMetallicRoughness" in mat:
if "baseColorTexture" in mat["pbrMetallicRoughness"]:
if "index" in mat["pbrMetallicRoughness"]["baseColorTexture"]:
mat["pbrMetallicRoughness"]["baseColorTexture"]["index"] += texture_index_offset
if "metallicRoughnessTexture" in mat["pbrMetallicRoughness"]:
if "index" in mat["pbrMetallicRoughness"]["metallicRoughnessTexture"]:
mat["pbrMetallicRoughness"]["metallicRoughnessTexture"]["index"] += texture_index_offset
return mat
def concat(source, target, offsets, prefix, extraInfo):
# Args:
# source: dict(modified), target: dict(modified),
# offsets: dict(modified). contains offset table.
# prefix: str
# extraInfo: dict(inout)
num_source_meshes = len(source["meshes"])
num_source_buffers = len(source["buffers"])
num_source_bufferViews = len(source["bufferViews"])
num_source_accessors = len(source["accessors"])
num_source_materials = len(source["materials"]) if "materials" in source else 0
num_source_samplers = len(source["samplers"]) if "samplers" in source else 0
num_source_images = len(source["images"]) if "images" in source else 0
num_source_textures = len(source["textures"]) if "textures" in source else 0
print("src =====")
print("num_src_meshes: ", num_source_meshes)
print("num_src_buffers: ", num_source_buffers)
print("num_src_bufferViews: ", num_source_bufferViews)
print("num_src_accessors: ", num_source_accessors)
print("num_src_materials: ", num_source_materials)
print("num_src_textures: ", num_source_textures)
print("num_src_images: ", num_source_images)
print("num_src_samplers: ", num_source_samplers)
#
# Modify name and adjust index offset
#
for i in range(len(source["buffers"])):
if "name" in source["buffers"][i]:
source["buffers"][i]["name"] = prefix + source["buffers"][i]["name"]
for i in range(len(source["bufferViews"])):
if "name" in source["bufferViews"][i]:
source["bufferViews"][i]["name"] = prefix + source["bufferViews"][i]["name"]
source["bufferViews"][i]["buffer"] += offsets["buffers"]
for i in range(len(source["accessors"])):
if "name" in source["accessors"][i]:
source["accessors"][i]["name"] = prefix + source["accessors"][i]["name"]
source["accessors"][i]["bufferView"] += offsets["bufferViews"]
for i in range(len(source["meshes"])):
mesh = source["meshes"][i]
if "name" in mesh:
source["meshes"][i]["name"] = prefix + source["meshes"][i]["name"]
for primid in range(len(mesh["primitives"])):
for attrib in mesh["primitives"][primid]["attributes"]:
#print(source["meshes"][i]["primitives"][primid]["attributes"][attrib])
source["meshes"][i]["primitives"][primid]["attributes"][attrib] += offsets["accessors"]
source["meshes"][i]["primitives"][primid]["indices"] += offsets["accessors"]
if "material" in source["meshes"][i]["primitives"][primid]:
source["meshes"][i]["primitives"][primid]["material"] += offsets["materials"]
if "materials" in source:
for i in range(len(source["materials"])):
if "name" in source["materials"][i]:
source["materials"][i]["name"] = prefix + source["materials"][i]["name"]
source["materials"][i] = adjustMaterialTextureIndex(source["materials"][i], offsets["textures"])
if "images" in source:
for i in range(len(source["images"])):
if "name" in source["images"][i]:
source["images"][i]["name"] = prefix + source["images"][i]["name"]
if "samplers" in source:
for i in range(len(source["samplers"])):
if "name" in source["samplers"][i]:
source["samplers"][i]["name"] = prefix + source["samplers"][i]["name"]
if "textures" in source:
for i in range(len(source["textures"])):
if "name" in source["textures"][i]:
source["textures"][i]["name"] = prefix + source["textures"][i]["name"]
source["textures"][i]["sampler"] += offsets["samplers"]
source["textures"][i]["source"] += offsets["images"]
#
# Append mesh info
#
target["buffers"] += source["buffers"]
target["bufferViews"] += source["bufferViews"]
target["meshes"] += source["meshes"]
target["accessors"] += source["accessors"]
if "materials" in source:
if "materials" in target:
target["materials"] += source["materials"]
else:
target["materials"] = source["materials"]
if "images" in source:
if "images" in target:
target["images"] += source["images"]
else:
target["images"] = source["images"]
if "textures" in source:
if "textures" in target:
target["textures"] += source["textures"]
else:
target["textures"] = source["textures"]
if "samplers" in source:
if "samplers" in target:
target["samplers"] += source["samplers"]
else:
target["samplers"] = source["samplers"]
# assume `prefix` is unique
extraInfo["num_{}_meshes".format(prefix)] = num_source_meshes
extraInfo["num_{}_buffers".format(prefix)] = num_source_buffers
extraInfo["num_{}_bufferViews".format(prefix)] = num_source_bufferViews
extraInfo["num_{}_accessors".format(prefix)] = num_source_accessors
extraInfo["num_{}_materials".format(prefix)] = num_source_materials
extraInfo["num_{}_images".format(prefix)] = num_source_images
extraInfo["num_{}_samplers".format(prefix)] = num_source_samplers
extraInfo["num_{}_textures".format(prefix)] = num_source_textures
# update offsets
offsets["meshes"] += num_source_meshes
offsets["buffers"] += num_source_buffers
offsets["bufferViews"] += num_source_bufferViews
offsets["accessors"] += num_source_accessors
offsets["materials"] += num_source_materials
offsets["textures"] += num_source_textures
offsets["images"] += num_source_images
offsets["samplers"] += num_source_samplers
def main():
if len(sys.argv) < 4:
print("Needs source0.gltf <source1.gltf, ...> target.gltf output.gltf")
sys.exit(-1)
num_args = len(sys.argv)
print("num_args = ", num_args)
source_filenames = []
num_srcs = num_args - 3
for i in range(num_srcs):
source_filenames.append(sys.argv[1+i])
print("source[{}] = {}".format(i, sys.argv[1+i]))
target_filename = sys.argv[num_args - 2]
output_filename = sys.argv[num_args - 1]
print("target = ", target_filename)
print("output = ", output_filename)
sources = []
for i in range(num_srcs):
sources.append(json.loads(open(source_filenames[i]).read()))
target = json.loads(open(target_filename).read())
num_target_meshes = len(target["meshes"])
num_target_buffers = len(target["buffers"])
num_target_bufferViews = len(target["bufferViews"])
num_target_accessors = len(target["accessors"])
num_target_materials = 0
if "materials" in target:
num_target_materials = len(target["materials"])
num_target_images = 0
if "images" in target:
num_target_images = len(target["images"])
num_target_samplers = 0
if "samplers" in target:
num_target_samplers = len(target["samplers"])
num_target_textures = 0
if "textures" in target:
num_target_textures = len(target["textures"])
print("num_target_meshes: ", num_target_meshes)
print("num_target_buffers: ", num_target_buffers)
print("num_target_bufferViews: ", num_target_bufferViews)
print("num_target_accessors: ", num_target_accessors)
print("num_target_materials: ", num_target_materials)
print("num_target_textures: ", num_target_textures)
print("num_target_images: ", num_target_images)
print("num_target_samplers: ", num_target_samplers)
#
# add some info to asset.extras
#
extraInfo = {}
extraInfo["num_target_meshes"] = num_target_meshes
extraInfo["num_target_buffers"] = num_target_buffers
extraInfo["num_target_bufferViews"] = num_target_bufferViews
extraInfo["num_target_accessors"] = num_target_accessors
extraInfo["num_target_materials"] = num_target_materials
extraInfo["num_target_textures"] = num_target_textures
extraInfo["num_target_images"] = num_target_images
extraInfo["num_target_samplers"] = num_target_samplers
offsets = {}
offsets["meshes"] = num_target_meshes
offsets["buffers"] = num_target_buffers
offsets["bufferViews"] = num_target_bufferViews
offsets["accessors"] = num_target_accessors
offsets["materials"] = num_target_materials
offsets["textures"] = num_target_textures
offsets["samplers"] = num_target_samplers
offsets["images"] = num_target_images
for i in range(num_srcs):
source = sources[i]
prefix = g_prefix + "{}/".format(source_filenames[i])
concat(source, target, offsets, prefix, extraInfo)
extraInfo["num_total_meshes"] = offsets["meshes"]
extraInfo["num_total_buffers"] = offsets["buffers"]
extraInfo["num_total_bufferViews"] = offsets["bufferViews"]
extraInfo["num_total_accessors"] = offsets["accessors"]
extraInfo["num_total_materials"] = offsets["materials"]
extraInfo["num_total_textures"] = offsets["textures"]
extraInfo["num_total_images"] = offsets["images"]
extraInfo["num_total_samplers"] = offsets["samplers"]
target["asset"]["extras"] = extraInfo
with open(output_filename, "w") as f:
f.write(json.dumps(target, indent=2))
print("Merged glTF was exported to : ", output_filename)
main()

View File

@@ -0,0 +1,107 @@
# Replace accessor id of attributes for speicified mesh
# Usually called after concat_mesh.py
# Example usecase is to replace UV coordinate of a mesh.
import json
import sys, os
replace_indices = False
attrib_names = ["TEXCOORD_0"]
def check_accessor(src, target):
if src["componentType"] != target["componentType"]:
print("componentType mismatch!")
return False
if src["count"] != target["count"]:
print("`count` mismatch!")
return False
if src["type"] != target["type"]:
print("`type` mismatch!")
return False
return True
def main():
if len(sys.argv) < 5:
print("Needs input.gltf output.gltf source_mesh_name target_mesh_name <source_primid> <target_primid>")
sys.exit(-1)
input_filename = sys.argv[1]
output_filename = sys.argv[2]
source_mesh_name = sys.argv[3]
target_mesh_name = sys.argv[4]
source_primid = 0
target_primid = 0
if len(sys.argv) > 5:
source_primid = int(sys.argv[5])
if len(sys.argv) > 6:
target_primid = int(sys.argv[6])
gltf = json.loads(open(input_filename).read())
source_mesh_id = -1
target_mesh_id = -1
for i in range(len(gltf["meshes"])):
mesh = gltf["meshes"][i]
print("mesh[{}].name = {}".format(i, mesh["name"]))
if target_mesh_name == mesh["name"]:
target_mesh_id = i
if source_mesh_name == mesh["name"]:
source_mesh_id = i
if source_mesh_id == -1:
print("source mesh with name [{}] not found.".format(source_mesh_name))
sys.exit(-1)
if target_mesh_id == -1:
print("target mesh with name [{}] not found.".format(target_mesh_name))
sys.exit(-1)
print("target: name = {}, id = {}".format(target_mesh_name, target_mesh_id))
print("source: name = {}, id = {}".format(source_mesh_name, source_mesh_id))
source_mesh = gltf["meshes"][source_mesh_id]
target_mesh = gltf["meshes"][target_mesh_id]
source_prim = source_mesh["primitives"][source_primid]
target_prim = target_mesh["primitives"][target_primid]
if replace_indices:
target_indices_id = target_prim["indices"]
src_indices_id = source_prim["indices"]
print("Replace vertex indices from {} to {}".format(target_indices_id, src_indices_id))
gltf["meshes"][target_mesh_id]["primitives"][target_primid]["indices"] = gltf["meshes"][source_mesh_id]["primitives"][source_primid]["indices"]
for attrib in target_prim["attributes"]:
print("attrib ", attrib)
if attrib in attrib_names:
if attrib in source_prim["attributes"]:
target_accessor_id = target_prim["attributes"][attrib]
src_accessor_id = source_prim["attributes"][attrib]
if check_accessor(gltf["accessors"][src_accessor_id], gltf["accessors"][target_accessor_id]):
gltf["meshes"][target_mesh_id]["primitives"][target_primid]["attributes"][attrib] = src_accessor_id
print("Replaced accessor id for attrib {} from {} to {}".format(attrib, target_accessor_id, src_accessor_id))
else:
print("Accessor type/format is not identical. Skip replace")
print(" attrib {}".format(attrib))
else:
print("attribute[{}] not found in source primitive: mesh[{}].primitives[{}]".format(attrib, source_mesh_name, source_primid))
with open(output_filename, "w") as f:
f.write(json.dumps(gltf, indent=2))
print("Merged glTF was exported to : ", output_filename)
main()

View File

@@ -0,0 +1,9 @@
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#ifdef _WIN32
#include "../../tiny_gltf.h"
#else
#include "tiny_gltf.h"
#endif

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
@@ -527,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)
@@ -1151,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;
@@ -2144,12 +2146,12 @@ std::string base64_decode(std::string const &encoded_string) {
namespace dlib {
#if 0
inline unsigned char to_hex( unsigned char x )
inline unsigned char to_hex( unsigned char x )
{
return x + (x > 9 ? ('A'-10) : '0');
}
const std::string urlencode( const std::string& s )
const std::string urlencode( const std::string& s )
{
std::ostringstream os;
@@ -2553,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;
@@ -3606,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);
@@ -4855,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;
@@ -5664,6 +5676,13 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
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;
}
}
}
}
@@ -6532,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));
}
@@ -6571,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];
@@ -7096,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()) {
@@ -7126,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()) {
@@ -7402,20 +7428,21 @@ bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream,
SerializeGltfModel(model, output);
// BUFFERS
std::vector<std::string> usedUris;
std::vector<unsigned char> binBuffer;
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);
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));
}
JsonPushBack(buffers, std::move(buffer));
JsonAddMember(output, "buffers", std::move(buffers));
}
JsonAddMember(output, "buffers", std::move(buffers));
// IMAGES
if (model->images.size()) {
@@ -7469,44 +7496,46 @@ bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename,
// BUFFERS
std::vector<std::string> usedUris;
std::vector<unsigned char> binBuffer;
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 {
std::string binSavePath;
std::string binUri;
if (!model->buffers[i].uri.empty() && !IsDataURI(model->buffers[i].uri)) {
binUri = model->buffers[i].uri;
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()) {