tiny_gltf 1.0.1 → 2.5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // The MIT License (MIT)
6
6
  //
7
- // Copyright (c) 2015 - 2019 Syoyo Fujita, Aurélien Chatelain and many
7
+ // Copyright (c) 2015 - Present Syoyo Fujita, Aurélien Chatelain and many
8
8
  // contributors.
9
9
  //
10
10
  // Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -26,6 +26,10 @@
26
26
  // THE SOFTWARE.
27
27
 
28
28
  // Version:
29
+ // - v2.5.0 Add SetPreserveImageChannels() option to load image data as is.
30
+ // - v2.4.3 Fix null object output when when material has all default
31
+ // parameters.
32
+ // - v2.4.2 Decode percent-encoded URI.
29
33
  // - v2.4.1 Fix some glTF object class does not have `extensions` and/or
30
34
  // `extras` property.
31
35
  // - v2.4.0 Experimental RapidJSON and C++14 support(Thanks to @jrkoone).
@@ -51,6 +55,7 @@
51
55
 
52
56
  #include <array>
53
57
  #include <cassert>
58
+ #include <cmath> // std::fabs
54
59
  #include <cstdint>
55
60
  #include <cstdlib>
56
61
  #include <cstring>
@@ -103,7 +108,7 @@ namespace tinygltf {
103
108
  #define TINYGLTF_COMPONENT_TYPE_INT (5124)
104
109
  #define TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT (5125)
105
110
  #define TINYGLTF_COMPONENT_TYPE_FLOAT (5126)
106
- #define TINYGLTF_COMPONENT_TYPE_DOUBLE (5130)
111
+ #define TINYGLTF_COMPONENT_TYPE_DOUBLE (5130) // OpenGL double type. Note that some of glTF 2.0 validator does not support double type even the schema seems allow any value of integer: https://github.com/KhronosGroup/glTF/blob/b9884a2fd45130b4d673dd6c8a706ee21ee5c5f7/specification/2.0/schema/accessor.schema.json#L22
107
112
 
108
113
  #define TINYGLTF_TEXTURE_FILTER_NEAREST (9728)
109
114
  #define TINYGLTF_TEXTURE_FILTER_LINEAR (9729)
@@ -186,14 +191,14 @@ AAssetManager *asset_manager = nullptr;
186
191
  #endif
187
192
 
188
193
  typedef enum {
189
- NULL_TYPE = 0,
190
- REAL_TYPE = 1,
191
- INT_TYPE = 2,
192
- BOOL_TYPE = 3,
193
- STRING_TYPE = 4,
194
- ARRAY_TYPE = 5,
195
- BINARY_TYPE = 6,
196
- OBJECT_TYPE = 7
194
+ NULL_TYPE,
195
+ REAL_TYPE,
196
+ INT_TYPE,
197
+ BOOL_TYPE,
198
+ STRING_TYPE,
199
+ ARRAY_TYPE,
200
+ BINARY_TYPE,
201
+ OBJECT_TYPE
197
202
  } Type;
198
203
 
199
204
  static inline int32_t GetComponentSizeInBytes(uint32_t componentType) {
@@ -248,7 +253,6 @@ bool DecodeDataURI(std::vector<unsigned char> *out, std::string &mime_type,
248
253
  #ifdef __clang__
249
254
  #pragma clang diagnostic push
250
255
  // Suppress warning for : static Value null_value
251
- // https://stackoverflow.com/questions/15708411/how-to-deal-with-global-constructor-warning-in-clang
252
256
  #pragma clang diagnostic ignored "-Wexit-time-destructors"
253
257
  #pragma clang diagnostic ignored "-Wpadded"
254
258
  #endif
@@ -293,7 +297,7 @@ class Value {
293
297
 
294
298
  DEFAULT_METHODS(Value)
295
299
 
296
- char Type() const { return static_cast<const char>(type_); }
300
+ char Type() const { return static_cast<char>(type_); }
297
301
 
298
302
  bool IsBool() const { return (type_ == BOOL_TYPE); }
299
303
 
@@ -321,7 +325,8 @@ class Value {
321
325
  }
322
326
 
323
327
  // Use this function if you want to have number value as int.
324
- double GetNumberAsInt() const {
328
+ // TODO(syoyo): Support int value larger than 32 bits
329
+ int GetNumberAsInt() const {
325
330
  if (type_ == REAL_TYPE) {
326
331
  return int(real_value_);
327
332
  } else {
@@ -526,10 +531,12 @@ struct AnimationChannel {
526
531
  // "weights"]
527
532
  Value extras;
528
533
  ExtensionMap extensions;
534
+ ExtensionMap target_extensions;
529
535
 
530
536
  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
531
537
  std::string extras_json_string;
532
538
  std::string extensions_json_string;
539
+ std::string target_extensions_json_string;
533
540
 
534
541
  AnimationChannel() : sampler(-1), target_node(-1) {}
535
542
  DEFAULT_METHODS(AnimationChannel)
@@ -596,7 +603,7 @@ struct Sampler {
596
603
  // `magFilter`. Set -1 in TinyGLTF(issue #186)
597
604
  int minFilter =
598
605
  -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR",
599
- // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_NEAREST",
606
+ // "NEAREST_MIPMAP_NEAREST", "LINEAR_MIPMAP_NEAREST",
600
607
  // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_LINEAR"]
601
608
  int magFilter =
602
609
  -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR"]
@@ -606,7 +613,7 @@ struct Sampler {
606
613
  int wrapT =
607
614
  TINYGLTF_TEXTURE_WRAP_REPEAT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT",
608
615
  // "REPEAT"], default "REPEAT"
609
- int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT; // TinyGLTF extension
616
+ //int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT; // TinyGLTF extension. currently not used.
610
617
 
611
618
  Value extras;
612
619
  ExtensionMap extensions;
@@ -619,8 +626,7 @@ struct Sampler {
619
626
  : minFilter(-1),
620
627
  magFilter(-1),
621
628
  wrapS(TINYGLTF_TEXTURE_WRAP_REPEAT),
622
- wrapT(TINYGLTF_TEXTURE_WRAP_REPEAT),
623
- wrapR(TINYGLTF_TEXTURE_WRAP_REPEAT) {}
629
+ wrapT(TINYGLTF_TEXTURE_WRAP_REPEAT) {}
624
630
  DEFAULT_METHODS(Sampler)
625
631
  bool operator==(const Sampler &) const;
626
632
  };
@@ -637,7 +643,8 @@ struct Image {
637
643
  int bufferView; // (required if no uri)
638
644
  std::string mimeType; // (required if no uri) ["image/jpeg", "image/png",
639
645
  // "image/bmp", "image/gif"]
640
- std::string uri; // (required if no mimeType)
646
+ std::string uri; // (required if no mimeType) uri is not decoded(e.g.
647
+ // whitespace may be represented as %20)
641
648
  Value extras;
642
649
  ExtensionMap extensions;
643
650
 
@@ -658,6 +665,8 @@ struct Image {
658
665
  width = -1;
659
666
  height = -1;
660
667
  component = -1;
668
+ bits = -1;
669
+ pixel_type = -1;
661
670
  }
662
671
  DEFAULT_METHODS(Image)
663
672
 
@@ -797,12 +806,13 @@ struct Material {
797
806
 
798
807
  struct BufferView {
799
808
  std::string name;
800
- int buffer; // Required
801
- size_t byteOffset; // minimum 0, default 0
802
- size_t byteLength; // required, minimum 1
803
- size_t byteStride; // minimum 4, maximum 252 (multiple of 4), default 0 =
804
- // understood to be tightly packed
805
- int target; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"]
809
+ int buffer{-1}; // Required
810
+ size_t byteOffset{0}; // minimum 0, default 0
811
+ size_t byteLength{0}; // required, minimum 1. 0 = invalid
812
+ size_t byteStride{0}; // minimum 4, maximum 252 (multiple of 4), default 0 =
813
+ // understood to be tightly packed
814
+ int target{0}; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] for vertex indices
815
+ // or atttribs. Could be 0 for other data
806
816
  Value extras;
807
817
  ExtensionMap extensions;
808
818
 
@@ -810,9 +820,15 @@ struct BufferView {
810
820
  std::string extras_json_string;
811
821
  std::string extensions_json_string;
812
822
 
813
- bool dracoDecoded; // Flag indicating this has been draco decoded
823
+ bool dracoDecoded{false}; // Flag indicating this has been draco decoded
814
824
 
815
- BufferView() : byteOffset(0), byteStride(0), dracoDecoded(false) {}
825
+ BufferView()
826
+ : buffer(-1),
827
+ byteOffset(0),
828
+ byteLength(0),
829
+ byteStride(0),
830
+ target(0),
831
+ dracoDecoded(false) {}
816
832
  DEFAULT_METHODS(BufferView)
817
833
  bool operator==(const BufferView &) const;
818
834
  };
@@ -833,8 +849,10 @@ struct Accessor {
833
849
  std::string extras_json_string;
834
850
  std::string extensions_json_string;
835
851
 
836
- std::vector<double> minValues; // optional
837
- std::vector<double> maxValues; // optional
852
+ std::vector<double>
853
+ minValues; // optional. integer value is promoted to double
854
+ std::vector<double>
855
+ maxValues; // optional. integer value is promoted to double
838
856
 
839
857
  struct {
840
858
  int count;
@@ -887,13 +905,13 @@ struct Accessor {
887
905
  // unreachable return 0;
888
906
  }
889
907
 
890
- Accessor() :
891
- bufferView(-1),
892
- byteOffset(0),
893
- normalized(false),
894
- componentType(-1),
895
- count(0),
896
- type(-1){
908
+ Accessor()
909
+ : bufferView(-1),
910
+ byteOffset(0),
911
+ normalized(false),
912
+ componentType(-1),
913
+ count(0),
914
+ type(-1) {
897
915
  sparse.isSparse = false;
898
916
  }
899
917
  DEFAULT_METHODS(Accessor)
@@ -983,6 +1001,7 @@ struct Primitive {
983
1001
  Primitive() {
984
1002
  material = -1;
985
1003
  indices = -1;
1004
+ mode = -1;
986
1005
  }
987
1006
  DEFAULT_METHODS(Primitive)
988
1007
  bool operator==(const Primitive &) const;
@@ -1037,6 +1056,7 @@ struct Buffer {
1037
1056
  std::vector<unsigned char> data;
1038
1057
  std::string
1039
1058
  uri; // considered as required here but not in the spec (need to clarify)
1059
+ // uri is not decoded(e.g. whitespace may be represented as %20)
1040
1060
  Value extras;
1041
1061
  ExtensionMap extensions;
1042
1062
 
@@ -1050,7 +1070,7 @@ struct Buffer {
1050
1070
  };
1051
1071
 
1052
1072
  struct Asset {
1053
- std::string version; // required
1073
+ std::string version = "2.0"; // required
1054
1074
  std::string generator;
1055
1075
  std::string minVersion;
1056
1076
  std::string copyright;
@@ -1101,9 +1121,9 @@ struct SpotLight {
1101
1121
  struct Light {
1102
1122
  std::string name;
1103
1123
  std::vector<double> color;
1104
- double intensity;
1124
+ double intensity{1.0};
1105
1125
  std::string type;
1106
- double range;
1126
+ double range{0.0}; // 0.0 = inifinite
1107
1127
  SpotLight spot;
1108
1128
 
1109
1129
  Light() : intensity(1.0), range(0.0) {}
@@ -1141,7 +1161,7 @@ class Model {
1141
1161
  std::vector<Scene> scenes;
1142
1162
  std::vector<Light> lights;
1143
1163
 
1144
- int defaultScene;
1164
+ int defaultScene = -1;
1145
1165
  std::vector<std::string> extensionsUsed;
1146
1166
  std::vector<std::string> extensionsRequired;
1147
1167
 
@@ -1172,7 +1192,8 @@ enum SectionCheck {
1172
1192
  ///
1173
1193
  typedef bool (*LoadImageDataFunction)(Image *, const int, std::string *,
1174
1194
  std::string *, int, int,
1175
- const unsigned char *, int, void *);
1195
+ const unsigned char *, int,
1196
+ void *user_pointer);
1176
1197
 
1177
1198
  ///
1178
1199
  /// WriteImageDataFunction type. Signature for custom image writing callbacks.
@@ -1235,7 +1256,14 @@ struct FsCallbacks {
1235
1256
 
1236
1257
  bool FileExists(const std::string &abs_filename, void *);
1237
1258
 
1238
- std::string ExpandFilePath(const std::string &filepath, void *);
1259
+ ///
1260
+ /// Expand file path(e.g. `~` to home directory on posix, `%APPDATA%` to
1261
+ /// `C:\\Users\\tinygltf\\AppData`)
1262
+ ///
1263
+ /// @param[in] filepath File path string. Assume UTF-8
1264
+ /// @param[in] userdata User data. Set to `nullptr` if you don't need it.
1265
+ ///
1266
+ std::string ExpandFilePath(const std::string &filepath, void *userdata);
1239
1267
 
1240
1268
  bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
1241
1269
  const std::string &filepath, void *);
@@ -1321,6 +1349,11 @@ class TinyGLTF {
1321
1349
  ///
1322
1350
  void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data);
1323
1351
 
1352
+ ///
1353
+ /// Unset(remove) callback of loading image data
1354
+ ///
1355
+ void RemoveImageLoader();
1356
+
1324
1357
  ///
1325
1358
  /// Set callback to use for writing image data
1326
1359
  ///
@@ -1359,6 +1392,16 @@ class TinyGLTF {
1359
1392
  return store_original_json_for_extras_and_extensions_;
1360
1393
  }
1361
1394
 
1395
+ ///
1396
+ /// Specify whether preserve image channales when loading images or not.
1397
+ /// (Not effective when the user suppy their own LoadImageData callbacks)
1398
+ ///
1399
+ void SetPreserveImageChannels(bool onoff) {
1400
+ preserve_image_channels_ = onoff;
1401
+ }
1402
+
1403
+ bool GetPreserveImageChannels() const { return preserve_image_channels_; }
1404
+
1362
1405
  private:
1363
1406
  ///
1364
1407
  /// Loads glTF asset from string(memory).
@@ -1378,6 +1421,9 @@ class TinyGLTF {
1378
1421
 
1379
1422
  bool store_original_json_for_extras_and_extensions_ = false;
1380
1423
 
1424
+ bool preserve_image_channels_ = false; /// Default false(expand channels to
1425
+ /// RGBA) for backward compatibility.
1426
+
1381
1427
  FsCallbacks fs = {
1382
1428
  #ifndef TINYGLTF_NO_FS
1383
1429
  &tinygltf::FileExists, &tinygltf::ExpandFilePath,
@@ -1397,7 +1443,8 @@ class TinyGLTF {
1397
1443
  #else
1398
1444
  nullptr;
1399
1445
  #endif
1400
- void *load_image_user_data_ = reinterpret_cast<void *>(&fs);
1446
+ void *load_image_user_data_{nullptr};
1447
+ bool user_image_loader_{false};
1401
1448
 
1402
1449
  WriteImageDataFunction WriteImageData =
1403
1450
  #ifndef TINYGLTF_NO_STB_IMAGE_WRITE
@@ -1405,7 +1452,7 @@ class TinyGLTF {
1405
1452
  #else
1406
1453
  nullptr;
1407
1454
  #endif
1408
- void *write_image_user_data_ = reinterpret_cast<void *>(&fs);
1455
+ void *write_image_user_data_{nullptr};
1409
1456
  };
1410
1457
 
1411
1458
  #ifdef __clang__
@@ -1490,6 +1537,7 @@ class TinyGLTF {
1490
1537
  #ifndef TINYGLTF_USE_RAPIDJSON
1491
1538
  #include "json.hpp"
1492
1539
  #else
1540
+ #ifndef TINYGLTF_NO_INCLUDE_RAPIDJSON
1493
1541
  #include "document.h"
1494
1542
  #include "prettywriter.h"
1495
1543
  #include "rapidjson.h"
@@ -1497,6 +1545,7 @@ class TinyGLTF {
1497
1545
  #include "writer.h"
1498
1546
  #endif
1499
1547
  #endif
1548
+ #endif
1500
1549
 
1501
1550
  #ifdef TINYGLTF_ENABLE_DRACO
1502
1551
  #include "draco/compression/decode.h"
@@ -1547,14 +1596,15 @@ class TinyGLTF {
1547
1596
  #undef NOMINMAX
1548
1597
  #endif
1549
1598
 
1550
- #if defined(__GLIBCXX__) // mingw
1599
+ #if defined(__GLIBCXX__) // mingw
1551
1600
 
1552
- #include <ext/stdio_filebuf.h> // fstream (all sorts of IO stuff) + stdio_filebuf (=streambuf)
1553
1601
  #include <fcntl.h> // _O_RDONLY
1554
1602
 
1603
+ #include <ext/stdio_filebuf.h> // fstream (all sorts of IO stuff) + stdio_filebuf (=streambuf)
1604
+
1555
1605
  #endif
1556
1606
 
1557
- #elif !defined(__ANDROID__)
1607
+ #elif !defined(__ANDROID__) && !defined(__OpenBSD__)
1558
1608
  #include <wordexp.h>
1559
1609
  #endif
1560
1610
 
@@ -1658,6 +1708,19 @@ void JsonParse(JsonDocument &doc, const char *str, size_t length,
1658
1708
 
1659
1709
  namespace tinygltf {
1660
1710
 
1711
+ ///
1712
+ /// Internal LoadImageDataOption struct.
1713
+ /// This struct is passed through `user_pointer` in LoadImageData.
1714
+ /// The struct is not passed when the user supply their own LoadImageData
1715
+ /// callbacks.
1716
+ ///
1717
+ struct LoadImageDataOption {
1718
+ // true: preserve image channels(e.g. load as RGB image if the image has RGB
1719
+ // channels) default `false`(channels are expanded to RGBA for backward
1720
+ // compatiblity).
1721
+ bool preserve_channels{false};
1722
+ };
1723
+
1661
1724
  // Equals function for Value, for recursivity
1662
1725
  static bool Equals(const tinygltf::Value &one, const tinygltf::Value &other) {
1663
1726
  if (one.Type() != other.Type()) return false;
@@ -1870,8 +1933,10 @@ bool Sampler::operator==(const Sampler &other) const {
1870
1933
  return this->extensions == other.extensions && this->extras == other.extras &&
1871
1934
  this->magFilter == other.magFilter &&
1872
1935
  this->minFilter == other.minFilter && this->name == other.name &&
1873
- this->wrapR == other.wrapR && this->wrapS == other.wrapS &&
1936
+ this->wrapS == other.wrapS &&
1874
1937
  this->wrapT == other.wrapT;
1938
+
1939
+ //this->wrapR == other.wrapR
1875
1940
  }
1876
1941
  bool Scene::operator==(const Scene &other) const {
1877
1942
  return this->extensions == other.extensions && this->extras == other.extras &&
@@ -1975,9 +2040,11 @@ static std::string GetBaseDir(const std::string &filepath) {
1975
2040
  return "";
1976
2041
  }
1977
2042
 
1978
- // https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path
1979
2043
  static std::string GetBaseFilename(const std::string &filepath) {
1980
- return filepath.substr(filepath.find_last_of("/\\") + 1);
2044
+ auto idx = filepath.find_last_of("/\\");
2045
+ if (idx != std::string::npos)
2046
+ return filepath.substr(idx + 1);
2047
+ return filepath;
1981
2048
  }
1982
2049
 
1983
2050
  std::string base64_encode(unsigned char const *, unsigned int len);
@@ -2119,6 +2186,76 @@ std::string base64_decode(std::string const &encoded_string) {
2119
2186
  #pragma clang diagnostic pop
2120
2187
  #endif
2121
2188
 
2189
+ // https://github.com/syoyo/tinygltf/issues/228
2190
+ // TODO(syoyo): Use uriparser https://uriparser.github.io/ for stricter Uri
2191
+ // decoding?
2192
+ //
2193
+ // Uri Decoding from DLIB
2194
+ // http://dlib.net/dlib/server/server_http.cpp.html
2195
+ // --- dlib begin ------------------------------------------------------------
2196
+ // Copyright (C) 2003 Davis E. King (davis@dlib.net)
2197
+ // License: Boost Software License
2198
+ // Boost Software License - Version 1.0 - August 17th, 2003
2199
+
2200
+ // Permission is hereby granted, free of charge, to any person or organization
2201
+ // obtaining a copy of the software and accompanying documentation covered by
2202
+ // this license (the "Software") to use, reproduce, display, distribute,
2203
+ // execute, and transmit the Software, and to prepare derivative works of the
2204
+ // Software, and to permit third-parties to whom the Software is furnished to
2205
+ // do so, all subject to the following:
2206
+ // The copyright notices in the Software and this entire statement, including
2207
+ // the above license grant, this restriction and the following disclaimer,
2208
+ // must be included in all copies of the Software, in whole or in part, and
2209
+ // all derivative works of the Software, unless such copies or derivative
2210
+ // works are solely in the form of machine-executable object code generated by
2211
+ // a source language processor.
2212
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2213
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2214
+ // FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
2215
+ // SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
2216
+ // FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
2217
+ // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
2218
+ // DEALINGS IN THE SOFTWARE.
2219
+ //
2220
+ namespace dlib {
2221
+
2222
+ inline unsigned char from_hex(unsigned char ch) {
2223
+ if (ch <= '9' && ch >= '0')
2224
+ ch -= '0';
2225
+ else if (ch <= 'f' && ch >= 'a')
2226
+ ch -= 'a' - 10;
2227
+ else if (ch <= 'F' && ch >= 'A')
2228
+ ch -= 'A' - 10;
2229
+ else
2230
+ ch = 0;
2231
+ return ch;
2232
+ }
2233
+
2234
+ static const std::string urldecode(const std::string &str) {
2235
+ using namespace std;
2236
+ string result;
2237
+ string::size_type i;
2238
+ for (i = 0; i < str.size(); ++i) {
2239
+ if (str[i] == '+') {
2240
+ result += ' ';
2241
+ } else if (str[i] == '%' && str.size() > i + 2) {
2242
+ const unsigned char ch1 =
2243
+ from_hex(static_cast<unsigned char>(str[i + 1]));
2244
+ const unsigned char ch2 =
2245
+ from_hex(static_cast<unsigned char>(str[i + 2]));
2246
+ const unsigned char ch = static_cast<unsigned char>((ch1 << 4) | ch2);
2247
+ result += static_cast<char>(ch);
2248
+ i += 2;
2249
+ } else {
2250
+ result += str[i];
2251
+ }
2252
+ }
2253
+ return result;
2254
+ }
2255
+
2256
+ } // namespace dlib
2257
+ // --- dlib end --------------------------------------------------------------
2258
+
2122
2259
  static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
2123
2260
  std::string *warn, const std::string &filename,
2124
2261
  const std::string &basedir, bool required,
@@ -2190,22 +2327,40 @@ static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
2190
2327
  void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) {
2191
2328
  LoadImageData = func;
2192
2329
  load_image_user_data_ = user_data;
2330
+ user_image_loader_ = true;
2331
+ }
2332
+
2333
+ void TinyGLTF::RemoveImageLoader() {
2334
+ LoadImageData =
2335
+ #ifndef TINYGLTF_NO_STB_IMAGE
2336
+ &tinygltf::LoadImageData;
2337
+ #else
2338
+ nullptr;
2339
+ #endif
2340
+
2341
+ load_image_user_data_ = nullptr;
2342
+ user_image_loader_ = false;
2193
2343
  }
2194
2344
 
2195
2345
  #ifndef TINYGLTF_NO_STB_IMAGE
2196
2346
  bool LoadImageData(Image *image, const int image_idx, std::string *err,
2197
2347
  std::string *warn, int req_width, int req_height,
2198
2348
  const unsigned char *bytes, int size, void *user_data) {
2199
- (void)user_data;
2200
2349
  (void)warn;
2201
2350
 
2351
+ LoadImageDataOption option;
2352
+ if (user_data) {
2353
+ option = *reinterpret_cast<LoadImageDataOption *>(user_data);
2354
+ }
2355
+
2202
2356
  int w = 0, h = 0, comp = 0, req_comp = 0;
2203
2357
 
2204
2358
  unsigned char *data = nullptr;
2205
2359
 
2206
- // force 32-bit textures for common Vulkan compatibility. It appears that
2207
- // some GPU drivers do not support 24-bit images for Vulkan
2208
- req_comp = 4;
2360
+ // preserve_channels true: Use channels stored in the image file.
2361
+ // false: force 32-bit textures for common Vulkan compatibility. It appears
2362
+ // that some GPU drivers do not support 24-bit images for Vulkan
2363
+ req_comp = option.preserve_channels ? 0 : 4;
2209
2364
  int bits = 8;
2210
2365
  int pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
2211
2366
 
@@ -2277,13 +2432,18 @@ bool LoadImageData(Image *image, const int image_idx, std::string *err,
2277
2432
  }
2278
2433
  }
2279
2434
 
2435
+ if (req_comp != 0) {
2436
+ // loaded data has `req_comp` channels(components)
2437
+ comp = req_comp;
2438
+ }
2439
+
2280
2440
  image->width = w;
2281
2441
  image->height = h;
2282
- image->component = req_comp;
2442
+ image->component = comp;
2283
2443
  image->bits = bits;
2284
2444
  image->pixel_type = pixel_type;
2285
- image->image.resize(static_cast<size_t>(w * h * req_comp) * size_t(bits / 8));
2286
- std::copy(data, data + w * h * req_comp * (bits / 8), image->image.begin());
2445
+ image->image.resize(static_cast<size_t>(w * h * comp) * size_t(bits / 8));
2446
+ std::copy(data, data + w * h * comp * (bits / 8), image->image.begin());
2287
2447
  stbi_image_free(data);
2288
2448
 
2289
2449
  return true;
@@ -2379,11 +2539,22 @@ void TinyGLTF::SetFsCallbacks(FsCallbacks callbacks) { fs = callbacks; }
2379
2539
 
2380
2540
  #ifdef _WIN32
2381
2541
  static inline std::wstring UTF8ToWchar(const std::string &str) {
2382
- int wstr_size = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
2542
+ int wstr_size =
2543
+ MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
2383
2544
  std::wstring wstr(wstr_size, 0);
2384
- MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstr[0], (int)wstr.size());
2545
+ MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstr[0],
2546
+ (int)wstr.size());
2385
2547
  return wstr;
2386
2548
  }
2549
+
2550
+ static inline std::string WcharToUTF8(const std::wstring &wstr) {
2551
+ int str_size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(),
2552
+ nullptr, 0, NULL, NULL);
2553
+ std::string str(str_size, 0);
2554
+ WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), &str[0],
2555
+ (int)str.size(), NULL, NULL);
2556
+ return str;
2557
+ }
2387
2558
  #endif
2388
2559
 
2389
2560
  #ifndef TINYGLTF_NO_FS
@@ -2435,19 +2606,20 @@ bool FileExists(const std::string &abs_filename, void *) {
2435
2606
 
2436
2607
  std::string ExpandFilePath(const std::string &filepath, void *) {
2437
2608
  #ifdef _WIN32
2438
- DWORD len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0);
2439
- char *str = new char[len];
2440
- ExpandEnvironmentStringsA(filepath.c_str(), str, len);
2609
+ // Assume input `filepath` is encoded in UTF-8
2610
+ std::wstring wfilepath = UTF8ToWchar(filepath);
2611
+ DWORD wlen = ExpandEnvironmentStringsW(wfilepath.c_str(), nullptr, 0);
2612
+ wchar_t *wstr = new wchar_t[wlen];
2613
+ ExpandEnvironmentStringsW(wfilepath.c_str(), wstr, wlen);
2441
2614
 
2442
- std::string s(str);
2615
+ std::wstring ws(wstr);
2616
+ delete[] wstr;
2617
+ return WcharToUTF8(ws);
2443
2618
 
2444
- delete[] str;
2445
-
2446
- return s;
2447
2619
  #else
2448
2620
 
2449
2621
  #if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \
2450
- defined(__ANDROID__) || defined(__EMSCRIPTEN__)
2622
+ defined(__ANDROID__) || defined(__EMSCRIPTEN__) || defined(__OpenBSD__)
2451
2623
  // no expansion
2452
2624
  std::string s = filepath;
2453
2625
  #else
@@ -2458,8 +2630,10 @@ std::string ExpandFilePath(const std::string &filepath, void *) {
2458
2630
  return "";
2459
2631
  }
2460
2632
 
2633
+ // Quote the string to keep any spaces in filepath intact.
2634
+ std::string quoted_path = "\"" + filepath + "\"";
2461
2635
  // char** w;
2462
- int ret = wordexp(filepath.c_str(), &p, 0);
2636
+ int ret = wordexp(quoted_path.c_str(), &p, 0);
2463
2637
  if (ret) {
2464
2638
  // err
2465
2639
  s = filepath;
@@ -2493,11 +2667,12 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
2493
2667
  return false;
2494
2668
  }
2495
2669
  size_t size = AAsset_getLength(asset);
2496
- if (size <= 0) {
2670
+ if (size == 0) {
2497
2671
  if (err) {
2498
2672
  (*err) += "Invalid file size : " + filepath +
2499
2673
  " (does the path point to a directory?)";
2500
2674
  }
2675
+ return false;
2501
2676
  }
2502
2677
  out->resize(size);
2503
2678
  AAsset_read(asset, reinterpret_cast<char *>(&out->at(0)), size);
@@ -2511,13 +2686,17 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
2511
2686
  }
2512
2687
  #else
2513
2688
  #ifdef _WIN32
2514
- #if defined(__GLIBCXX__) // mingw
2515
- int file_descriptor = _wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY);
2689
+ #if defined(__GLIBCXX__) // mingw
2690
+ int file_descriptor =
2691
+ _wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY);
2516
2692
  __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
2517
2693
  std::istream f(&wfile_buf);
2518
- #elif defined(_MSC_VER)
2694
+ #elif defined(_MSC_VER) || defined(_LIBCPP_VERSION)
2695
+ // For libcxx, assume _LIBCPP_HAS_OPEN_WITH_WCHAR is defined to accept
2696
+ // `wchar_t *`
2519
2697
  std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary);
2520
- #else // clang?
2698
+ #else
2699
+ // Unknown compiler/runtime
2521
2700
  std::ifstream f(filepath.c_str(), std::ifstream::binary);
2522
2701
  #endif
2523
2702
  #else
@@ -2558,13 +2737,15 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
2558
2737
  bool WriteWholeFile(std::string *err, const std::string &filepath,
2559
2738
  const std::vector<unsigned char> &contents, void *) {
2560
2739
  #ifdef _WIN32
2561
- #if defined(__GLIBCXX__) // mingw
2562
- int file_descriptor = _wopen(UTF8ToWchar(filepath).c_str(), _O_WRONLY | _O_BINARY);
2563
- __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
2740
+ #if defined(__GLIBCXX__) // mingw
2741
+ int file_descriptor = _wopen(UTF8ToWchar(filepath).c_str(),
2742
+ _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY);
2743
+ __gnu_cxx::stdio_filebuf<char> wfile_buf(
2744
+ file_descriptor, std::ios_base::out | std::ios_base::binary);
2564
2745
  std::ostream f(&wfile_buf);
2565
2746
  #elif defined(_MSC_VER)
2566
2747
  std::ofstream f(UTF8ToWchar(filepath).c_str(), std::ofstream::binary);
2567
- #else // clang?
2748
+ #else // clang?
2568
2749
  std::ofstream f(filepath.c_str(), std::ofstream::binary);
2569
2750
  #endif
2570
2751
  #else
@@ -2611,12 +2792,13 @@ static void UpdateImageObject(Image &image, std::string &baseDir, int index,
2611
2792
  void *user_data = nullptr) {
2612
2793
  std::string filename;
2613
2794
  std::string ext;
2614
-
2615
- // If image have uri. Use it it as a filename
2795
+ // If image has uri, use it it as a filename
2616
2796
  if (image.uri.size()) {
2617
2797
  filename = GetBaseFilename(image.uri);
2618
2798
  ext = GetFilePathExtension(filename);
2619
-
2799
+ } else if (image.bufferView != -1) {
2800
+ // If there's no URI and the data exists in a buffer,
2801
+ // don't change properties or write images
2620
2802
  } else if (image.name.size()) {
2621
2803
  ext = MimeToExt(image.mimeType);
2622
2804
  // Otherwise use name as filename
@@ -2628,7 +2810,7 @@ static void UpdateImageObject(Image &image, std::string &baseDir, int index,
2628
2810
  }
2629
2811
 
2630
2812
  // If callback is set, modify image data object
2631
- if (*WriteImageData != nullptr) {
2813
+ if (*WriteImageData != nullptr && !filename.empty()) {
2632
2814
  std::string uri;
2633
2815
  (*WriteImageData)(&baseDir, &filename, &image, embedImages, user_data);
2634
2816
  }
@@ -2728,6 +2910,7 @@ bool DecodeDataURI(std::vector<unsigned char> *out, std::string &mime_type,
2728
2910
  }
2729
2911
  }
2730
2912
 
2913
+ // TODO(syoyo): Allow empty buffer? #229
2731
2914
  if (data.empty()) {
2732
2915
  return false;
2733
2916
  }
@@ -2872,7 +3055,9 @@ json_const_iterator ObjectEnd(const json &o) {
2872
3055
  #endif
2873
3056
  }
2874
3057
 
2875
- const char *GetKey(json_const_iterator &it) {
3058
+ // Making this a const char* results in a pointer to a temporary when
3059
+ // TINYGLTF_USE_RAPIDJSON is off.
3060
+ std::string GetKey(json_const_iterator &it) {
2876
3061
  #ifdef TINYGLTF_USE_RAPIDJSON
2877
3062
  return it->name.GetString();
2878
3063
  #else
@@ -3007,6 +3192,7 @@ static bool ParseJsonAsValue(Value *ret, const json &o) {
3007
3192
  break;
3008
3193
  case json::value_t::null:
3009
3194
  case json::value_t::discarded:
3195
+ case json::value_t::binary:
3010
3196
  // default:
3011
3197
  break;
3012
3198
  }
@@ -3508,6 +3694,7 @@ static bool ParseAsset(Asset *asset, std::string *err, const json &o,
3508
3694
  ParseStringProperty(&asset->version, err, o, "version", true, "Asset");
3509
3695
  ParseStringProperty(&asset->generator, err, o, "generator", false, "Asset");
3510
3696
  ParseStringProperty(&asset->minVersion, err, o, "minVersion", false, "Asset");
3697
+ ParseStringProperty(&asset->copyright, err, o, "copyright", false, "Asset");
3511
3698
 
3512
3699
  ParseExtensionsProperty(&asset->extensions, err, o);
3513
3700
 
@@ -3646,7 +3833,10 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err,
3646
3833
  #ifdef TINYGLTF_NO_EXTERNAL_IMAGE
3647
3834
  return true;
3648
3835
  #endif
3649
- if (!LoadExternalFile(&img, err, warn, uri, basedir, false, 0, false, fs)) {
3836
+ std::string decoded_uri = dlib::urldecode(uri);
3837
+ if (!LoadExternalFile(&img, err, warn, decoded_uri, basedir,
3838
+ /* required */ false, /* required bytes */ 0,
3839
+ /* checksize */ false, fs)) {
3650
3840
  if (warn) {
3651
3841
  (*warn) += "Failed to load external 'uri' for image[" +
3652
3842
  std::to_string(image_idx) + "] name = [" + image->name +
@@ -3868,9 +4058,10 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
3868
4058
  }
3869
4059
  } else {
3870
4060
  // External .bin file.
4061
+ std::string decoded_uri = dlib::urldecode(buffer->uri);
3871
4062
  if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr,
3872
- buffer->uri, basedir, true, byteLength, true,
3873
- fs)) {
4063
+ decoded_uri, basedir, /* required */ true,
4064
+ byteLength, /* checkSize */ true, fs)) {
3874
4065
  return false;
3875
4066
  }
3876
4067
  }
@@ -3912,8 +4103,10 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
3912
4103
  }
3913
4104
  } else {
3914
4105
  // Assume external .bin file.
3915
- if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, buffer->uri,
3916
- basedir, true, byteLength, true, fs)) {
4106
+ std::string decoded_uri = dlib::urldecode(buffer->uri);
4107
+ if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, decoded_uri,
4108
+ basedir, /* required */ true, byteLength,
4109
+ /* checkSize */ true, fs)) {
3917
4110
  return false;
3918
4111
  }
3919
4112
  }
@@ -4023,7 +4216,9 @@ static bool ParseSparseAccessor(Accessor *accessor, std::string *err,
4023
4216
  accessor->sparse.isSparse = true;
4024
4217
 
4025
4218
  int count = 0;
4026
- ParseIntegerProperty(&count, err, o, "count", true);
4219
+ if (!ParseIntegerProperty(&count, err, o, "count", true, "SparseAccessor")) {
4220
+ return false;
4221
+ }
4027
4222
 
4028
4223
  json_const_iterator indices_iterator;
4029
4224
  json_const_iterator values_iterator;
@@ -4041,18 +4236,24 @@ static bool ParseSparseAccessor(Accessor *accessor, std::string *err,
4041
4236
  const json &values_obj = GetValue(values_iterator);
4042
4237
 
4043
4238
  int indices_buffer_view = 0, indices_byte_offset = 0, component_type = 0;
4044
- ParseIntegerProperty(&indices_buffer_view, err, indices_obj, "bufferView",
4045
- true);
4239
+ if (!ParseIntegerProperty(&indices_buffer_view, err, indices_obj, "bufferView",
4240
+ true, "SparseAccessor")) {
4241
+ return false;
4242
+ }
4046
4243
  ParseIntegerProperty(&indices_byte_offset, err, indices_obj, "byteOffset",
4047
- true);
4048
- ParseIntegerProperty(&component_type, err, indices_obj, "componentType",
4049
- true);
4244
+ false);
4245
+ if (!ParseIntegerProperty(&component_type, err, indices_obj, "componentType",
4246
+ true, "SparseAccessor")) {
4247
+ return false;
4248
+ }
4050
4249
 
4051
4250
  int values_buffer_view = 0, values_byte_offset = 0;
4052
- ParseIntegerProperty(&values_buffer_view, err, values_obj, "bufferView",
4053
- true);
4251
+ if (!ParseIntegerProperty(&values_buffer_view, err, values_obj, "bufferView",
4252
+ true, "SparseAccessor")) {
4253
+ return false;
4254
+ }
4054
4255
  ParseIntegerProperty(&values_byte_offset, err, values_obj, "byteOffset",
4055
- true);
4256
+ false);
4056
4257
 
4057
4258
  accessor->sparse.count = count;
4058
4259
  accessor->sparse.indices.bufferView = indices_buffer_view;
@@ -4061,8 +4262,6 @@ static bool ParseSparseAccessor(Accessor *accessor, std::string *err,
4061
4262
  accessor->sparse.values.bufferView = values_buffer_view;
4062
4263
  accessor->sparse.values.byteOffset = values_byte_offset;
4063
4264
 
4064
- // todo check theses values
4065
-
4066
4265
  return true;
4067
4266
  }
4068
4267
 
@@ -4270,6 +4469,7 @@ static bool GetAttributeForAllPoints(uint32_t componentType, draco::Mesh *mesh,
4270
4469
  static bool ParseDracoExtension(Primitive *primitive, Model *model,
4271
4470
  std::string *err,
4272
4471
  const Value &dracoExtensionValue) {
4472
+ (void)err;
4273
4473
  auto bufferViewValue = dracoExtensionValue.Get("bufferView");
4274
4474
  if (!bufferViewValue.IsInt()) return false;
4275
4475
  auto attributesValue = dracoExtensionValue.Get("attributes");
@@ -4330,7 +4530,6 @@ static bool ParseDracoExtension(Primitive *primitive, Model *model,
4330
4530
 
4331
4531
  int dracoAttributeIndex = attribute.second.Get<int>();
4332
4532
  const auto pAttribute = mesh->GetAttributeByUniqueId(dracoAttributeIndex);
4333
- const auto pBuffer = pAttribute->buffer();
4334
4533
  const auto componentType =
4335
4534
  model->accessors[primitiveAttribute->second].componentType;
4336
4535
 
@@ -4751,6 +4950,13 @@ static bool ParseAnimationChannel(
4751
4950
  }
4752
4951
  return false;
4753
4952
  }
4953
+ ParseExtensionsProperty(&channel->target_extensions, err, target_object);
4954
+ if (store_original_json_for_extras_and_extensions) {
4955
+ json_const_iterator it;
4956
+ if (FindMember(target_object, "extensions", it)) {
4957
+ channel->target_extensions_json_string = JsonToString(GetValue(it));
4958
+ }
4959
+ }
4754
4960
  }
4755
4961
 
4756
4962
  channel->sampler = samplerIndex;
@@ -4882,12 +5088,12 @@ static bool ParseSampler(Sampler *sampler, std::string *err, const json &o,
4882
5088
  int magFilter = -1;
4883
5089
  int wrapS = TINYGLTF_TEXTURE_WRAP_REPEAT;
4884
5090
  int wrapT = TINYGLTF_TEXTURE_WRAP_REPEAT;
4885
- int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT;
5091
+ //int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT;
4886
5092
  ParseIntegerProperty(&minFilter, err, o, "minFilter", false);
4887
5093
  ParseIntegerProperty(&magFilter, err, o, "magFilter", false);
4888
5094
  ParseIntegerProperty(&wrapS, err, o, "wrapS", false);
4889
5095
  ParseIntegerProperty(&wrapT, err, o, "wrapT", false);
4890
- ParseIntegerProperty(&wrapR, err, o, "wrapR", false); // tinygltf extension
5096
+ //ParseIntegerProperty(&wrapR, err, o, "wrapR", false); // tinygltf extension
4891
5097
 
4892
5098
  // TODO(syoyo): Check the value is alloed one.
4893
5099
  // (e.g. we allow 9728(NEAREST), but don't allow 9727)
@@ -4896,7 +5102,7 @@ static bool ParseSampler(Sampler *sampler, std::string *err, const json &o,
4896
5102
  sampler->magFilter = magFilter;
4897
5103
  sampler->wrapS = wrapS;
4898
5104
  sampler->wrapT = wrapT;
4899
- sampler->wrapR = wrapR;
5105
+ //sampler->wrapR = wrapR;
4900
5106
 
4901
5107
  ParseExtensionsProperty(&(sampler->extensions), err, o);
4902
5108
  ParseExtrasProperty(&(sampler->extras), o);
@@ -5252,7 +5458,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
5252
5458
 
5253
5459
  #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \
5254
5460
  defined(_CPPUNWIND)) && \
5255
- !defined(TINYGLTF_NOEXCEPTION)
5461
+ !defined(TINYGLTF_NOEXCEPTION)
5256
5462
  try {
5257
5463
  JsonParse(v, json_str, json_str_length, true);
5258
5464
 
@@ -5525,7 +5731,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
5525
5731
 
5526
5732
  // Assign missing bufferView target types
5527
5733
  // - Look for missing Mesh indices
5528
- // - Look for missing bufferView targets
5734
+ // - Look for missing Mesh attributes
5529
5735
  for (auto &mesh : model->meshes) {
5530
5736
  for (auto &primitive : mesh.primitives) {
5531
5737
  if (primitive.indices >
@@ -5553,14 +5759,25 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
5553
5759
  // we could optionally check if acessors' bufferView type is Scalar, as
5554
5760
  // it should be
5555
5761
  }
5556
- }
5557
- }
5558
- // find any missing targets, must be an array buffer type if not fulfilled
5559
- // from previous check
5560
- for (auto &bufferView : model->bufferViews) {
5561
- if (bufferView.target == 0) // missing target type
5562
- {
5563
- bufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
5762
+
5763
+ for (auto &attribute : primitive.attributes) {
5764
+ model
5765
+ ->bufferViews[size_t(
5766
+ model->accessors[size_t(attribute.second)].bufferView)]
5767
+ .target = TINYGLTF_TARGET_ARRAY_BUFFER;
5768
+ }
5769
+
5770
+ for (auto &target : primitive.targets) {
5771
+ for (auto &attribute : target) {
5772
+ auto bufferView =
5773
+ model->accessors[size_t(attribute.second)].bufferView;
5774
+ // bufferView could be null(-1) for sparse morph target
5775
+ if (bufferView >= 0) {
5776
+ model->bufferViews[size_t(bufferView)].target =
5777
+ TINYGLTF_TARGET_ARRAY_BUFFER;
5778
+ }
5779
+ }
5780
+ }
5564
5781
  }
5565
5782
  }
5566
5783
 
@@ -5612,13 +5829,13 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
5612
5829
  {
5613
5830
  json_const_iterator it;
5614
5831
  if (FindMember(o, "extensions", it)) {
5615
- model->extensions_json_string = JsonToString(GetValue(it));
5832
+ scene.extensions_json_string = JsonToString(GetValue(it));
5616
5833
  }
5617
5834
  }
5618
5835
  {
5619
5836
  json_const_iterator it;
5620
5837
  if (FindMember(o, "extras", it)) {
5621
- model->extras_json_string = JsonToString(GetValue(it));
5838
+ scene.extras_json_string = JsonToString(GetValue(it));
5622
5839
  }
5623
5840
  }
5624
5841
  }
@@ -5668,6 +5885,18 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
5668
5885
  }
5669
5886
 
5670
5887
  // 11. Parse Image
5888
+ void *load_image_user_data{nullptr};
5889
+
5890
+ LoadImageDataOption load_image_option;
5891
+
5892
+ if (user_image_loader_) {
5893
+ // Use user supplied pointer
5894
+ load_image_user_data = load_image_user_data_;
5895
+ } else {
5896
+ load_image_option.preserve_channels = preserve_image_channels_;
5897
+ load_image_user_data = reinterpret_cast<void *>(&load_image_option);
5898
+ }
5899
+
5671
5900
  {
5672
5901
  int idx = 0;
5673
5902
  bool success = ForEachInArray(v, "images", [&](const json &o) {
@@ -5680,7 +5909,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
5680
5909
  Image image;
5681
5910
  if (!ParseImage(&image, idx, err, warn, o,
5682
5911
  store_original_json_for_extras_and_extensions_, base_dir,
5683
- &fs, &this->LoadImageData, load_image_user_data_)) {
5912
+ &fs, &this->LoadImageData, load_image_user_data)) {
5684
5913
  return false;
5685
5914
  }
5686
5915
 
@@ -5718,7 +5947,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
5718
5947
  bool ret = LoadImageData(
5719
5948
  &image, idx, err, warn, image.width, image.height,
5720
5949
  &buffer.data[bufferView.byteOffset],
5721
- static_cast<int>(bufferView.byteLength), load_image_user_data_);
5950
+ static_cast<int>(bufferView.byteLength), load_image_user_data);
5722
5951
  if (!ret) {
5723
5952
  return false;
5724
5953
  }
@@ -6143,6 +6372,13 @@ static void SerializeNumberProperty(const std::string &key, T number,
6143
6372
  JsonAddMember(obj, key.c_str(), json(number));
6144
6373
  }
6145
6374
 
6375
+ #ifdef TINYGLTF_USE_RAPIDJSON
6376
+ template <>
6377
+ void SerializeNumberProperty(const std::string &key, size_t number, json &obj) {
6378
+ JsonAddMember(obj, key.c_str(), json(static_cast<uint64_t>(number)));
6379
+ }
6380
+ #endif
6381
+
6146
6382
  template <typename T>
6147
6383
  static void SerializeNumberArrayProperty(const std::string &key,
6148
6384
  const std::vector<T> &value,
@@ -6278,17 +6514,25 @@ static void SerializeValue(const std::string &key, const Value &value,
6278
6514
  static void SerializeGltfBufferData(const std::vector<unsigned char> &data,
6279
6515
  json &o) {
6280
6516
  std::string header = "data:application/octet-stream;base64,";
6281
- std::string encodedData =
6282
- base64_encode(&data[0], static_cast<unsigned int>(data.size()));
6283
- SerializeStringProperty("uri", header + encodedData, o);
6517
+ if (data.size() > 0) {
6518
+ std::string encodedData =
6519
+ base64_encode(&data[0], static_cast<unsigned int>(data.size()));
6520
+ SerializeStringProperty("uri", header + encodedData, o);
6521
+ } else {
6522
+ // Issue #229
6523
+ // size 0 is allowd. Just emit mime header.
6524
+ SerializeStringProperty("uri", header, o);
6525
+ }
6284
6526
  }
6285
6527
 
6286
6528
  static bool SerializeGltfBufferData(const std::vector<unsigned char> &data,
6287
6529
  const std::string &binFilename) {
6288
6530
  #ifdef _WIN32
6289
- #if defined(__GLIBCXX__) // mingw
6290
- int file_descriptor = _wopen(UTF8ToWchar(binFilename).c_str(), _O_WRONLY | _O_BINARY);
6291
- __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
6531
+ #if defined(__GLIBCXX__) // mingw
6532
+ int file_descriptor = _wopen(UTF8ToWchar(binFilename).c_str(),
6533
+ _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY);
6534
+ __gnu_cxx::stdio_filebuf<char> wfile_buf(
6535
+ file_descriptor, std::ios_base::out | std::ios_base::binary);
6292
6536
  std::ostream output(&wfile_buf);
6293
6537
  if (!wfile_buf.is_open()) return false;
6294
6538
  #elif defined(_MSC_VER)
@@ -6302,8 +6546,14 @@ static bool SerializeGltfBufferData(const std::vector<unsigned char> &data,
6302
6546
  std::ofstream output(binFilename.c_str(), std::ofstream::binary);
6303
6547
  if (!output.is_open()) return false;
6304
6548
  #endif
6305
- output.write(reinterpret_cast<const char *>(&data[0]),
6306
- std::streamsize(data.size()));
6549
+ if (data.size() > 0) {
6550
+ output.write(reinterpret_cast<const char *>(&data[0]),
6551
+ std::streamsize(data.size()));
6552
+ } else {
6553
+ // Issue #229
6554
+ // size 0 will be still valid buffer data.
6555
+ // write empty file.
6556
+ }
6307
6557
  return true;
6308
6558
  }
6309
6559
 
@@ -6365,15 +6615,41 @@ static void SerializeExtensionMap(const ExtensionMap &extensions, json &o) {
6365
6615
  }
6366
6616
 
6367
6617
  static void SerializeGltfAccessor(Accessor &accessor, json &o) {
6368
- SerializeNumberProperty<int>("bufferView", accessor.bufferView, o);
6618
+ if (accessor.bufferView >= 0)
6619
+ SerializeNumberProperty<int>("bufferView", accessor.bufferView, o);
6369
6620
 
6370
- if (accessor.byteOffset != 0.0)
6621
+ if (accessor.byteOffset != 0)
6371
6622
  SerializeNumberProperty<int>("byteOffset", int(accessor.byteOffset), o);
6372
6623
 
6373
6624
  SerializeNumberProperty<int>("componentType", accessor.componentType, o);
6374
6625
  SerializeNumberProperty<size_t>("count", accessor.count, o);
6375
- SerializeNumberArrayProperty<double>("min", accessor.minValues, o);
6376
- SerializeNumberArrayProperty<double>("max", accessor.maxValues, o);
6626
+
6627
+ if ((accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) ||
6628
+ (accessor.componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE)) {
6629
+ SerializeNumberArrayProperty<double>("min", accessor.minValues, o);
6630
+ SerializeNumberArrayProperty<double>("max", accessor.maxValues, o);
6631
+ } else {
6632
+ // Issue #301. Serialize as integer.
6633
+ // Assume int value is within [-2**31-1, 2**31-1]
6634
+ {
6635
+ std::vector<int> values;
6636
+ std::transform(accessor.minValues.begin(), accessor.minValues.end(),
6637
+ std::back_inserter(values),
6638
+ [](double v) { return static_cast<int>(v); });
6639
+
6640
+ SerializeNumberArrayProperty<int>("min", values, o);
6641
+ }
6642
+
6643
+ {
6644
+ std::vector<int> values;
6645
+ std::transform(accessor.maxValues.begin(), accessor.maxValues.end(),
6646
+ std::back_inserter(values),
6647
+ [](double v) { return static_cast<int>(v); });
6648
+
6649
+ SerializeNumberArrayProperty<int>("max", values, o);
6650
+ }
6651
+ }
6652
+
6377
6653
  if (accessor.normalized)
6378
6654
  SerializeValue("normalized", Value(accessor.normalized), o);
6379
6655
  std::string type;
@@ -6416,6 +6692,8 @@ static void SerializeGltfAnimationChannel(AnimationChannel &channel, json &o) {
6416
6692
  SerializeNumberProperty("node", channel.target_node, target);
6417
6693
  SerializeStringProperty("path", channel.target_path, target);
6418
6694
 
6695
+ SerializeExtensionMap(channel.target_extensions, target);
6696
+
6419
6697
  JsonAddMember(o, "target", std::move(target));
6420
6698
  }
6421
6699
 
@@ -6455,6 +6733,7 @@ static void SerializeGltfAnimation(Animation &animation, json &o) {
6455
6733
 
6456
6734
  {
6457
6735
  json samplers;
6736
+ JsonReserveArray(samplers, animation.samplers.size());
6458
6737
  for (unsigned int i = 0; i < animation.samplers.size(); ++i) {
6459
6738
  json sampler;
6460
6739
  AnimationSampler gltfSampler = animation.samplers[i];
@@ -6480,10 +6759,15 @@ static void SerializeGltfAsset(Asset &asset, json &o) {
6480
6759
  SerializeStringProperty("copyright", asset.copyright, o);
6481
6760
  }
6482
6761
 
6483
- if (!asset.version.empty()) {
6484
- SerializeStringProperty("version", asset.version, o);
6762
+ if (asset.version.empty()) {
6763
+ // Just in case
6764
+ // `version` must be defined
6765
+ asset.version = "2.0";
6485
6766
  }
6486
6767
 
6768
+ // TODO(syoyo): Do we need to check if `version` is greater or equal to 2.0?
6769
+ SerializeStringProperty("version", asset.version, o);
6770
+
6487
6771
  if (asset.extras.Keys().size()) {
6488
6772
  SerializeValue("extras", asset.extras, o);
6489
6773
  }
@@ -6491,10 +6775,10 @@ static void SerializeGltfAsset(Asset &asset, json &o) {
6491
6775
  SerializeExtensionMap(asset.extensions, o);
6492
6776
  }
6493
6777
 
6494
- static void SerializeGltfBufferBin(Buffer &buffer, json &o,
6495
- std::vector<unsigned char> &binBuffer) {
6778
+ static void SerializeGltfBufferBin(Buffer &buffer, json &o,
6779
+ std::vector<unsigned char> &binBuffer) {
6496
6780
  SerializeNumberProperty("byteLength", buffer.data.size(), o);
6497
- binBuffer=buffer.data;
6781
+ binBuffer = buffer.data;
6498
6782
 
6499
6783
  if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o);
6500
6784
 
@@ -6561,6 +6845,7 @@ static void SerializeGltfImage(Image &image, json &o) {
6561
6845
  SerializeStringProperty("mimeType", image.mimeType, o);
6562
6846
  SerializeNumberProperty<int>("bufferView", image.bufferView, o);
6563
6847
  } else {
6848
+ // TODO(syoyo): dlib::urilencode?
6564
6849
  SerializeStringProperty("uri", image.uri, o);
6565
6850
  }
6566
6851
 
@@ -6677,8 +6962,8 @@ static void SerializeGltfMaterial(Material &material, json &o) {
6677
6962
  SerializeStringProperty("alphaMode", material.alphaMode, o);
6678
6963
  }
6679
6964
 
6680
- if(material.doubleSided != false)
6681
- JsonAddMember(o, "doubleSided", json(material.doubleSided));
6965
+ if (material.doubleSided != false)
6966
+ JsonAddMember(o, "doubleSided", json(material.doubleSided));
6682
6967
 
6683
6968
  if (material.normalTexture.index > -1) {
6684
6969
  json texinfo;
@@ -6782,7 +7067,7 @@ static void SerializeGltfMesh(Mesh &mesh, json &o) {
6782
7067
  JsonAddMember(primitive, "targets", std::move(targets));
6783
7068
  }
6784
7069
 
6785
- SerializeExtensionMap(gltfPrimitive.extensions, o);
7070
+ SerializeExtensionMap(gltfPrimitive.extensions, primitive);
6786
7071
 
6787
7072
  if (gltfPrimitive.extras.Type() != NULL_TYPE) {
6788
7073
  SerializeValue("extras", gltfPrimitive.extras, primitive);
@@ -6819,7 +7104,9 @@ static void SerializeSpotLight(SpotLight &spot, json &o) {
6819
7104
  static void SerializeGltfLight(Light &light, json &o) {
6820
7105
  if (!light.name.empty()) SerializeStringProperty("name", light.name, o);
6821
7106
  SerializeNumberProperty("intensity", light.intensity, o);
6822
- SerializeNumberProperty("range", light.range, o);
7107
+ if (light.range > 0.0) {
7108
+ SerializeNumberProperty("range", light.range, o);
7109
+ }
6823
7110
  SerializeNumberArrayProperty("color", light.color, o);
6824
7111
  SerializeStringProperty("type", light.type, o);
6825
7112
  if (light.type == "spot") {
@@ -6878,7 +7165,7 @@ static void SerializeGltfSampler(Sampler &sampler, json &o) {
6878
7165
  if (sampler.minFilter != -1) {
6879
7166
  SerializeNumberProperty("minFilter", sampler.minFilter, o);
6880
7167
  }
6881
- SerializeNumberProperty("wrapR", sampler.wrapR, o);
7168
+ //SerializeNumberProperty("wrapR", sampler.wrapR, o);
6882
7169
  SerializeNumberProperty("wrapS", sampler.wrapS, o);
6883
7170
  SerializeNumberProperty("wrapT", sampler.wrapT, o);
6884
7171
 
@@ -6933,6 +7220,11 @@ static void SerializeGltfCamera(const Camera &camera, json &o) {
6933
7220
  } else {
6934
7221
  // ???
6935
7222
  }
7223
+
7224
+ if (camera.extras.Type() != NULL_TYPE) {
7225
+ SerializeValue("extras", camera.extras, o);
7226
+ }
7227
+ SerializeExtensionMap(camera.extensions, o);
6936
7228
  }
6937
7229
 
6938
7230
  static void SerializeGltfScene(Scene &scene, json &o) {
@@ -6948,11 +7240,17 @@ static void SerializeGltfScene(Scene &scene, json &o) {
6948
7240
  }
6949
7241
 
6950
7242
  static void SerializeGltfSkin(Skin &skin, json &o) {
6951
- if (skin.inverseBindMatrices != -1)
7243
+ // required
7244
+ SerializeNumberArrayProperty<int>("joints", skin.joints, o);
7245
+
7246
+ if (skin.inverseBindMatrices >= 0) {
6952
7247
  SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o);
7248
+ }
7249
+
7250
+ if (skin.skeleton >= 0) {
7251
+ SerializeNumberProperty("skeleton", skin.skeleton, o);
7252
+ }
6953
7253
 
6954
- SerializeNumberArrayProperty<int>("joints", skin.joints, o);
6955
- SerializeNumberProperty("skeleton", skin.skeleton, o);
6956
7254
  if (skin.name.size()) {
6957
7255
  SerializeStringProperty("name", skin.name, o);
6958
7256
  }
@@ -6979,14 +7277,16 @@ static void SerializeGltfTexture(Texture &texture, json &o) {
6979
7277
  ///
6980
7278
  static void SerializeGltfModel(Model *model, json &o) {
6981
7279
  // ACCESSORS
6982
- json accessors;
6983
- JsonReserveArray(accessors, model->accessors.size());
6984
- for (unsigned int i = 0; i < model->accessors.size(); ++i) {
6985
- json accessor;
6986
- SerializeGltfAccessor(model->accessors[i], accessor);
6987
- JsonPushBack(accessors, std::move(accessor));
7280
+ if (model->accessors.size()) {
7281
+ json accessors;
7282
+ JsonReserveArray(accessors, model->accessors.size());
7283
+ for (unsigned int i = 0; i < model->accessors.size(); ++i) {
7284
+ json accessor;
7285
+ SerializeGltfAccessor(model->accessors[i], accessor);
7286
+ JsonPushBack(accessors, std::move(accessor));
7287
+ }
7288
+ JsonAddMember(o, "accessors", std::move(accessors));
6988
7289
  }
6989
- JsonAddMember(o, "accessors", std::move(accessors));
6990
7290
 
6991
7291
  // ANIMATIONS
6992
7292
  if (model->animations.size()) {
@@ -7009,18 +7309,15 @@ static void SerializeGltfModel(Model *model, json &o) {
7009
7309
  JsonAddMember(o, "asset", std::move(asset));
7010
7310
 
7011
7311
  // BUFFERVIEWS
7012
- json bufferViews;
7013
- JsonReserveArray(bufferViews, model->bufferViews.size());
7014
- for (unsigned int i = 0; i < model->bufferViews.size(); ++i) {
7015
- json bufferView;
7016
- SerializeGltfBufferView(model->bufferViews[i], bufferView);
7017
- JsonPushBack(bufferViews, std::move(bufferView));
7018
- }
7019
- JsonAddMember(o, "bufferViews", std::move(bufferViews));
7020
-
7021
- // Extensions used
7022
- if (model->extensionsUsed.size()) {
7023
- SerializeStringArrayProperty("extensionsUsed", model->extensionsUsed, o);
7312
+ if (model->bufferViews.size()) {
7313
+ json bufferViews;
7314
+ JsonReserveArray(bufferViews, model->bufferViews.size());
7315
+ for (unsigned int i = 0; i < model->bufferViews.size(); ++i) {
7316
+ json bufferView;
7317
+ SerializeGltfBufferView(model->bufferViews[i], bufferView);
7318
+ JsonPushBack(bufferViews, std::move(bufferView));
7319
+ }
7320
+ JsonAddMember(o, "bufferViews", std::move(bufferViews));
7024
7321
  }
7025
7322
 
7026
7323
  // Extensions required
@@ -7036,6 +7333,16 @@ static void SerializeGltfModel(Model *model, json &o) {
7036
7333
  for (unsigned int i = 0; i < model->materials.size(); ++i) {
7037
7334
  json material;
7038
7335
  SerializeGltfMaterial(model->materials[i], material);
7336
+
7337
+ if (JsonIsNull(material)) {
7338
+ // Issue 294.
7339
+ // `material` does not have any required parameters
7340
+ // so the result may be null(unmodified) when all material parameters
7341
+ // have default value.
7342
+ //
7343
+ // null is not allowed thus we create an empty JSON object.
7344
+ JsonSetObject(material);
7345
+ }
7039
7346
  JsonPushBack(materials, std::move(material));
7040
7347
  }
7041
7348
  JsonAddMember(o, "materials", std::move(materials));
@@ -7133,7 +7440,9 @@ static void SerializeGltfModel(Model *model, json &o) {
7133
7440
  // EXTENSIONS
7134
7441
  SerializeExtensionMap(model->extensions, o);
7135
7442
 
7136
- // LIGHTS as KHR_lights_cmn
7443
+ auto extensionsUsed = model->extensionsUsed;
7444
+
7445
+ // LIGHTS as KHR_lights_punctual
7137
7446
  if (model->lights.size()) {
7138
7447
  json lights;
7139
7448
  JsonReserveArray(lights, model->lights.size());
@@ -7148,7 +7457,7 @@ static void SerializeGltfModel(Model *model, json &o) {
7148
7457
 
7149
7458
  {
7150
7459
  json_const_iterator it;
7151
- if (!FindMember(o, "extensions", it)) {
7460
+ if (FindMember(o, "extensions", it)) {
7152
7461
  JsonAssign(ext_j, GetValue(it));
7153
7462
  }
7154
7463
  }
@@ -7156,6 +7465,24 @@ static void SerializeGltfModel(Model *model, json &o) {
7156
7465
  JsonAddMember(ext_j, "KHR_lights_punctual", std::move(khr_lights_cmn));
7157
7466
 
7158
7467
  JsonAddMember(o, "extensions", std::move(ext_j));
7468
+
7469
+ // Also add "KHR_lights_punctual" to `extensionsUsed`
7470
+ {
7471
+ auto has_khr_lights_punctual =
7472
+ std::find_if(extensionsUsed.begin(), extensionsUsed.end(),
7473
+ [](const std::string &s) {
7474
+ return (s.compare("KHR_lights_punctual") == 0);
7475
+ });
7476
+
7477
+ if (has_khr_lights_punctual == extensionsUsed.end()) {
7478
+ extensionsUsed.push_back("KHR_lights_punctual");
7479
+ }
7480
+ }
7481
+ }
7482
+
7483
+ // Extensions used
7484
+ if (extensionsUsed.size()) {
7485
+ SerializeStringArrayProperty("extensionsUsed", extensionsUsed, o);
7159
7486
  }
7160
7487
 
7161
7488
  // EXTRAS
@@ -7175,8 +7502,10 @@ static bool WriteGltfFile(const std::string &output,
7175
7502
  #if defined(_MSC_VER)
7176
7503
  std::ofstream gltfFile(UTF8ToWchar(output).c_str());
7177
7504
  #elif defined(__GLIBCXX__)
7178
- int file_descriptor = _wopen(UTF8ToWchar(output).c_str(), _O_WRONLY | _O_BINARY);
7179
- __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
7505
+ int file_descriptor = _wopen(UTF8ToWchar(output).c_str(),
7506
+ _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY);
7507
+ __gnu_cxx::stdio_filebuf<char> wfile_buf(
7508
+ file_descriptor, std::ios_base::out | std::ios_base::binary);
7180
7509
  std::ostream gltfFile(&wfile_buf);
7181
7510
  if (!wfile_buf.is_open()) return false;
7182
7511
  #else
@@ -7196,33 +7525,25 @@ static void WriteBinaryGltfStream(std::ostream &stream,
7196
7525
  const std::string header = "glTF";
7197
7526
  const int version = 2;
7198
7527
 
7199
- // https://stackoverflow.com/questions/3407012/c-rounding-up-to-the-nearest-multiple-of-a-number
7200
- auto roundUp = [](uint32_t numToRound, uint32_t multiple)
7201
- {
7202
- if (multiple == 0)
7203
- return numToRound;
7204
-
7205
- uint32_t remainder = numToRound % multiple;
7206
- if (remainder == 0)
7207
- return numToRound;
7208
-
7209
- return numToRound + multiple - remainder;
7210
- };
7211
-
7212
- const uint32_t padding_size = roundUp(content.size(), 4) - content.size();
7528
+ const uint32_t content_size = uint32_t(content.size());
7529
+ const uint32_t binBuffer_size = uint32_t(binBuffer.size());
7530
+ // determine number of padding bytes required to ensure 4 byte alignment
7531
+ const uint32_t content_padding_size = content_size % 4 == 0 ? 0 : 4 - content_size % 4;
7532
+ const uint32_t bin_padding_size = binBuffer_size % 4 == 0 ? 0 : 4 - binBuffer_size % 4;
7213
7533
 
7214
7534
  // 12 bytes for header, JSON content length, 8 bytes for JSON chunk info.
7215
- // Chunk data must be located at 4-byte boundary.
7216
- const int length = 12 + 8 + roundUp(content.size(), 4)+
7217
- (binBuffer.size()?(8+roundUp(binBuffer.size(),4)) : 0);
7535
+ // Chunk data must be located at 4-byte boundary, which may require padding
7536
+ const uint32_t length =
7537
+ 12 + 8 + content_size + content_padding_size +
7538
+ (binBuffer_size ? (8 + binBuffer_size + bin_padding_size) : 0);
7218
7539
 
7219
7540
  stream.write(header.c_str(), std::streamsize(header.size()));
7220
7541
  stream.write(reinterpret_cast<const char *>(&version), sizeof(version));
7221
7542
  stream.write(reinterpret_cast<const char *>(&length), sizeof(length));
7222
7543
 
7223
7544
  // JSON chunk info, then JSON data
7224
- const int model_length = int(content.size()) + padding_size;
7225
- const int model_format = 0x4E4F534A;
7545
+ const uint32_t model_length = uint32_t(content.size()) + content_padding_size;
7546
+ const uint32_t model_format = 0x4E4F534A;
7226
7547
  stream.write(reinterpret_cast<const char *>(&model_length),
7227
7548
  sizeof(model_length));
7228
7549
  stream.write(reinterpret_cast<const char *>(&model_format),
@@ -7230,24 +7551,26 @@ static void WriteBinaryGltfStream(std::ostream &stream,
7230
7551
  stream.write(content.c_str(), std::streamsize(content.size()));
7231
7552
 
7232
7553
  // Chunk must be multiplies of 4, so pad with spaces
7233
- if (padding_size > 0) {
7234
- const std::string padding = std::string(size_t(padding_size), ' ');
7554
+ if (content_padding_size > 0) {
7555
+ const std::string padding = std::string(size_t(content_padding_size), ' ');
7235
7556
  stream.write(padding.c_str(), std::streamsize(padding.size()));
7236
7557
  }
7237
- if (binBuffer.size() > 0){
7238
- const uint32_t bin_padding_size = roundUp(binBuffer.size(), 4) - binBuffer.size();
7558
+ if (binBuffer.size() > 0) {
7239
7559
  // BIN chunk info, then BIN data
7240
- const int bin_length = int(binBuffer.size()) + bin_padding_size;
7241
- const int bin_format = 0x004e4942;
7560
+ const uint32_t bin_length = uint32_t(binBuffer.size()) + bin_padding_size;
7561
+ const uint32_t bin_format = 0x004e4942;
7242
7562
  stream.write(reinterpret_cast<const char *>(&bin_length),
7243
- sizeof(bin_length));
7563
+ sizeof(bin_length));
7244
7564
  stream.write(reinterpret_cast<const char *>(&bin_format),
7245
- sizeof(bin_format));
7246
- stream.write((const char *)binBuffer.data(), std::streamsize(binBuffer.size()));
7565
+ sizeof(bin_format));
7566
+ stream.write(reinterpret_cast<const char *>(binBuffer.data()),
7567
+ std::streamsize(binBuffer.size()));
7247
7568
  // Chunksize must be multiplies of 4, so pad with zeroes
7248
7569
  if (bin_padding_size > 0) {
7249
- const std::vector<unsigned char> padding = std::vector<unsigned char>(size_t(bin_padding_size), 0);
7250
- stream.write((const char *)padding.data(), std::streamsize(padding.size()));
7570
+ const std::vector<unsigned char> padding =
7571
+ std::vector<unsigned char>(size_t(bin_padding_size), 0);
7572
+ stream.write(reinterpret_cast<const char *>(padding.data()),
7573
+ std::streamsize(padding.size()));
7251
7574
  }
7252
7575
  }
7253
7576
  }
@@ -7259,8 +7582,10 @@ static void WriteBinaryGltfFile(const std::string &output,
7259
7582
  #if defined(_MSC_VER)
7260
7583
  std::ofstream gltfFile(UTF8ToWchar(output).c_str(), std::ios::binary);
7261
7584
  #elif defined(__GLIBCXX__)
7262
- int file_descriptor = _wopen(UTF8ToWchar(output).c_str(), _O_WRONLY | _O_BINARY);
7263
- __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
7585
+ int file_descriptor = _wopen(UTF8ToWchar(output).c_str(),
7586
+ _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY);
7587
+ __gnu_cxx::stdio_filebuf<char> wfile_buf(
7588
+ file_descriptor, std::ios_base::out | std::ios_base::binary);
7264
7589
  std::ostream gltfFile(&wfile_buf);
7265
7590
  #else
7266
7591
  std::ofstream gltfFile(output.c_str(), std::ios::binary);
@@ -7268,7 +7593,7 @@ static void WriteBinaryGltfFile(const std::string &output,
7268
7593
  #else
7269
7594
  std::ofstream gltfFile(output.c_str(), std::ios::binary);
7270
7595
  #endif
7271
- WriteBinaryGltfStream(gltfFile, content,binBuffer);
7596
+ WriteBinaryGltfStream(gltfFile, content, binBuffer);
7272
7597
  }
7273
7598
 
7274
7599
  bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream,
@@ -7280,20 +7605,21 @@ bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream,
7280
7605
  SerializeGltfModel(model, output);
7281
7606
 
7282
7607
  // BUFFERS
7283
- std::vector<std::string> usedUris;
7284
7608
  std::vector<unsigned char> binBuffer;
7285
- json buffers;
7286
- JsonReserveArray(buffers, model->buffers.size());
7287
- for (unsigned int i = 0; i < model->buffers.size(); ++i) {
7288
- json buffer;
7289
- if (writeBinary && i==0 && model->buffers[i].uri.empty()){
7290
- SerializeGltfBufferBin(model->buffers[i], buffer,binBuffer);
7291
- } else {
7292
- SerializeGltfBuffer(model->buffers[i], buffer);
7609
+ if (model->buffers.size()) {
7610
+ json buffers;
7611
+ JsonReserveArray(buffers, model->buffers.size());
7612
+ for (unsigned int i = 0; i < model->buffers.size(); ++i) {
7613
+ json buffer;
7614
+ if (writeBinary && i == 0 && model->buffers[i].uri.empty()) {
7615
+ SerializeGltfBufferBin(model->buffers[i], buffer, binBuffer);
7616
+ } else {
7617
+ SerializeGltfBuffer(model->buffers[i], buffer);
7618
+ }
7619
+ JsonPushBack(buffers, std::move(buffer));
7293
7620
  }
7294
- JsonPushBack(buffers, std::move(buffer));
7621
+ JsonAddMember(output, "buffers", std::move(buffers));
7295
7622
  }
7296
- JsonAddMember(output, "buffers", std::move(buffers));
7297
7623
 
7298
7624
  // IMAGES
7299
7625
  if (model->images.size()) {
@@ -7303,9 +7629,9 @@ bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream,
7303
7629
  json image;
7304
7630
 
7305
7631
  std::string dummystring = "";
7306
- // UpdateImageObject need baseDir but only uses it if embededImages is
7307
- // enable, since we won't write separte images when writing to a stream we
7308
- // use a dummystring
7632
+ // UpdateImageObject need baseDir but only uses it if embeddedImages is
7633
+ // enabled, since we won't write separate images when writing to a stream
7634
+ // we
7309
7635
  UpdateImageObject(model->images[i], dummystring, int(i), false,
7310
7636
  &this->WriteImageData, this->write_image_user_data_);
7311
7637
  SerializeGltfImage(model->images[i], image);
@@ -7315,7 +7641,7 @@ bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream,
7315
7641
  }
7316
7642
 
7317
7643
  if (writeBinary) {
7318
- WriteBinaryGltfStream(stream, JsonToString(output),binBuffer);
7644
+ WriteBinaryGltfStream(stream, JsonToString(output), binBuffer);
7319
7645
  } else {
7320
7646
  WriteGltfStream(stream, JsonToString(output, prettyPrint ? 2 : -1));
7321
7647
  }
@@ -7347,44 +7673,47 @@ bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename,
7347
7673
  // BUFFERS
7348
7674
  std::vector<std::string> usedUris;
7349
7675
  std::vector<unsigned char> binBuffer;
7350
- json buffers;
7351
- JsonReserveArray(buffers, model->buffers.size());
7352
- for (unsigned int i = 0; i < model->buffers.size(); ++i) {
7353
- json buffer;
7354
- if (writeBinary && i==0 && model->buffers[i].uri.empty()){
7355
- SerializeGltfBufferBin(model->buffers[i], buffer,binBuffer);
7356
- } else if (embedBuffers) {
7357
- SerializeGltfBuffer(model->buffers[i], buffer);
7358
- } else {
7359
- std::string binSavePath;
7360
- std::string binUri;
7361
- if (!model->buffers[i].uri.empty() && !IsDataURI(model->buffers[i].uri)) {
7362
- binUri = model->buffers[i].uri;
7676
+ if (model->buffers.size()) {
7677
+ json buffers;
7678
+ JsonReserveArray(buffers, model->buffers.size());
7679
+ for (unsigned int i = 0; i < model->buffers.size(); ++i) {
7680
+ json buffer;
7681
+ if (writeBinary && i == 0 && model->buffers[i].uri.empty()) {
7682
+ SerializeGltfBufferBin(model->buffers[i], buffer, binBuffer);
7683
+ } else if (embedBuffers) {
7684
+ SerializeGltfBuffer(model->buffers[i], buffer);
7363
7685
  } else {
7364
- binUri = defaultBinFilename + defaultBinFileExt;
7365
- bool inUse = true;
7366
- int numUsed = 0;
7367
- while (inUse) {
7368
- inUse = false;
7369
- for (const std::string &usedName : usedUris) {
7370
- if (binUri.compare(usedName) != 0) continue;
7371
- inUse = true;
7372
- binUri = defaultBinFilename + std::to_string(numUsed++) +
7373
- defaultBinFileExt;
7374
- break;
7686
+ std::string binSavePath;
7687
+ std::string binUri;
7688
+ if (!model->buffers[i].uri.empty() &&
7689
+ !IsDataURI(model->buffers[i].uri)) {
7690
+ binUri = model->buffers[i].uri;
7691
+ } else {
7692
+ binUri = defaultBinFilename + defaultBinFileExt;
7693
+ bool inUse = true;
7694
+ int numUsed = 0;
7695
+ while (inUse) {
7696
+ inUse = false;
7697
+ for (const std::string &usedName : usedUris) {
7698
+ if (binUri.compare(usedName) != 0) continue;
7699
+ inUse = true;
7700
+ binUri = defaultBinFilename + std::to_string(numUsed++) +
7701
+ defaultBinFileExt;
7702
+ break;
7703
+ }
7375
7704
  }
7376
7705
  }
7706
+ usedUris.push_back(binUri);
7707
+ binSavePath = JoinPath(baseDir, binUri);
7708
+ if (!SerializeGltfBuffer(model->buffers[i], buffer, binSavePath,
7709
+ binUri)) {
7710
+ return false;
7711
+ }
7377
7712
  }
7378
- usedUris.push_back(binUri);
7379
- binSavePath = JoinPath(baseDir, binUri);
7380
- if (!SerializeGltfBuffer(model->buffers[i], buffer, binSavePath,
7381
- binUri)) {
7382
- return false;
7383
- }
7713
+ JsonPushBack(buffers, std::move(buffer));
7384
7714
  }
7385
- JsonPushBack(buffers, std::move(buffer));
7715
+ JsonAddMember(output, "buffers", std::move(buffers));
7386
7716
  }
7387
- JsonAddMember(output, "buffers", std::move(buffers));
7388
7717
 
7389
7718
  // IMAGES
7390
7719
  if (model->images.size()) {
@@ -7402,7 +7731,7 @@ bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename,
7402
7731
  }
7403
7732
 
7404
7733
  if (writeBinary) {
7405
- WriteBinaryGltfFile(filename, JsonToString(output),binBuffer);
7734
+ WriteBinaryGltfFile(filename, JsonToString(output), binBuffer);
7406
7735
  } else {
7407
7736
  WriteGltfFile(filename, JsonToString(output, (prettyPrint ? 2 : -1)));
7408
7737
  }