Compare commits

..

96 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
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
30f333c607 Fix travis script. 2019-10-31 02:17:22 +09:00
Syoyo Fujita
419162716e Introduce TINYGLTF_BUILD_EXAMPLES option to CMakeLists.txt. Fixes #218 2019-10-31 01:55:57 +09:00
Syoyo Fujita
9533c352a1 Add gcc-4.8 build on Travis. 2019-10-31 01:02:43 +09:00
Syoyo Fujita
9ff3d93084 Merge pull request #217 from syoyo/store-json-string-for-extras-and-extensions
Add feature to store original JSON string for extras and extensions
2019-10-30 19:08:08 +09:00
Syoyo Fujita
aea514a975 Merge pull request #216 from harokyang/feature/utf8_path
Feature/utf8 path
2019-10-30 17:40:22 +09:00
Harokyang
05a4456948 fix build error on C++11/C++14 2019-10-30 16:30:00 +08:00
Syoyo Fujita
6e08b17ce3 Add feature to store original JSON string for extras and extensions(for user-specific JSON parsing). # Fixes 215
Fix some glTF object(e.g. Skin) does not have extras and/or extensions property.
2019-10-30 17:25:38 +09:00
Harokyang
fb256609f2 support writing with utf-8 filepath 2019-10-30 16:14:10 +08:00
Harokyang
5cecef2b92 Treat all filepath string as utf-8 encoded
On Windows, convert utf-8 to wchar string before interact with filepath
2019-10-30 15:17:09 +08:00
Syoyo Fujita
1100f0f1eb Allow parsing integer number value as double(For example, glTF-Sample-Models/2.0/Cameras/glTF has zfar number property with integer value, which trigerred a parsing failure). 2019-10-30 15:18:55 +09:00
Syoyo Fujita
2b66c81f67 Merge pull request #212 from Selmar/move_operator
Move semantics
2019-10-30 15:11:26 +09:00
Selmar Kok
f29375f59b revert some local modifications, oops.... >_> 2019-10-29 16:25:37 +01:00
Selmar Kok
79e3df26a8 replace TINYGLTF_USE_NO_EXCEPT with GNU compiler version check 2019-10-29 16:22:07 +01:00
Selmar Kok
e3cf5edb94 Merge remote-tracking branch 'origin/Selmar-move_operator' into move_operator 2019-10-29 16:09:40 +01:00
Selmar Kok
b74fadef8e TINYGLTF_USE_NOEXCEPT 2019-10-29 16:09:32 +01:00
Syoyo Fujita
a11709a9ff Merge pull request #213 from dmuir/master
Validate model_length against length in LoadBinaryFromMemory.
2019-10-29 02:20:31 +09:00
Doug Muir
cf668683f9 Fix check for invalid glTF to consider invalid length/model_length combinations. 2019-10-28 09:51:13 -07:00
Syoyo Fujita
817bbcfb58 Show clang version in Travis build. 2019-10-23 00:41:40 +09:00
Selmar Kok
23467959ca pbrMetallicRoughnes default noexcept move operator 2019-10-21 19:23:15 +02:00
Selmar Kok
6f08785e9f Merge remote-tracking branch 'origin/master' into move_operator 2019-10-21 17:58:57 +02:00
Selmar Kok
ff2b1f92dd use default noexcept move constructor / operator 2019-10-21 17:58:09 +02:00
Syoyo Fujita
e886247329 Fix parsing a glTF file with 2GB+ or lareger size. 2019-10-20 17:47:50 +09:00
Syoyo Fujita
83bb1a48d7 Merge pull request #211 from Selmar/add_missing_serialization
small serialization additions
2019-10-19 15:18:19 +09:00
Selmar Kok
c3353e1acd set default values for baseColorFactor so that it has correct values when parsing a material without a pbrMetallicRoughness struct 2019-10-18 18:22:35 +02:00
Selmar Kok
81b672bcc2 add some missing serialization // add mesh==() weights comparison // use const iterator for extension serialization 2019-10-18 16:08:44 +02:00
Syoyo Fujita
cece6ee6d2 Merge pull request #209 from patriciogonzalezvivo/master
Adding glslViewer
2019-10-08 19:21:51 +09:00
Patricio Gonzalez Vivo
31f875ddbc adding glslViewer 2019-10-08 03:07:58 -07:00
Syoyo Fujita
8dccf9bf4e Merge pull request #208 from toshiks/patch-1
Added project to Readme.md (QuickLook GLTF plugin)
2019-09-15 14:50:06 +09:00
Anton Klochkov
508dcfa2e6 Added project to Readme.md (QuickLook GLTF plugin) 2019-09-15 01:25:47 +03:00
Syoyo Fujita
7e009041e3 Do not serialize pbrMetallicRoughness when they have all default values. Fixes #204 2019-09-13 15:32:22 +09:00
Syoyo Fujita
14977937c2 Add a link to Lighthouse 2. 2019-09-09 20:07:54 +09:00
Syoyo Fujita
8bb18fbd9c Merge pull request #202 from Ybalrid/fix_copy_assignment_operator
Fix broken copy-assignment operators
2019-09-06 13:58:54 +09:00
Arthur Brainville (Ybalrid)
9eeaf20133 Fix copy-assignment operator
When manual move constructor are declared, C++ compilers deletes the
copy constructor and copy-assign operator.

Most of these structures recently got a move ctor added, and copy ctor
re-enabled ( = default ). But copy-assignment are missing, thus in some
context, breaking instantiations of templates like std::vector<>.
2019-09-05 13:02:05 +02:00
30 changed files with 13675 additions and 934 deletions

View File

@@ -31,6 +31,17 @@ matrix:
- addons: *1
compiler: clang
env: COMPILER_VERSION=3.9 BUILD_TYPE=Debug CFLAGS="-O0" CXXFLAGS="-O0"
- addons: &3
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
compiler: gcc
env: COMPILER_VERSION=4.8 BUILD_TYPE=Debug
- addons: *3
compiler: gcc
env: COMPILER_VERSION=4.8 BUILD_TYPE=Release
before_install:
- ./.travis-before-install.sh
@@ -43,6 +54,7 @@ script:
- ${CXX} ${EXTRA_CXXFLAGS} -std=c++11 -Wall -g -o loader_example loader_example.cc
- ./loader_example ./models/Cube/Cube.gltf
- cd tests
- clang++ -v
- make
- ./tester
- ./tester_noexcept

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
@@ -67,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
@@ -75,6 +80,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
* [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
@@ -156,14 +165,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.
@@ -191,9 +203,21 @@ $ ./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
### 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;
}

View File

@@ -322,6 +322,15 @@ static void DumpStringIntMap(const std::map<std::string, int> &m, int indent) {
}
}
static void DumpExtensions(const tinygltf::ExtensionMap &extension,
const int indent) {
// TODO(syoyo): pritty print Value
for (auto &e : extension) {
std::cout << Indent(indent) << e.first << std::endl;
std::cout << PrintValue("extensions", e.second, indent + 1) << std::endl;
}
}
static void DumpPrimitive(const tinygltf::Primitive &primitive, int indent) {
std::cout << Indent(indent) << "material : " << primitive.material
<< std::endl;
@@ -333,19 +342,23 @@ static void DumpPrimitive(const tinygltf::Primitive &primitive, int indent) {
<< std::endl;
DumpStringIntMap(primitive.attributes, indent + 1);
DumpExtensions(primitive.extensions, indent);
std::cout << Indent(indent) << "extras :" << std::endl
<< PrintValue("extras", primitive.extras, indent + 1) << std::endl;
}
static void DumpExtensions(const tinygltf::ExtensionMap &extension,
const int indent) {
// TODO(syoyo): pritty print Value
for (auto &e : extension) {
std::cout << Indent(indent) << e.first << std::endl;
std::cout << PrintValue("extensions", e.second, indent + 1) << std::endl;
if (!primitive.extensions_json_string.empty()) {
std::cout << Indent(indent + 1) << "extensions(JSON string) = "
<< primitive.extensions_json_string << "\n";
}
if (!primitive.extras_json_string.empty()) {
std::cout << Indent(indent + 1)
<< "extras(JSON string) = " << primitive.extras_json_string
<< "\n";
}
}
static void DumpTextureInfo(const tinygltf::TextureInfo &texinfo,
const int indent) {
std::cout << Indent(indent) << "index : " << texinfo.index << "\n";
@@ -353,6 +366,17 @@ static void DumpTextureInfo(const tinygltf::TextureInfo &texinfo,
<< "\n";
DumpExtensions(texinfo.extensions, indent + 1);
std::cout << PrintValue("extras", texinfo.extras, indent + 1) << "\n";
if (!texinfo.extensions_json_string.empty()) {
std::cout << Indent(indent)
<< "extensions(JSON string) = " << texinfo.extensions_json_string
<< "\n";
}
if (!texinfo.extras_json_string.empty()) {
std::cout << Indent(indent)
<< "extras(JSON string) = " << texinfo.extras_json_string << "\n";
}
}
static void DumpNormalTextureInfo(const tinygltf::NormalTextureInfo &texinfo,
@@ -546,6 +570,21 @@ static void Dump(const tinygltf::Model &model) {
std::cout << Indent(2)
<< "target : " << PrintTarget(bufferView.target)
<< std::endl;
std::cout << Indent(1) << "-------------------------------------\n";
DumpExtensions(bufferView.extensions, 1);
std::cout << PrintValue("extras", bufferView.extras, 2) << std::endl;
if (!bufferView.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< bufferView.extensions_json_string << "\n";
}
if (!bufferView.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << bufferView.extras_json_string
<< "\n";
}
}
}
@@ -556,6 +595,21 @@ static void Dump(const tinygltf::Model &model) {
std::cout << Indent(1) << "name : " << buffer.name << std::endl;
std::cout << Indent(2) << "byteLength : " << buffer.data.size()
<< std::endl;
std::cout << Indent(1) << "-------------------------------------\n";
DumpExtensions(buffer.extensions, 1);
std::cout << PrintValue("extras", buffer.extras, 2) << std::endl;
if (!buffer.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< buffer.extensions_json_string << "\n";
}
if (!buffer.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << buffer.extras_json_string
<< "\n";
}
}
}
@@ -602,6 +656,17 @@ static void Dump(const tinygltf::Model &model) {
DumpExtensions(material.extensions, 1);
std::cout << PrintValue("extras", material.extras, 2) << std::endl;
if (!material.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< material.extensions_json_string << "\n";
}
if (!material.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << material.extras_json_string
<< "\n";
}
}
}
@@ -625,6 +690,18 @@ static void Dump(const tinygltf::Model &model) {
std::cout << Indent(2) << "height : " << image.height << std::endl;
std::cout << Indent(2) << "component : " << image.component << std::endl;
DumpExtensions(image.extensions, 1);
std::cout << PrintValue("extras", image.extras, 2) << std::endl;
if (!image.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< image.extensions_json_string << "\n";
}
if (!image.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << image.extras_json_string
<< "\n";
}
}
}
@@ -637,6 +714,18 @@ static void Dump(const tinygltf::Model &model) {
std::cout << Indent(1) << "source : " << texture.source
<< std::endl;
DumpExtensions(texture.extensions, 1);
std::cout << PrintValue("extras", texture.extras, 2) << std::endl;
if (!texture.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< texture.extensions_json_string << "\n";
}
if (!texture.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << texture.extras_json_string
<< "\n";
}
}
}
@@ -658,6 +747,20 @@ static void Dump(const tinygltf::Model &model) {
std::cout << Indent(2)
<< "wrapT : " << PrintWrapMode(sampler.wrapT)
<< std::endl;
DumpExtensions(sampler.extensions, 1);
std::cout << PrintValue("extras", sampler.extras, 2) << std::endl;
if (!sampler.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< sampler.extensions_json_string << "\n";
}
if (!sampler.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << sampler.extras_json_string
<< "\n";
}
}
}
@@ -690,6 +793,54 @@ static void Dump(const tinygltf::Model &model) {
<< "znear : " << camera.orthographic.znear
<< std::endl;
}
std::cout << Indent(1) << "-------------------------------------\n";
DumpExtensions(camera.extensions, 1);
std::cout << PrintValue("extras", camera.extras, 2) << std::endl;
if (!camera.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< camera.extensions_json_string << "\n";
}
if (!camera.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << camera.extras_json_string
<< "\n";
}
}
}
{
std::cout << "skins(items=" << model.skins.size() << ")" << std::endl;
for (size_t i = 0; i < model.skins.size(); i++) {
const tinygltf::Skin &skin = model.skins[i];
std::cout << Indent(1) << "name : " << skin.name << std::endl;
std::cout << Indent(2)
<< "inverseBindMatrices : " << skin.inverseBindMatrices
<< std::endl;
std::cout << Indent(2) << "skeleton : " << skin.skeleton
<< std::endl;
std::cout << Indent(2)
<< "joints : " << PrintIntArray(skin.joints)
<< std::endl;
std::cout << Indent(1) << "-------------------------------------\n";
DumpExtensions(skin.extensions, 1);
std::cout << PrintValue("extras", skin.extras, 2) << std::endl;
if (!skin.extensions_json_string.empty()) {
std::cout << Indent(2)
<< "extensions(JSON string) = " << skin.extensions_json_string
<< "\n";
}
if (!skin.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << skin.extras_json_string
<< "\n";
}
}
}
@@ -707,6 +858,12 @@ int main(int argc, char **argv) {
exit(1);
}
// Store original JSON string for `extras` and `extensions`
bool store_original_json_for_extras_and_extensions = false;
if (argc > 2) {
store_original_json_for_extras_and_extensions = true;
}
tinygltf::Model model;
tinygltf::TinyGLTF gltf_ctx;
std::string err;
@@ -714,6 +871,9 @@ int main(int argc, char **argv) {
std::string input_filename(argv[1]);
std::string ext = GetFilePathExtension(input_filename);
gltf_ctx.SetStoreOriginalJSONForExtrasAndExtensions(
store_original_json_for_extras_and_extensions);
bool ret = false;
if (ext.compare("glb") == 0) {
std::cout << "Reading binary glTF" << 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);
}

File diff suppressed because it is too large Load Diff