tiny_gltf 1.0.2 → 2.5.0.3

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 - 2020 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,9 @@
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.
29
32
  // - v2.4.2 Decode percent-encoded URI.
30
33
  // - v2.4.1 Fix some glTF object class does not have `extensions` and/or
31
34
  // `extras` property.
@@ -105,7 +108,7 @@ namespace tinygltf {
105
108
  #define TINYGLTF_COMPONENT_TYPE_INT (5124)
106
109
  #define TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT (5125)
107
110
  #define TINYGLTF_COMPONENT_TYPE_FLOAT (5126)
108
- #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
109
112
 
110
113
  #define TINYGLTF_TEXTURE_FILTER_NEAREST (9728)
111
114
  #define TINYGLTF_TEXTURE_FILTER_LINEAR (9729)
@@ -188,14 +191,14 @@ AAssetManager *asset_manager = nullptr;
188
191
  #endif
189
192
 
190
193
  typedef enum {
191
- NULL_TYPE = 0,
192
- REAL_TYPE = 1,
193
- INT_TYPE = 2,
194
- BOOL_TYPE = 3,
195
- STRING_TYPE = 4,
196
- ARRAY_TYPE = 5,
197
- BINARY_TYPE = 6,
198
- 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
199
202
  } Type;
200
203
 
201
204
  static inline int32_t GetComponentSizeInBytes(uint32_t componentType) {
@@ -250,7 +253,6 @@ bool DecodeDataURI(std::vector<unsigned char> *out, std::string &mime_type,
250
253
  #ifdef __clang__
251
254
  #pragma clang diagnostic push
252
255
  // Suppress warning for : static Value null_value
253
- // https://stackoverflow.com/questions/15708411/how-to-deal-with-global-constructor-warning-in-clang
254
256
  #pragma clang diagnostic ignored "-Wexit-time-destructors"
255
257
  #pragma clang diagnostic ignored "-Wpadded"
256
258
  #endif
@@ -295,7 +297,7 @@ class Value {
295
297
 
296
298
  DEFAULT_METHODS(Value)
297
299
 
298
- char Type() const { return static_cast<const char>(type_); }
300
+ char Type() const { return static_cast<char>(type_); }
299
301
 
300
302
  bool IsBool() const { return (type_ == BOOL_TYPE); }
301
303
 
@@ -601,7 +603,7 @@ struct Sampler {
601
603
  // `magFilter`. Set -1 in TinyGLTF(issue #186)
602
604
  int minFilter =
603
605
  -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR",
604
- // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_NEAREST",
606
+ // "NEAREST_MIPMAP_NEAREST", "LINEAR_MIPMAP_NEAREST",
605
607
  // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_LINEAR"]
606
608
  int magFilter =
607
609
  -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR"]
@@ -611,7 +613,7 @@ struct Sampler {
611
613
  int wrapT =
612
614
  TINYGLTF_TEXTURE_WRAP_REPEAT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT",
613
615
  // "REPEAT"], default "REPEAT"
614
- int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT; // TinyGLTF extension
616
+ //int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT; // TinyGLTF extension. currently not used.
615
617
 
616
618
  Value extras;
617
619
  ExtensionMap extensions;
@@ -624,8 +626,7 @@ struct Sampler {
624
626
  : minFilter(-1),
625
627
  magFilter(-1),
626
628
  wrapS(TINYGLTF_TEXTURE_WRAP_REPEAT),
627
- wrapT(TINYGLTF_TEXTURE_WRAP_REPEAT),
628
- wrapR(TINYGLTF_TEXTURE_WRAP_REPEAT) {}
629
+ wrapT(TINYGLTF_TEXTURE_WRAP_REPEAT) {}
629
630
  DEFAULT_METHODS(Sampler)
630
631
  bool operator==(const Sampler &) const;
631
632
  };
@@ -848,8 +849,10 @@ struct Accessor {
848
849
  std::string extras_json_string;
849
850
  std::string extensions_json_string;
850
851
 
851
- std::vector<double> minValues; // optional
852
- 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
853
856
 
854
857
  struct {
855
858
  int count;
@@ -1067,7 +1070,7 @@ struct Buffer {
1067
1070
  };
1068
1071
 
1069
1072
  struct Asset {
1070
- std::string version; // required
1073
+ std::string version = "2.0"; // required
1071
1074
  std::string generator;
1072
1075
  std::string minVersion;
1073
1076
  std::string copyright;
@@ -1189,7 +1192,8 @@ enum SectionCheck {
1189
1192
  ///
1190
1193
  typedef bool (*LoadImageDataFunction)(Image *, const int, std::string *,
1191
1194
  std::string *, int, int,
1192
- const unsigned char *, int, void *);
1195
+ const unsigned char *, int,
1196
+ void *user_pointer);
1193
1197
 
1194
1198
  ///
1195
1199
  /// WriteImageDataFunction type. Signature for custom image writing callbacks.
@@ -1254,7 +1258,7 @@ bool FileExists(const std::string &abs_filename, void *);
1254
1258
 
1255
1259
  ///
1256
1260
  /// Expand file path(e.g. `~` to home directory on posix, `%APPDATA%` to
1257
- /// `C:\Users\tinygltf\AppData`)
1261
+ /// `C:\\Users\\tinygltf\\AppData`)
1258
1262
  ///
1259
1263
  /// @param[in] filepath File path string. Assume UTF-8
1260
1264
  /// @param[in] userdata User data. Set to `nullptr` if you don't need it.
@@ -1345,6 +1349,11 @@ class TinyGLTF {
1345
1349
  ///
1346
1350
  void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data);
1347
1351
 
1352
+ ///
1353
+ /// Unset(remove) callback of loading image data
1354
+ ///
1355
+ void RemoveImageLoader();
1356
+
1348
1357
  ///
1349
1358
  /// Set callback to use for writing image data
1350
1359
  ///
@@ -1383,6 +1392,16 @@ class TinyGLTF {
1383
1392
  return store_original_json_for_extras_and_extensions_;
1384
1393
  }
1385
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
+
1386
1405
  private:
1387
1406
  ///
1388
1407
  /// Loads glTF asset from string(memory).
@@ -1402,6 +1421,9 @@ class TinyGLTF {
1402
1421
 
1403
1422
  bool store_original_json_for_extras_and_extensions_ = false;
1404
1423
 
1424
+ bool preserve_image_channels_ = false; /// Default false(expand channels to
1425
+ /// RGBA) for backward compatibility.
1426
+
1405
1427
  FsCallbacks fs = {
1406
1428
  #ifndef TINYGLTF_NO_FS
1407
1429
  &tinygltf::FileExists, &tinygltf::ExpandFilePath,
@@ -1421,7 +1443,8 @@ class TinyGLTF {
1421
1443
  #else
1422
1444
  nullptr;
1423
1445
  #endif
1424
- void *load_image_user_data_ = reinterpret_cast<void *>(&fs);
1446
+ void *load_image_user_data_{nullptr};
1447
+ bool user_image_loader_{false};
1425
1448
 
1426
1449
  WriteImageDataFunction WriteImageData =
1427
1450
  #ifndef TINYGLTF_NO_STB_IMAGE_WRITE
@@ -1429,7 +1452,7 @@ class TinyGLTF {
1429
1452
  #else
1430
1453
  nullptr;
1431
1454
  #endif
1432
- void *write_image_user_data_ = reinterpret_cast<void *>(&fs);
1455
+ void *write_image_user_data_{nullptr};
1433
1456
  };
1434
1457
 
1435
1458
  #ifdef __clang__
@@ -1514,6 +1537,7 @@ class TinyGLTF {
1514
1537
  #ifndef TINYGLTF_USE_RAPIDJSON
1515
1538
  #include "json.hpp"
1516
1539
  #else
1540
+ #ifndef TINYGLTF_NO_INCLUDE_RAPIDJSON
1517
1541
  #include "document.h"
1518
1542
  #include "prettywriter.h"
1519
1543
  #include "rapidjson.h"
@@ -1521,6 +1545,7 @@ class TinyGLTF {
1521
1545
  #include "writer.h"
1522
1546
  #endif
1523
1547
  #endif
1548
+ #endif
1524
1549
 
1525
1550
  #ifdef TINYGLTF_ENABLE_DRACO
1526
1551
  #include "draco/compression/decode.h"
@@ -1579,7 +1604,7 @@ class TinyGLTF {
1579
1604
 
1580
1605
  #endif
1581
1606
 
1582
- #elif !defined(__ANDROID__)
1607
+ #elif !defined(__ANDROID__) && !defined(__OpenBSD__)
1583
1608
  #include <wordexp.h>
1584
1609
  #endif
1585
1610
 
@@ -1683,6 +1708,19 @@ void JsonParse(JsonDocument &doc, const char *str, size_t length,
1683
1708
 
1684
1709
  namespace tinygltf {
1685
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
+
1686
1724
  // Equals function for Value, for recursivity
1687
1725
  static bool Equals(const tinygltf::Value &one, const tinygltf::Value &other) {
1688
1726
  if (one.Type() != other.Type()) return false;
@@ -1895,8 +1933,10 @@ bool Sampler::operator==(const Sampler &other) const {
1895
1933
  return this->extensions == other.extensions && this->extras == other.extras &&
1896
1934
  this->magFilter == other.magFilter &&
1897
1935
  this->minFilter == other.minFilter && this->name == other.name &&
1898
- this->wrapR == other.wrapR && this->wrapS == other.wrapS &&
1936
+ this->wrapS == other.wrapS &&
1899
1937
  this->wrapT == other.wrapT;
1938
+
1939
+ //this->wrapR == other.wrapR
1900
1940
  }
1901
1941
  bool Scene::operator==(const Scene &other) const {
1902
1942
  return this->extensions == other.extensions && this->extras == other.extras &&
@@ -2000,9 +2040,11 @@ static std::string GetBaseDir(const std::string &filepath) {
2000
2040
  return "";
2001
2041
  }
2002
2042
 
2003
- // https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path
2004
2043
  static std::string GetBaseFilename(const std::string &filepath) {
2005
- 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;
2006
2048
  }
2007
2049
 
2008
2050
  std::string base64_encode(unsigned char const *, unsigned int len);
@@ -2148,47 +2190,35 @@ std::string base64_decode(std::string const &encoded_string) {
2148
2190
  // TODO(syoyo): Use uriparser https://uriparser.github.io/ for stricter Uri
2149
2191
  // decoding?
2150
2192
  //
2151
- // https://stackoverflow.com/questions/18307429/encode-decode-url-in-c
2193
+ // Uri Decoding from DLIB
2152
2194
  // http://dlib.net/dlib/server/server_http.cpp.html
2153
-
2154
- // --- dlib beign ------------------------------------------------------------
2195
+ // --- dlib begin ------------------------------------------------------------
2155
2196
  // Copyright (C) 2003 Davis E. King (davis@dlib.net)
2156
- // License: Boost Software License See LICENSE.txt for the full license.
2157
-
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
+ //
2158
2220
  namespace dlib {
2159
2221
 
2160
- #if 0
2161
- inline unsigned char to_hex( unsigned char x )
2162
- {
2163
- return x + (x > 9 ? ('A'-10) : '0');
2164
- }
2165
-
2166
- const std::string urlencode( const std::string& s )
2167
- {
2168
- std::ostringstream os;
2169
-
2170
- for ( std::string::const_iterator ci = s.begin(); ci != s.end(); ++ci )
2171
- {
2172
- if ( (*ci >= 'a' && *ci <= 'z') ||
2173
- (*ci >= 'A' && *ci <= 'Z') ||
2174
- (*ci >= '0' && *ci <= '9') )
2175
- { // allowed
2176
- os << *ci;
2177
- }
2178
- else if ( *ci == ' ')
2179
- {
2180
- os << '+';
2181
- }
2182
- else
2183
- {
2184
- os << '%' << to_hex(static_cast<unsigned char>(*ci >> 4)) << to_hex(static_cast<unsigned char>(*ci % 16));
2185
- }
2186
- }
2187
-
2188
- return os.str();
2189
- }
2190
- #endif
2191
-
2192
2222
  inline unsigned char from_hex(unsigned char ch) {
2193
2223
  if (ch <= '9' && ch >= '0')
2194
2224
  ch -= '0';
@@ -2297,22 +2327,40 @@ static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
2297
2327
  void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) {
2298
2328
  LoadImageData = func;
2299
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;
2300
2343
  }
2301
2344
 
2302
2345
  #ifndef TINYGLTF_NO_STB_IMAGE
2303
2346
  bool LoadImageData(Image *image, const int image_idx, std::string *err,
2304
2347
  std::string *warn, int req_width, int req_height,
2305
2348
  const unsigned char *bytes, int size, void *user_data) {
2306
- (void)user_data;
2307
2349
  (void)warn;
2308
2350
 
2351
+ LoadImageDataOption option;
2352
+ if (user_data) {
2353
+ option = *reinterpret_cast<LoadImageDataOption *>(user_data);
2354
+ }
2355
+
2309
2356
  int w = 0, h = 0, comp = 0, req_comp = 0;
2310
2357
 
2311
2358
  unsigned char *data = nullptr;
2312
2359
 
2313
- // force 32-bit textures for common Vulkan compatibility. It appears that
2314
- // some GPU drivers do not support 24-bit images for Vulkan
2315
- 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;
2316
2364
  int bits = 8;
2317
2365
  int pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
2318
2366
 
@@ -2384,13 +2432,18 @@ bool LoadImageData(Image *image, const int image_idx, std::string *err,
2384
2432
  }
2385
2433
  }
2386
2434
 
2435
+ if (req_comp != 0) {
2436
+ // loaded data has `req_comp` channels(components)
2437
+ comp = req_comp;
2438
+ }
2439
+
2387
2440
  image->width = w;
2388
2441
  image->height = h;
2389
- image->component = req_comp;
2442
+ image->component = comp;
2390
2443
  image->bits = bits;
2391
2444
  image->pixel_type = pixel_type;
2392
- image->image.resize(static_cast<size_t>(w * h * req_comp) * size_t(bits / 8));
2393
- 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());
2394
2447
  stbi_image_free(data);
2395
2448
 
2396
2449
  return true;
@@ -2566,7 +2619,7 @@ std::string ExpandFilePath(const std::string &filepath, void *) {
2566
2619
  #else
2567
2620
 
2568
2621
  #if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \
2569
- defined(__ANDROID__) || defined(__EMSCRIPTEN__)
2622
+ defined(__ANDROID__) || defined(__EMSCRIPTEN__) || defined(__OpenBSD__)
2570
2623
  // no expansion
2571
2624
  std::string s = filepath;
2572
2625
  #else
@@ -3139,6 +3192,7 @@ static bool ParseJsonAsValue(Value *ret, const json &o) {
3139
3192
  break;
3140
3193
  case json::value_t::null:
3141
3194
  case json::value_t::discarded:
3195
+ case json::value_t::binary:
3142
3196
  // default:
3143
3197
  break;
3144
3198
  }
@@ -4162,7 +4216,9 @@ static bool ParseSparseAccessor(Accessor *accessor, std::string *err,
4162
4216
  accessor->sparse.isSparse = true;
4163
4217
 
4164
4218
  int count = 0;
4165
- ParseIntegerProperty(&count, err, o, "count", true);
4219
+ if (!ParseIntegerProperty(&count, err, o, "count", true, "SparseAccessor")) {
4220
+ return false;
4221
+ }
4166
4222
 
4167
4223
  json_const_iterator indices_iterator;
4168
4224
  json_const_iterator values_iterator;
@@ -4180,18 +4236,24 @@ static bool ParseSparseAccessor(Accessor *accessor, std::string *err,
4180
4236
  const json &values_obj = GetValue(values_iterator);
4181
4237
 
4182
4238
  int indices_buffer_view = 0, indices_byte_offset = 0, component_type = 0;
4183
- ParseIntegerProperty(&indices_buffer_view, err, indices_obj, "bufferView",
4184
- true);
4239
+ if (!ParseIntegerProperty(&indices_buffer_view, err, indices_obj, "bufferView",
4240
+ true, "SparseAccessor")) {
4241
+ return false;
4242
+ }
4185
4243
  ParseIntegerProperty(&indices_byte_offset, err, indices_obj, "byteOffset",
4186
- true);
4187
- ParseIntegerProperty(&component_type, err, indices_obj, "componentType",
4188
- true);
4244
+ false);
4245
+ if (!ParseIntegerProperty(&component_type, err, indices_obj, "componentType",
4246
+ true, "SparseAccessor")) {
4247
+ return false;
4248
+ }
4189
4249
 
4190
4250
  int values_buffer_view = 0, values_byte_offset = 0;
4191
- ParseIntegerProperty(&values_buffer_view, err, values_obj, "bufferView",
4192
- true);
4251
+ if (!ParseIntegerProperty(&values_buffer_view, err, values_obj, "bufferView",
4252
+ true, "SparseAccessor")) {
4253
+ return false;
4254
+ }
4193
4255
  ParseIntegerProperty(&values_byte_offset, err, values_obj, "byteOffset",
4194
- true);
4256
+ false);
4195
4257
 
4196
4258
  accessor->sparse.count = count;
4197
4259
  accessor->sparse.indices.bufferView = indices_buffer_view;
@@ -4200,8 +4262,6 @@ static bool ParseSparseAccessor(Accessor *accessor, std::string *err,
4200
4262
  accessor->sparse.values.bufferView = values_buffer_view;
4201
4263
  accessor->sparse.values.byteOffset = values_byte_offset;
4202
4264
 
4203
- // todo check theses values
4204
-
4205
4265
  return true;
4206
4266
  }
4207
4267
 
@@ -4409,6 +4469,7 @@ static bool GetAttributeForAllPoints(uint32_t componentType, draco::Mesh *mesh,
4409
4469
  static bool ParseDracoExtension(Primitive *primitive, Model *model,
4410
4470
  std::string *err,
4411
4471
  const Value &dracoExtensionValue) {
4472
+ (void)err;
4412
4473
  auto bufferViewValue = dracoExtensionValue.Get("bufferView");
4413
4474
  if (!bufferViewValue.IsInt()) return false;
4414
4475
  auto attributesValue = dracoExtensionValue.Get("attributes");
@@ -4469,7 +4530,6 @@ static bool ParseDracoExtension(Primitive *primitive, Model *model,
4469
4530
 
4470
4531
  int dracoAttributeIndex = attribute.second.Get<int>();
4471
4532
  const auto pAttribute = mesh->GetAttributeByUniqueId(dracoAttributeIndex);
4472
- const auto pBuffer = pAttribute->buffer();
4473
4533
  const auto componentType =
4474
4534
  model->accessors[primitiveAttribute->second].componentType;
4475
4535
 
@@ -5028,12 +5088,12 @@ static bool ParseSampler(Sampler *sampler, std::string *err, const json &o,
5028
5088
  int magFilter = -1;
5029
5089
  int wrapS = TINYGLTF_TEXTURE_WRAP_REPEAT;
5030
5090
  int wrapT = TINYGLTF_TEXTURE_WRAP_REPEAT;
5031
- int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT;
5091
+ //int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT;
5032
5092
  ParseIntegerProperty(&minFilter, err, o, "minFilter", false);
5033
5093
  ParseIntegerProperty(&magFilter, err, o, "magFilter", false);
5034
5094
  ParseIntegerProperty(&wrapS, err, o, "wrapS", false);
5035
5095
  ParseIntegerProperty(&wrapT, err, o, "wrapT", false);
5036
- ParseIntegerProperty(&wrapR, err, o, "wrapR", false); // tinygltf extension
5096
+ //ParseIntegerProperty(&wrapR, err, o, "wrapR", false); // tinygltf extension
5037
5097
 
5038
5098
  // TODO(syoyo): Check the value is alloed one.
5039
5099
  // (e.g. we allow 9728(NEAREST), but don't allow 9727)
@@ -5042,7 +5102,7 @@ static bool ParseSampler(Sampler *sampler, std::string *err, const json &o,
5042
5102
  sampler->magFilter = magFilter;
5043
5103
  sampler->wrapS = wrapS;
5044
5104
  sampler->wrapT = wrapT;
5045
- sampler->wrapR = wrapR;
5105
+ //sampler->wrapR = wrapR;
5046
5106
 
5047
5107
  ParseExtensionsProperty(&(sampler->extensions), err, o);
5048
5108
  ParseExtrasProperty(&(sampler->extras), o);
@@ -5769,13 +5829,13 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
5769
5829
  {
5770
5830
  json_const_iterator it;
5771
5831
  if (FindMember(o, "extensions", it)) {
5772
- model->extensions_json_string = JsonToString(GetValue(it));
5832
+ scene.extensions_json_string = JsonToString(GetValue(it));
5773
5833
  }
5774
5834
  }
5775
5835
  {
5776
5836
  json_const_iterator it;
5777
5837
  if (FindMember(o, "extras", it)) {
5778
- model->extras_json_string = JsonToString(GetValue(it));
5838
+ scene.extras_json_string = JsonToString(GetValue(it));
5779
5839
  }
5780
5840
  }
5781
5841
  }
@@ -5825,6 +5885,18 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
5825
5885
  }
5826
5886
 
5827
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
+
5828
5900
  {
5829
5901
  int idx = 0;
5830
5902
  bool success = ForEachInArray(v, "images", [&](const json &o) {
@@ -5837,7 +5909,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
5837
5909
  Image image;
5838
5910
  if (!ParseImage(&image, idx, err, warn, o,
5839
5911
  store_original_json_for_extras_and_extensions_, base_dir,
5840
- &fs, &this->LoadImageData, load_image_user_data_)) {
5912
+ &fs, &this->LoadImageData, load_image_user_data)) {
5841
5913
  return false;
5842
5914
  }
5843
5915
 
@@ -5875,7 +5947,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
5875
5947
  bool ret = LoadImageData(
5876
5948
  &image, idx, err, warn, image.width, image.height,
5877
5949
  &buffer.data[bufferView.byteOffset],
5878
- static_cast<int>(bufferView.byteLength), load_image_user_data_);
5950
+ static_cast<int>(bufferView.byteLength), load_image_user_data);
5879
5951
  if (!ret) {
5880
5952
  return false;
5881
5953
  }
@@ -6551,8 +6623,33 @@ static void SerializeGltfAccessor(Accessor &accessor, json &o) {
6551
6623
 
6552
6624
  SerializeNumberProperty<int>("componentType", accessor.componentType, o);
6553
6625
  SerializeNumberProperty<size_t>("count", accessor.count, o);
6554
- SerializeNumberArrayProperty<double>("min", accessor.minValues, o);
6555
- 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
+
6556
6653
  if (accessor.normalized)
6557
6654
  SerializeValue("normalized", Value(accessor.normalized), o);
6558
6655
  std::string type;
@@ -6662,10 +6759,15 @@ static void SerializeGltfAsset(Asset &asset, json &o) {
6662
6759
  SerializeStringProperty("copyright", asset.copyright, o);
6663
6760
  }
6664
6761
 
6665
- if (!asset.version.empty()) {
6666
- SerializeStringProperty("version", asset.version, o);
6762
+ if (asset.version.empty()) {
6763
+ // Just in case
6764
+ // `version` must be defined
6765
+ asset.version = "2.0";
6667
6766
  }
6668
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
+
6669
6771
  if (asset.extras.Keys().size()) {
6670
6772
  SerializeValue("extras", asset.extras, o);
6671
6773
  }
@@ -7063,7 +7165,7 @@ static void SerializeGltfSampler(Sampler &sampler, json &o) {
7063
7165
  if (sampler.minFilter != -1) {
7064
7166
  SerializeNumberProperty("minFilter", sampler.minFilter, o);
7065
7167
  }
7066
- SerializeNumberProperty("wrapR", sampler.wrapR, o);
7168
+ //SerializeNumberProperty("wrapR", sampler.wrapR, o);
7067
7169
  SerializeNumberProperty("wrapS", sampler.wrapS, o);
7068
7170
  SerializeNumberProperty("wrapT", sampler.wrapT, o);
7069
7171
 
@@ -7138,11 +7240,17 @@ static void SerializeGltfScene(Scene &scene, json &o) {
7138
7240
  }
7139
7241
 
7140
7242
  static void SerializeGltfSkin(Skin &skin, json &o) {
7141
- if (skin.inverseBindMatrices != -1)
7243
+ // required
7244
+ SerializeNumberArrayProperty<int>("joints", skin.joints, o);
7245
+
7246
+ if (skin.inverseBindMatrices >= 0) {
7142
7247
  SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o);
7248
+ }
7249
+
7250
+ if (skin.skeleton >= 0) {
7251
+ SerializeNumberProperty("skeleton", skin.skeleton, o);
7252
+ }
7143
7253
 
7144
- SerializeNumberArrayProperty<int>("joints", skin.joints, o);
7145
- SerializeNumberProperty("skeleton", skin.skeleton, o);
7146
7254
  if (skin.name.size()) {
7147
7255
  SerializeStringProperty("name", skin.name, o);
7148
7256
  }
@@ -7225,6 +7333,16 @@ static void SerializeGltfModel(Model *model, json &o) {
7225
7333
  for (unsigned int i = 0; i < model->materials.size(); ++i) {
7226
7334
  json material;
7227
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
+ }
7228
7346
  JsonPushBack(materials, std::move(material));
7229
7347
  }
7230
7348
  JsonAddMember(o, "materials", std::move(materials));
@@ -7363,7 +7481,7 @@ static void SerializeGltfModel(Model *model, json &o) {
7363
7481
  }
7364
7482
 
7365
7483
  // Extensions used
7366
- if (model->extensionsUsed.size()) {
7484
+ if (extensionsUsed.size()) {
7367
7485
  SerializeStringArrayProperty("extensionsUsed", extensionsUsed, o);
7368
7486
  }
7369
7487
 
@@ -7407,31 +7525,24 @@ static void WriteBinaryGltfStream(std::ostream &stream,
7407
7525
  const std::string header = "glTF";
7408
7526
  const int version = 2;
7409
7527
 
7410
- // https://stackoverflow.com/questions/3407012/c-rounding-up-to-the-nearest-multiple-of-a-number
7411
- auto roundUp = [](uint32_t numToRound, uint32_t multiple) {
7412
- if (multiple == 0) return numToRound;
7413
-
7414
- uint32_t remainder = numToRound % multiple;
7415
- if (remainder == 0) return numToRound;
7416
-
7417
- return numToRound + multiple - remainder;
7418
- };
7419
-
7420
- const uint32_t padding_size =
7421
- roundUp(uint32_t(content.size()), 4) - uint32_t(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;
7422
7533
 
7423
7534
  // 12 bytes for header, JSON content length, 8 bytes for JSON chunk info.
7424
- // Chunk data must be located at 4-byte boundary.
7535
+ // Chunk data must be located at 4-byte boundary, which may require padding
7425
7536
  const uint32_t length =
7426
- 12 + 8 + roundUp(uint32_t(content.size()), 4) +
7427
- (binBuffer.size() ? (8 + roundUp(uint32_t(binBuffer.size()), 4)) : 0);
7537
+ 12 + 8 + content_size + content_padding_size +
7538
+ (binBuffer_size ? (8 + binBuffer_size + bin_padding_size) : 0);
7428
7539
 
7429
7540
  stream.write(header.c_str(), std::streamsize(header.size()));
7430
7541
  stream.write(reinterpret_cast<const char *>(&version), sizeof(version));
7431
7542
  stream.write(reinterpret_cast<const char *>(&length), sizeof(length));
7432
7543
 
7433
7544
  // JSON chunk info, then JSON data
7434
- const uint32_t model_length = uint32_t(content.size()) + padding_size;
7545
+ const uint32_t model_length = uint32_t(content.size()) + content_padding_size;
7435
7546
  const uint32_t model_format = 0x4E4F534A;
7436
7547
  stream.write(reinterpret_cast<const char *>(&model_length),
7437
7548
  sizeof(model_length));
@@ -7440,13 +7551,11 @@ static void WriteBinaryGltfStream(std::ostream &stream,
7440
7551
  stream.write(content.c_str(), std::streamsize(content.size()));
7441
7552
 
7442
7553
  // Chunk must be multiplies of 4, so pad with spaces
7443
- if (padding_size > 0) {
7444
- 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), ' ');
7445
7556
  stream.write(padding.c_str(), std::streamsize(padding.size()));
7446
7557
  }
7447
7558
  if (binBuffer.size() > 0) {
7448
- const uint32_t bin_padding_size =
7449
- roundUp(uint32_t(binBuffer.size()), 4) - uint32_t(binBuffer.size());
7450
7559
  // BIN chunk info, then BIN data
7451
7560
  const uint32_t bin_length = uint32_t(binBuffer.size()) + bin_padding_size;
7452
7561
  const uint32_t bin_format = 0x004e4942;