Merge branch 'separate-serializer' into sajson
diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml
new file mode 100644
index 0000000..9424b6d
--- /dev/null
+++ b/.github/workflows/c-cpp.yml
@@ -0,0 +1,165 @@
+name: C/C++ CI
+
+on: [push, pull_request]
+
+jobs:
+
+  # compile with older gcc4.8
+  build-gcc48:
+
+    runs-on: ubuntu-18.04
+    name: Build with gcc 4.8
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v1
+
+      - name: Build
+        run: |
+            sudo apt-get update
+            sudo apt-get install -y build-essential
+            sudo apt-get install -y gcc-4.8 g++-4.8
+            g++-4.8 -std=c++11 -o loader_example loader_example.cc
+
+      - name: NoexceptBuild
+        run: |
+            g++-4.8 -DTINYGLTF_NOEXCEPTION -std=c++11 -o loader_example loader_example.cc
+
+      - name: RapidjsonBuild
+        run: |
+            git clone https://github.com/Tencent/rapidjson
+            g++-4.8 -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -o loader_example loader_example.cc
+
+  # compile with mingw gcc cross
+  build-mingw-cross:
+
+    runs-on: ubuntu-18.04
+    name: Build with MinGW gcc cross
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v1
+
+      - name: Build
+        run: |
+            sudo apt-get update
+            sudo apt-get install -y build-essential
+            sudo apt-get install -y mingw-w64
+            x86_64-w64-mingw32-g++ -std=c++11 -o loader_example loader_example.cc
+
+  # Windows(x64) + Visual Studio 2019 build
+  build-windows-msvc:
+
+    runs-on: windows-latest
+    name: Build for Windows(x64, MSVC)
+
+    # Use system installed cmake
+    # https://help.github.com/en/actions/reference/software-installed-on-github-hosted-runners
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v1
+      - name: Configure
+        run: |
+          mkdir build
+          cd build
+          cmake -G "Visual Studio 16 2019" -DTINYGLTF_BUILD_LOADER_EXAMPLE=On -DTINYGLTF_BUILD_GL_EXAMPLES=Off -DTINYGLTF_BUILD_VALIDATOR_EXAMPLE=On ..
+          cd ..
+      - name: Build
+        run: cmake --build build --config Release
+
+
+  build-linux:
+
+    runs-on: ubuntu-latest
+    name: Buld with gcc
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: build
+      run: |
+        g++ -std=c++11 -o loader_example loader_example.cc
+    - name: test
+      run: |
+        ./loader_example models/Cube/Cube.gltf
+
+    - name: tests
+      run: |
+        cd tests
+        g++ -I../  -std=c++11 -g -O0 -o tester tester.cc
+        ./tester
+        cd ..
+
+    - name: noexcept_tests
+      run: |
+        cd tests
+        g++ -DTINYGLTF_NOEXCEPTION -I../  -std=c++11 -g -O0 -o tester_noexcept tester.cc
+        ./tester_noexcept
+        cd ..
+
+
+  build-rapidjson-linux:
+
+    runs-on: ubuntu-latest
+    name: Buld with gcc + rapidjson
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: build
+      run: |
+        git clone https://github.com/Tencent/rapidjson
+        g++ -v
+        g++ -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -o loader_example loader_example.cc
+
+    - name: loader_example_test
+      run: |
+        ./loader_example models/Cube/Cube.gltf
+
+    - name: tests
+      run: |
+        cd tests
+        g++ -DTINYGLTF_USE_RAPIDJSON -I../rapidjson/include/rapidjson -I../  -std=c++11 -g -O0 -o tester tester.cc
+        ./tester
+        cd ..
+
+    - name: noexcept_tests
+      run: |
+        cd tests
+        g++ -DTINYGLTF_USE_RAPIDJSON -I../rapidjson/include/rapidjson -DTINYGLTF_NOEXCEPTION -I../  -std=c++11 -g -O0 -o tester_noexcept tester.cc
+        ./tester_noexcept
+        cd ..
+
+  # Cross-compile for aarch64 linux target
+  build-cross-aarch64:
+
+    runs-on: ubuntu-18.04
+    name: Build on cross aarch64
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v1
+      - name: Build
+        run: |
+            sudo apt-get update
+            sudo apt-get install -y build-essential
+            sudo apt-get install -y gcc-8-aarch64-linux-gnu g++-8-aarch64-linux-gnu
+
+            git clone https://github.com/Tencent/rapidjson
+            aarch64-linux-gnu-g++-8 -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -g -O0 -o loader_example loader_example.cc
+
+  # macOS clang
+  build-macos:
+
+    runs-on: macos-latest
+    name: Build on macOS
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v1
+      - name: Build
+        run: |
+            clang++ -std=c++11 -g -O0 -o loader_example loader_example.cc
+            ./loader_example models/Cube/Cube.gltf
+
+            git clone https://github.com/Tencent/rapidjson
+            clang++ -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -g -O0 -o loader_example loader_example.cc
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e7864e5..30cc854 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,18 +4,24 @@
 
 SET(CMAKE_CXX_STANDARD 11)
 
-option(TINYGLTF_BUILD_EXAMPLES "Build examples" ON)
+option(TINYGLTF_BUILD_LOADER_EXAMPLE "Build loader_example" ON)
+option(TINYGLTF_BUILD_GL_EXAMPLES "Build GL exampels(requires glfw, OpenGL, etc)" OFF)
+option(TINYGLTF_BUILD_VALIDATOR_EXAMPLE "Build validator exampe" OFF)
 
-if (TINYGLTF_BUILD_EXAMPLES)
+if (TINYGLTF_BUILD_LOADER_EXAMPLE)
   ADD_EXECUTABLE ( loader_example
     loader_example.cc
     )
+endif (TINYGLTF_BUILD_LOADER_EXAMPLE)
 
+if (TINYGLTF_BUILD_GL_EXAMPLES)
   ADD_SUBDIRECTORY ( examples/gltfutil )
   ADD_SUBDIRECTORY ( examples/glview )
-  ADD_SUBDIRECTORY ( examples/validator )
-endif (TINYGLTF_BUILD_EXAMPLES)
+endif (TINYGLTF_BUILD_GL_EXAMPLES)
 
+if (TINYGLTF_BUILD_VALIDATOR_EXAMPLE)
+  ADD_SUBDIRECTORY ( examples/validator )
+endif (TINYGLTF_BUILD_VALIDATOR_EXAMPLE)
 #
 # TinuGLTF is a header-only library, so no library build. just install header files.
 #
diff --git a/README.md b/README.md
index 2d9143d..0b9d4c5 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,8 @@
 
 [![Build status](https://ci.appveyor.com/api/projects/status/warngenu9wjjhlm8?svg=true)](https://ci.appveyor.com/project/syoyo/tinygltf)
 
+![C/C++ CI](https://github.com/syoyo/tinygltf/workflows/C/C++%20CI/badge.svg)
+
 ## Features
 
 * Written in portable C++. C++-11 with STL dependency only.
@@ -83,6 +85,7 @@
 * [QuickLook GLTF](https://github.com/toshiks/glTF-quicklook) - quicklook plugin for macos. Also SceneKit wrapper for tinygltf.
 * [GlslViewer](https://github.com/patriciogonzalezvivo/glslViewer) - live GLSL coding for MacOS and Linux
 * [Vulkan-Samples](https://github.com/KhronosGroup/Vulkan-Samples) - The Vulkan Samples is collection of resources to help you develop optimized Vulkan applications.
+* [TDME2](https://github.com/andreasdr/tdme2) - TDME2 - ThreeDeeMiniEngine2 is a lightweight 3D engine including tools suited for 3D game development using C++11
 * Your projects here! (Please send PR)
 
 ## TODOs
@@ -150,6 +153,7 @@
 
 ## Compile options
 
+* `TINYGLTF_ENABLE_SERIALIZER` : Enable glTF serialization feature.
 * `TINYGLTF_NOEXCEPTION` : Disable C++ exception in JSON parsing. You can use `-fno-exceptions` or by defining the symbol `JSON_NOEXCEPTION` and `TINYGLTF_NOEXCEPTION`  to fully remove C++ exception codes when compiling TinyGLTF.
 * `TINYGLTF_NO_STB_IMAGE` : Do not load images with stb_image. Instead use `TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)` to set a callback for loading images.
 * `TINYGLTF_NO_STB_IMAGE_WRITE` : Do not write images with stb_image_write. Instead use `TinyGLTF::SetImageWriter(WriteimageDataFunction WriteImageData, void *user_data)` to set a callback for writing images.
diff --git a/models/Extensions-overwrite-issue261/issue-261.bin b/models/Extensions-overwrite-issue261/issue-261.bin
new file mode 100644
index 0000000..60ed1b7
--- /dev/null
+++ b/models/Extensions-overwrite-issue261/issue-261.bin
Binary files differ
diff --git a/models/Extensions-overwrite-issue261/issue-261.gltf b/models/Extensions-overwrite-issue261/issue-261.gltf
new file mode 100644
index 0000000..7dd85e2
--- /dev/null
+++ b/models/Extensions-overwrite-issue261/issue-261.gltf
@@ -0,0 +1,376 @@
+{
+  "accessors": [
+    {
+      "bufferView": 0,
+      "componentType": 5126,
+      "count": 8,
+      "max": [
+        0.5,
+        0.5,
+        0.5
+      ],
+      "min": [
+        -0.5,
+        -0.5,
+        -0.5
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 1,
+      "componentType": 5125,
+      "count": 36,
+      "type": "SCALAR"
+    }
+  ],
+  "asset": {
+    "copyright": "NVIDIA Corporation",
+    "generator": "Iray glTF plugin",
+    "version": "2.0"
+  },
+  "bufferViews": [
+    {
+      "buffer": 0,
+      "byteLength": 96,
+      "byteStride": 12
+    },
+    {
+      "buffer": 0,
+      "byteLength": 144,
+      "byteOffset": 96
+    }
+  ],
+  "buffers": [
+    {
+      "byteLength": 240,
+      "uri": "issue-261.bin"
+    }
+  ],
+  "cameras": [
+    {
+      "extensions": {
+        "NV_Iray": {
+          "mip_burn_highlights": 0.699999988079071,
+          "mip_burn_highlights_per_component": false,
+          "mip_camera_shutter": 4.0,
+          "mip_cm2_factor": 1.0,
+          "mip_crush_blacks": 0.5,
+          "mip_f_number": 1.0,
+          "mip_film_iso": 100.0,
+          "mip_gamma": 2.200000047683716,
+          "mip_saturation": 1.0,
+          "mip_vignetting": 0.00019999999494757503,
+          "mip_whitepoint": [
+            1.0,
+            1.0,
+            1.0,
+            1.0
+          ],
+          "tm_enable_tonemapper": true,
+          "tm_tonemapper": "mia_exposure_photographic"
+        }
+      },
+      "extras": {
+        "resolution": [
+          640,
+          480
+        ]
+      },
+      "name": "default",
+      "perspective": {
+        "aspectRatio": 1.3333333730697632,
+        "yfov": 0.9272952079772949,
+        "zfar": 1000.0,
+        "znear": 0.1
+      },
+      "type": "perspective"
+    }
+  ],
+  "extensions": {
+    "KHR_lights_punctual": {
+      "lights": [
+        {
+          "color": [
+            1.0,
+            1.0,
+            1.0
+          ],
+          "intensity": 1000.0,
+          "name": "light",
+          "type": "point"
+        }
+      ]
+    },
+    "NV_MDL": {
+      "modules": [
+        "mdl::base",
+        "mdl::nvidia::core_definitions",
+        "mdl::state"
+      ],
+      "shaders": [
+        {
+          "definition": "mdl::base::environment_spherical(texture_2d)",
+          "name": "env_shd"
+        },
+        {
+          "arguments": {
+            "base_color": [
+              0.0055217444896698,
+              0.36859095096588135,
+              0.0041161770932376385
+            ],
+            "normal=": 2
+          },
+          "definition": "mdl::nvidia::core_definitions::flex_material",
+          "name": "cube_instance_material"
+        },
+        {
+          "definition": "mdl::state::normal()",
+          "name": "mdl::state::normal_341"
+        },
+        {
+          "arguments": {
+            "base_color": [
+              0.0055217444896698,
+              0.36859095096588135,
+              0.0041161770932376385
+            ],
+            "normal=": 4
+          },
+          "definition": "mdl::nvidia::core_definitions::flex_material",
+          "name": "cube_instance_material"
+        },
+        {
+          "definition": "mdl::state::normal()",
+          "name": "mdl::state::normal_341"
+        }
+      ]
+    }
+  },
+  "extensionsUsed": [
+    "NV_MDL",
+    "NV_Iray",
+    "KHR_lights_punctual"
+  ],
+  "materials": [
+    {
+      "doubleSided": true,
+      "extras": {
+        "mdl_shader": 1
+      },
+      "name": "cube_instance_material",
+      "pbrMetallicRoughness": {
+        "baseColorFactor": [
+          0.0055217444896698,
+          0.36859095096588135,
+          0.0041161770932376385,
+          1.0
+        ]
+      }
+    }
+  ],
+  "meshes": [
+    {
+      "name": "cube",
+      "primitives": [
+        {
+          "attributes": {
+            "POSITION": 0
+          },
+          "indices": 1,
+          "material": 0,
+          "mode": 4
+        }
+      ]
+    }
+  ],
+  "nodes": [
+    {
+      "camera": 0,
+      "extensions": {
+        "NV_Iray": {
+          "iview:fkey": -1,
+          "iview:fov": 53.130104064941406,
+          "iview:interest": [
+            0.1342654824256897,
+            -0.14356137812137604,
+            0.037264324724674225
+          ],
+          "iview:position": [
+            0.9729073643684387,
+            1.2592438459396362,
+            2.4199187755584717
+          ],
+          "iview:roll": 0.0,
+          "iview:up": [
+            0.0,
+            1.0,
+            0.0
+          ]
+        }
+      },
+      "matrix": [
+        0.9432751389105357,
+        -1.1769874756875739e-16,
+        -0.3320120665176343,
+        0.0,
+        -0.16119596696756341,
+        0.8742297945345237,
+        -0.45797175303889276,
+        0.0,
+        0.290254840694694,
+        0.48551237507207207,
+        0.8246392308792816,
+        0.0,
+        0.9729073377709113,
+        1.2592438262175363,
+        2.419918748461627,
+        1.0
+      ],
+      "name": "CamInst"
+    },
+    {
+      "extensions": {
+        "NV_Iray": {
+          "caustic": true,
+          "caustic_cast": true,
+          "caustic_recv": true,
+          "face_back": true,
+          "face_front": true,
+          "finalgather": true,
+          "finalgather_cast": true,
+          "finalgather_recv": true,
+          "globillum": true,
+          "globillum_cast": true,
+          "globillum_recv": true,
+          "material=": 3,
+          "pickable": true,
+          "reflection_cast": true,
+          "reflection_recv": true,
+          "refraction_cast": true,
+          "refraction_recv": true,
+          "shadow_cast": true,
+          "shadow_recv": true,
+          "transparency_cast": true,
+          "transparency_recv": true,
+          "visible": true
+        }
+      },
+      "mesh": 0,
+      "name": "cube_instance"
+    },
+    {
+      "extensions": {
+        "KHR_lights_punctual": {
+          "light": 0
+        },
+        "NV_Iray": {
+          "shadow_cast": true,
+          "visible": false
+        }
+      },
+      "matrix": [
+        0.6988062355563571,
+        -7.76042172309776e-17,
+        -0.7153110128800992,
+        -0.0,
+        -0.4276881690736487,
+        0.8015668284138362,
+        -0.41781987700564616,
+        -0.0,
+        0.57336957992379,
+        0.5979051928078428,
+        0.5601398979107212,
+        0.0,
+        2.3640632834071384,
+        2.465226204472449,
+        2.309515908392224,
+        1.0
+      ],
+      "name": "light_inst"
+    }
+  ],
+  "scene": 0,
+  "scenes": [
+    {
+      "extensions": {
+        "NV_Iray": {
+          "CP_canny_threshold1": 40,
+          "CP_canny_threshold2": 120,
+          "CP_color_quantization": 8,
+          "IVP_color": [
+            1.0,
+            0.0,
+            0.0,
+            1.0
+          ],
+          "TM_drago_bias": 0.8500000238418579,
+          "TM_drago_gamma2": 2.200000047683716,
+          "TM_drago_saturation": 1.0,
+          "TM_durand_contrast": 4.0,
+          "TM_durand_gamma": 2.200000047683716,
+          "TM_durand_saturation": 1.0,
+          "TM_durand_sigma_color": 2.0,
+          "TM_durand_sigma_space": 2.0,
+          "TM_linear_gamma": 2.200000047683716,
+          "TM_reinhard_color_adapt": 0.8999999761581421,
+          "TM_reinhard_gamma": 1.0,
+          "TM_reinhard_intensity": 0.0,
+          "TM_reinhard_light_adapt": 1.0,
+          "TM_reye_Ywhite": 1000000.0,
+          "TM_reye_bsize": 2,
+          "TM_reye_bthres": 3.0,
+          "TM_reye_gamma": 2.200000047683716,
+          "TM_reye_key": 0.5,
+          "TM_reye_saturation": 1.0,
+          "TM_reye_whitebalance": [
+            1.0,
+            0.9965101480484009,
+            0.9805564880371094,
+            0.0
+          ],
+          "environment_dome_depth": 200.0,
+          "environment_dome_height": 200.0,
+          "environment_dome_mode": "infinite",
+          "environment_dome_position": [
+            0.0,
+            0.0,
+            0.0
+          ],
+          "environment_dome_radius": 100.0,
+          "environment_dome_rotation_angle": 0.0,
+          "environment_dome_width": 200.0,
+          "environment_function=": 0,
+          "environment_function_intensity": 1.9900000095367432,
+          "iray_instancing": "off",
+          "iview::inline_color": [
+            1.0,
+            0.0,
+            0.0,
+            1.0
+          ],
+          "iview::inline_width": 1.0,
+          "iview::magnifier_size": 300,
+          "iview::offset": 10.0,
+          "iview::outline_color": [
+            0.0,
+            0.0,
+            0.0,
+            1.0
+          ],
+          "iview::outline_width": 2.0,
+          "iview::overview": true,
+          "iview::zoom_factor": 1.0,
+          "samples": 4.0,
+          "shadow_cast": true,
+          "shadow_recv": true
+        }
+      },
+      "nodes": [
+        0,
+        1,
+        2
+      ]
+    }
+  ]
+}
diff --git a/tests/tester.cc b/tests/tester.cc
index c6d31b0..012b54d 100644
--- a/tests/tester.cc
+++ b/tests/tester.cc
@@ -93,6 +93,59 @@
 
 }
 
+TEST_CASE("extension-overwrite", "[issue-261]") {
+
+  tinygltf::Model model;
+  tinygltf::TinyGLTF ctx;
+  std::string err;
+  std::string warn;
+
+  bool ret = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/Extensions-overwrite-issue261/issue-261.gltf");
+  if (!err.empty()) {
+    std::cerr << err << std::endl;
+  }
+  REQUIRE(true == ret);
+
+  REQUIRE(model.extensionsUsed.size() == 3);
+  {
+    bool has_ext_lights = false;
+    has_ext_lights |= (model.extensionsUsed[0].compare("KHR_lights_punctual") == 0);
+    has_ext_lights |= (model.extensionsUsed[1].compare("KHR_lights_punctual") == 0);
+    has_ext_lights |= (model.extensionsUsed[2].compare("KHR_lights_punctual") == 0);
+
+    REQUIRE(true == has_ext_lights);
+  }
+
+  {
+    REQUIRE(model.extensions.size() == 2);
+    REQUIRE(model.extensions.count("NV_MDL"));
+    REQUIRE(model.extensions.count("KHR_lights_punctual"));
+  }
+
+  // TODO(syoyo): create temp directory.
+  {
+    ret = ctx.WriteGltfSceneToFile(&model, "issue-261.gltf", true, true);
+    REQUIRE(true == ret);
+
+    tinygltf::Model m;
+
+    // read back serialized glTF
+    bool ret = ctx.LoadASCIIFromFile(&m, &err, &warn, "issue-261.gltf");
+    if (!err.empty()) {
+      std::cerr << err << std::endl;
+    }
+    REQUIRE(true == ret);
+
+    REQUIRE(m.extensionsUsed.size() == 3);
+
+    REQUIRE(m.extensions.size() == 2);
+    REQUIRE(m.extensions.count("NV_MDL"));
+    REQUIRE(m.extensions.count("KHR_lights_punctual"));
+
+  }
+
+}
+
 TEST_CASE("invalid-primitive-indices", "[bounds-checking]") {
   tinygltf::Model model;
   tinygltf::TinyGLTF ctx;
@@ -359,3 +412,19 @@
   REQUIRE(true == ret);
 }
 
+#ifndef TINYGLTF_NO_FS
+TEST_CASE("expandpath-utf-8", "[pr-226]") {
+
+  std::string s1 = "\xe5\xaf\xb9"; // utf-8 string
+
+  std::string ret = tinygltf::ExpandFilePath(s1, /* userdata */nullptr);
+
+  // expected: E5 AF B9
+  REQUIRE(3 == ret.size());
+
+  REQUIRE(0xe5 == static_cast<uint8_t>(ret[0]));
+  REQUIRE(0xaf == static_cast<uint8_t>(ret[1]));
+  REQUIRE(0xb9 == static_cast<uint8_t>(ret[2]));
+
+}
+#endif
diff --git a/tiny_gltf.h b/tiny_gltf.h
index 262dbd3..d7592e1 100644
--- a/tiny_gltf.h
+++ b/tiny_gltf.h
@@ -27,6 +27,7 @@
 
 // Version:
 //  - v2.4.3 Experimental sajson(lightweight JSON parser) backend support.
+//           Introduce TINYGLTF_ENABLE_SERIALIZER.
 //  - v2.4.2 Decode percent-encoded URI.
 //  - v2.4.1 Fix some glTF object class does not have `extensions` and/or
 //  `extras` property.
@@ -54,6 +55,7 @@
 
 #include <array>
 #include <cassert>
+#include <cmath>  // std::fabs
 #include <cstdint>
 #include <cstdlib>
 #include <cstring>
@@ -61,7 +63,6 @@
 #include <map>
 #include <string>
 #include <vector>
-#include <cmath> // std::fabs
 
 #ifndef TINYGLTF_USE_CPP14
 #include <functional>
@@ -325,7 +326,8 @@
   }
 
   // Use this function if you want to have number value as int.
-  double GetNumberAsInt() const {
+  // TODO(syoyo): Support int value larger than 32 bits
+  int GetNumberAsInt() const {
     if (type_ == REAL_TYPE) {
       return int(real_value_);
     } else {
@@ -1116,9 +1118,9 @@
 struct Light {
   std::string name;
   std::vector<double> color;
-  double intensity;
+  double intensity{1.0};
   std::string type;
-  double range;
+  double range{0.0};  // 0.0 = inifinite
   SpotLight spot;
 
   Light() : intensity(1.0), range(0.0) {}
@@ -1250,7 +1252,13 @@
 
 bool FileExists(const std::string &abs_filename, void *);
 
-std::string ExpandFilePath(const std::string &filepath, void *);
+///
+/// Expand file path(e.g. `~` to home directory on posix, `%APPDATA%` to `C:\Users\tinygltf\AppData`)
+///
+/// @param[in] filepath File path string. Assume UTF-8
+/// @param[in] userdata User data. Set to `nullptr` if you don't need it.
+///
+std::string ExpandFilePath(const std::string &filepath, void *userdata);
 
 bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
                    const std::string &filepath, void *);
@@ -1318,6 +1326,8 @@
                             const std::string &base_dir = "",
                             unsigned int check_sections = REQUIRE_VERSION);
 
+#if defined(TINYGLTF_ENABLE_SERIALIZER)
+
   ///
   /// Write glTF to stream, buffers and images will be embeded
   ///
@@ -1331,16 +1341,22 @@
                             bool embedImages, bool embedBuffers,
                             bool prettyPrint, bool writeBinary);
 
+#endif
+
   ///
   /// Set callback to use for loading image data
   ///
   void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data);
 
+#if defined(TINYGLTF_ENABLE_SERIALIZER)
+
   ///
   /// Set callback to use for writing image data
   ///
   void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data);
 
+#endif
+
   ///
   /// Set callbacks to use for filesystem (fs) access and their user data
   ///
@@ -2420,11 +2436,15 @@
 }
 #endif
 
+#if defined(TINYGLTF_ENABLE_SERIALIZER)
+
 void TinyGLTF::SetImageWriter(WriteImageDataFunction func, void *user_data) {
   WriteImageData = func;
   write_image_user_data_ = user_data;
 }
 
+#endif
+
 #ifndef TINYGLTF_NO_STB_IMAGE_WRITE
 static void WriteToMemory_stbi(void *context, void *data, int size) {
   std::vector<unsigned char> *buffer =
@@ -2516,6 +2536,15 @@
                       (int)wstr.size());
   return wstr;
 }
+
+static inline std::string WcharToUTF8(const std::wstring &wstr) {
+  int str_size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(),
+                                      nullptr, 0, NULL, NULL);
+  std::string str(str_size, 0);
+  WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), &str[0],
+                      (int)str.size(), NULL, NULL);
+  return str;
+}
 #endif
 
 #ifndef TINYGLTF_NO_FS
@@ -2567,15 +2596,16 @@
 
 std::string ExpandFilePath(const std::string &filepath, void *) {
 #ifdef _WIN32
-  DWORD len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0);
-  char *str = new char[len];
-  ExpandEnvironmentStringsA(filepath.c_str(), str, len);
+  // Assume input `filepath` is encoded in UTF-8
+  std::wstring wfilepath = UTF8ToWchar(filepath);
+  DWORD wlen = ExpandEnvironmentStringsW(wfilepath.c_str(), nullptr, 0);
+  wchar_t *wstr = new wchar_t[wlen];
+  ExpandEnvironmentStringsW(wfilepath.c_str(), wstr, wlen);
 
-  std::string s(str);
+  std::wstring ws(wstr);
+  delete[] wstr;
+  return WcharToUTF8(ws);
 
-  delete[] str;
-
-  return s;
 #else
 
 #if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \
@@ -2651,9 +2681,11 @@
       _wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY);
   __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
   std::istream f(&wfile_buf);
-#elif defined(_MSC_VER)
+#elif defined(_MSC_VER) || defined(_LIBCPP_VERSION)
+  // For libcxx, assume _LIBCPP_HAS_OPEN_WITH_WCHAR is defined to accept `wchar_t *`
   std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary);
-#else  // clang?
+#else
+  // Unknown compiler/runtime
   std::ifstream f(filepath.c_str(), std::ifstream::binary);
 #endif
 #else
@@ -2695,12 +2727,10 @@
                     const std::vector<unsigned char> &contents, void *) {
 #ifdef _WIN32
 #if defined(__GLIBCXX__)  // mingw
-  int file_descriptor =
-      _wopen(UTF8ToWchar(filepath).c_str(), _O_CREAT | _O_WRONLY |
-                                            _O_TRUNC | _O_BINARY);
-  __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor,
-                                           std::ios_base::out |
-                                           std::ios_base::binary);
+  int file_descriptor = _wopen(UTF8ToWchar(filepath).c_str(),
+                               _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY);
+  __gnu_cxx::stdio_filebuf<char> wfile_buf(
+      file_descriptor, std::ios_base::out | std::ios_base::binary);
   std::ostream f(&wfile_buf);
 #elif defined(_MSC_VER)
   std::ofstream f(UTF8ToWchar(filepath).c_str(), std::ofstream::binary);
@@ -2755,10 +2785,9 @@
   if (image.uri.size()) {
     filename = GetBaseFilename(image.uri);
     ext = GetFilePathExtension(filename);
-  }
-  else if (image.bufferView != -1) {
-	  //If there's no URI and the data exists in a buffer,
-	  //don't change properties or write images
+  } else if (image.bufferView != -1) {
+    // If there's no URI and the data exists in a buffer,
+    // don't change properties or write images
   } else if (image.name.size()) {
     ext = MimeToExt(image.mimeType);
     // Otherwise use name as filename
@@ -2909,7 +2938,7 @@
   return false;
 #elif defined(TINYGLTF_USE_SAJSON)
   auto type = o.get_type();
-  
+
   if (type == sajson::TYPE_INTEGER) {
     val = static_cast<int>(o.get_number_value());
     return true;
@@ -2950,7 +2979,7 @@
   return false;
 #elif defined(TINYGLTF_USE_SAJSON)
   auto type = o.get_type();
-  
+
   if ((type == sajson::TYPE_DOUBLE) ||
       (type == sajson::TYPE_INTEGER)) {
     val = static_cast<double>(o.get_number_value());
@@ -2978,7 +3007,7 @@
   return false;
 #elif defined(TINYGLTF_USE_SAJSON)
   auto type = o.get_type();
-  
+
   if (type == sajson::TYPE_STRING) {
     val = o.as_string();
     return true;
@@ -4944,13 +4973,13 @@
       }
       return false;
     }
-	ParseExtensionsProperty(&channel->target_extensions, err, target_object);
-	if (store_original_json_for_extras_and_extensions) {
+    ParseExtensionsProperty(&channel->target_extensions, err, target_object);
+    if (store_original_json_for_extras_and_extensions) {
       json_const_iterator it;
       if (FindMember(target_object, "extensions", it)) {
         channel->target_extensions_json_string = JsonToString(GetValue(it));
       }
-	}
+    }
   }
 
   channel->sampler = samplerIndex;
@@ -5761,9 +5790,11 @@
             .target = TINYGLTF_TARGET_ARRAY_BUFFER;
       }
 
-      for(auto &target : primitive.targets) {
-        for(auto &attribute : target) {
-          model->bufferViews[size_t(model->accessors[size_t(attribute.second)].bufferView)]
+      for (auto &target : primitive.targets) {
+        for (auto &attribute : target) {
+          model
+              ->bufferViews[size_t(
+                  model->accessors[size_t(attribute.second)].bufferView)]
               .target = TINYGLTF_TARGET_ARRAY_BUFFER;
         }
       }
@@ -6273,6 +6304,8 @@
   return ret;
 }
 
+#if defined(TINYGLTF_ENABLE_SERIALIZER)
+
 ///////////////////////
 // GLTF Serialization
 ///////////////////////
@@ -6349,6 +6382,13 @@
   JsonAddMember(obj, key.c_str(), json(number));
 }
 
+#ifdef TINYGLTF_USE_RAPIDJSON
+template <>
+void SerializeNumberProperty(const std::string &key, size_t number, json &obj) {
+  JsonAddMember(obj, key.c_str(), json(static_cast<uint64_t>(number)));
+}
+#endif
+
 template <typename T>
 static void SerializeNumberArrayProperty(const std::string &key,
                                          const std::vector<T> &value,
@@ -6499,12 +6539,10 @@
                                     const std::string &binFilename) {
 #ifdef _WIN32
 #if defined(__GLIBCXX__)  // mingw
-  int file_descriptor =
-      _wopen(UTF8ToWchar(binFilename).c_str(), _O_CREAT | _O_WRONLY |
-                                               _O_TRUNC | _O_BINARY);
-  __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor,
-                                           std::ios_base::out |
-                                           std::ios_base::binary);
+  int file_descriptor = _wopen(UTF8ToWchar(binFilename).c_str(),
+                               _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY);
+  __gnu_cxx::stdio_filebuf<char> wfile_buf(
+      file_descriptor, std::ios_base::out | std::ios_base::binary);
   std::ostream output(&wfile_buf);
   if (!wfile_buf.is_open()) return false;
 #elif defined(_MSC_VER)
@@ -6639,7 +6677,7 @@
     SerializeNumberProperty("node", channel.target_node, target);
     SerializeStringProperty("path", channel.target_path, target);
 
-	SerializeExtensionMap(channel.target_extensions, target);
+    SerializeExtensionMap(channel.target_extensions, target);
 
     JsonAddMember(o, "target", std::move(target));
   }
@@ -7046,7 +7084,9 @@
 static void SerializeGltfLight(Light &light, json &o) {
   if (!light.name.empty()) SerializeStringProperty("name", light.name, o);
   SerializeNumberProperty("intensity", light.intensity, o);
-  SerializeNumberProperty("range", light.range, o);
+  if (light.range > 0.0) {
+    SerializeNumberProperty("range", light.range, o);
+  }
   SerializeNumberArrayProperty("color", light.color, o);
   SerializeStringProperty("type", light.type, o);
   if (light.type == "spot") {
@@ -7160,6 +7200,11 @@
   } else {
     // ???
   }
+
+  if (camera.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", camera.extras, o);
+  }
+  SerializeExtensionMap(camera.extensions, o);
 }
 
 static void SerializeGltfScene(Scene &scene, json &o) {
@@ -7238,7 +7283,7 @@
   JsonAddMember(o, "asset", std::move(asset));
 
   // BUFFERVIEWS
-  if(model->bufferViews.size()) {
+  if (model->bufferViews.size()) {
     json bufferViews;
     JsonReserveArray(bufferViews, model->bufferViews.size());
     for (unsigned int i = 0; i < model->bufferViews.size(); ++i) {
@@ -7249,11 +7294,6 @@
     JsonAddMember(o, "bufferViews", std::move(bufferViews));
   }
 
-  // Extensions used
-  if (model->extensionsUsed.size()) {
-    SerializeStringArrayProperty("extensionsUsed", model->extensionsUsed, o);
-  }
-
   // Extensions required
   if (model->extensionsRequired.size()) {
     SerializeStringArrayProperty("extensionsRequired",
@@ -7364,7 +7404,9 @@
   // EXTENSIONS
   SerializeExtensionMap(model->extensions, o);
 
-  // LIGHTS as KHR_lights_cmn
+  auto extensionsUsed = model->extensionsUsed;
+
+  // LIGHTS as KHR_lights_punctual
   if (model->lights.size()) {
     json lights;
     JsonReserveArray(lights, model->lights.size());
@@ -7379,7 +7421,7 @@
 
     {
       json_const_iterator it;
-      if (!FindMember(o, "extensions", it)) {
+      if (FindMember(o, "extensions", it)) {
         JsonAssign(ext_j, GetValue(it));
       }
     }
@@ -7387,6 +7429,23 @@
     JsonAddMember(ext_j, "KHR_lights_punctual", std::move(khr_lights_cmn));
 
     JsonAddMember(o, "extensions", std::move(ext_j));
+
+    // Also add "KHR_lights_punctual" to `extensionsUsed`
+    {
+      auto has_khr_lights_punctual = std::find_if(
+          extensionsUsed.begin(), extensionsUsed.end(), [](const std::string &s) {
+                         return (s.compare("KHR_lights_punctual") == 0);
+                       });
+
+      if (has_khr_lights_punctual == extensionsUsed.end()) {
+        extensionsUsed.push_back("KHR_lights_punctual");
+      }
+    }
+  }
+
+  // Extensions used
+  if (model->extensionsUsed.size()) {
+    SerializeStringArrayProperty("extensionsUsed", extensionsUsed, o);
   }
 
   // EXTRAS
@@ -7406,12 +7465,10 @@
 #if defined(_MSC_VER)
   std::ofstream gltfFile(UTF8ToWchar(output).c_str());
 #elif defined(__GLIBCXX__)
-  int file_descriptor =
-      _wopen(UTF8ToWchar(output).c_str(), _O_CREAT | _O_WRONLY |
-                                          _O_TRUNC | _O_BINARY);
-  __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor,
-                                           std::ios_base::out |
-                                           std::ios_base::binary);
+  int file_descriptor = _wopen(UTF8ToWchar(output).c_str(),
+                               _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY);
+  __gnu_cxx::stdio_filebuf<char> wfile_buf(
+      file_descriptor, std::ios_base::out | std::ios_base::binary);
   std::ostream gltfFile(&wfile_buf);
   if (!wfile_buf.is_open()) return false;
 #else
@@ -7497,12 +7554,10 @@
 #if defined(_MSC_VER)
   std::ofstream gltfFile(UTF8ToWchar(output).c_str(), std::ios::binary);
 #elif defined(__GLIBCXX__)
-  int file_descriptor =
-      _wopen(UTF8ToWchar(output).c_str(), _O_CREAT | _O_WRONLY |
-                                          _O_TRUNC | _O_BINARY);
-  __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor,
-                                           std::ios_base::out |
-                                           std::ios_base::binary);
+  int file_descriptor = _wopen(UTF8ToWchar(output).c_str(),
+                               _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY);
+  __gnu_cxx::stdio_filebuf<char> wfile_buf(
+      file_descriptor, std::ios_base::out | std::ios_base::binary);
   std::ostream gltfFile(&wfile_buf);
 #else
   std::ofstream gltfFile(output.c_str(), std::ios::binary);
@@ -7523,13 +7578,13 @@
 
   // BUFFERS
   std::vector<unsigned char> binBuffer;
-  if(model->buffers.size()) {
+  if (model->buffers.size()) {
     json buffers;
     JsonReserveArray(buffers, model->buffers.size());
     for (unsigned int i = 0; i < model->buffers.size(); ++i) {
       json buffer;
-      if (writeBinary && i==0 && model->buffers[i].uri.empty()){
-        SerializeGltfBufferBin(model->buffers[i], buffer,binBuffer);
+      if (writeBinary && i == 0 && model->buffers[i].uri.empty()) {
+        SerializeGltfBufferBin(model->buffers[i], buffer, binBuffer);
       } else {
         SerializeGltfBuffer(model->buffers[i], buffer);
       }
@@ -7546,10 +7601,11 @@
       json image;
 
       std::string dummystring = "";
-	  // UpdateImageObject need baseDir but only uses it if embeddedImages is
-	  // enabled, since we won't write separate images when writing to a stream we
-	  UpdateImageObject(model->images[i], dummystring, int(i), false,
-		  &this->WriteImageData, this->write_image_user_data_);
+      // UpdateImageObject need baseDir but only uses it if embeddedImages is
+      // enabled, since we won't write separate images when writing to a stream
+      // we
+      UpdateImageObject(model->images[i], dummystring, int(i), false,
+                        &this->WriteImageData, this->write_image_user_data_);
       SerializeGltfImage(model->images[i], image);
       JsonPushBack(images, std::move(image));
     }
@@ -7594,14 +7650,15 @@
     JsonReserveArray(buffers, model->buffers.size());
     for (unsigned int i = 0; i < model->buffers.size(); ++i) {
       json buffer;
-      if (writeBinary && i==0 && model->buffers[i].uri.empty()){
-        SerializeGltfBufferBin(model->buffers[i], buffer,binBuffer);
+      if (writeBinary && i == 0 && model->buffers[i].uri.empty()) {
+        SerializeGltfBufferBin(model->buffers[i], buffer, binBuffer);
       } else if (embedBuffers) {
         SerializeGltfBuffer(model->buffers[i], buffer);
       } else {
         std::string binSavePath;
         std::string binUri;
-        if (!model->buffers[i].uri.empty() && !IsDataURI(model->buffers[i].uri)) {
+        if (!model->buffers[i].uri.empty() &&
+            !IsDataURI(model->buffers[i].uri)) {
           binUri = model->buffers[i].uri;
         } else {
           binUri = defaultBinFilename + defaultBinFileExt;
@@ -7627,7 +7684,7 @@
       }
       JsonPushBack(buffers, std::move(buffer));
     }
-  JsonAddMember(output, "buffers", std::move(buffers));
+    JsonAddMember(output, "buffers", std::move(buffers));
   }
 
   // IMAGES
@@ -7654,6 +7711,8 @@
   return true;
 }
 
+#endif // TINYGLTF_ENABLE_SERIALIZER
+
 }  // namespace tinygltf
 
 #ifdef __clang__