tiny_gltf 1.0.0 → 2.5.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  }