From f3f5bf480d3b4160617a02c74ad76f17b3084759 Mon Sep 17 00:00:00 2001 From: Austin Eng Date: Sat, 18 May 2019 03:14:46 +0000 Subject: [PATCH] Remove glTFViewer from samples Bug: dawn:152 Change-Id: I5bd36f4ae56889bf12652f8a201a7f7be5e2d25d Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/7360 Commit-Queue: Austin Eng Reviewed-by: Kai Ninomiya --- BUILD.gn | 12 - DEPS | 6 +- examples/glTFViewer/Camera.inc | 67 - examples/glTFViewer/README.md | 30 - examples/glTFViewer/glTFViewer.cpp | 686 ----- examples/glTFViewer/img/nxt-gltf-duck.jpg | Bin 16087 -> 0 bytes examples/glTFViewer/img/nxt-gltf-vc.jpg | Bin 95119 -> 0 bytes third_party/BUILD.gn | 41 - third_party/picojson/picojson.h | 1040 ------- third_party/tinygltfloader/tiny_gltf_loader.h | 2656 ----------------- 10 files changed, 1 insertion(+), 4537 deletions(-) delete mode 100644 examples/glTFViewer/Camera.inc delete mode 100644 examples/glTFViewer/README.md delete mode 100644 examples/glTFViewer/glTFViewer.cpp delete mode 100644 examples/glTFViewer/img/nxt-gltf-duck.jpg delete mode 100644 examples/glTFViewer/img/nxt-gltf-vc.jpg delete mode 100644 third_party/picojson/picojson.h delete mode 100644 third_party/tinygltfloader/tiny_gltf_loader.h diff --git a/BUILD.gn b/BUILD.gn index fe7e68a2a9..14e7cbb160 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -762,17 +762,6 @@ if (dawn_standalone) { ] } - dawn_sample("glTFViewer") { - sources = [ - "examples/glTFViewer/Camera.inc", - "examples/glTFViewer/glTFViewer.cpp", - ] - deps = [ - "third_party:glm", - "third_party:tiny_gltf_loader", - ] - } - group("dawn_samples") { deps = [ ":Animometer", @@ -780,7 +769,6 @@ if (dawn_standalone) { ":ComputeBoids", ":CppHelloTriangle", ":CubeReflection", - ":glTFViewer", ] } } diff --git a/DEPS b/DEPS index ebc5bbb66d..741f57bcc9 100644 --- a/DEPS +++ b/DEPS @@ -78,11 +78,7 @@ deps = { 'condition': 'dawn_standalone', }, - # Dependencies for samples: stb and GLM - 'third_party/stb': { - 'url': '{github_git}/nothings/stb.git@c7110588a4d24c4bb5155c184fbb77dd90b3116e', - 'condition': 'dawn_standalone', - }, + # Dependencies for samples: GLM 'third_party/glm': { 'url': '{github_git}/g-truc/glm.git@06f084063fd6d9aa2ef6904517650700ae47b63d', 'condition': 'dawn_standalone', diff --git a/examples/glTFViewer/Camera.inc b/examples/glTFViewer/Camera.inc deleted file mode 100644 index 530e8b1731..0000000000 --- a/examples/glTFViewer/Camera.inc +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2017 The Dawn Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -class Camera { - public: - Camera() - : _azimuth(glm::radians(45.f)), - _altitude(glm::radians(30.f)), - _radius(10.f), - _center(0, 0, 0), - _dirty(true) { - recalculate(); - } - - void rotate(float dAzimuth, float dAltitude) { - _dirty = true; - _azimuth = glm::mod(_azimuth + dAzimuth, glm::radians(360.f)); - _altitude = glm::clamp(_altitude + dAltitude, glm::radians(-89.f), glm::radians(89.f)); - } - - void pan(float dX, float dY) { - recalculate(); - glm::vec3 vX = glm::normalize(glm::cross(-_eyeDir, glm::vec3(0, 1, 0))); - glm::vec3 vY = glm::normalize(glm::cross(_eyeDir, vX)); - _center += vX * dX * _radius + vY * dY * _radius; - } - - void zoom(float factor) { - _dirty = true; - _radius = _radius * glm::exp(-factor); - } - - glm::mat4 view() { - if (_dirty) { - recalculate(); - } - return _view; - } - private: - void recalculate() { - glm::vec4 eye4 = glm::vec4(1, 0, 0, 1); - eye4 = glm::rotate(glm::mat4(), _altitude, glm::vec3(0, 0, 1)) * eye4; - eye4 = glm::rotate(glm::mat4(), _azimuth, glm::vec3(0, 1, 0)) * eye4; - _eyeDir = glm::vec3(eye4); - - _view = glm::lookAt(_center + _eyeDir * _radius, _center, glm::vec3(0, -1, 0)); - _dirty = false; - } - float _azimuth; - float _altitude; - float _radius; - glm::vec3 _center; - glm::vec3 _eyeDir; - bool _dirty; - glm::mat4 _view; -}; diff --git a/examples/glTFViewer/README.md b/examples/glTFViewer/README.md deleted file mode 100644 index d1ddf6bde9..0000000000 --- a/examples/glTFViewer/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Dawn glTF Viewer - -This is a barebones glTF model viewer using the Dawn API. It is intended as a -proof of concept for the API and is not a robust model viewer. It can load -basic mesh/texture data from a few -[glTF sample models](https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/1.0), -such as: - -* 2CylinderEngine -* BoxWithoutIndices -* Cesium Man -* Duck -* Monster -* VC (Virtual City) - -## Usage - -`build/examples/glTFViewer/glTFViewer path/to/Duck.gltf` - -`build/examples/glTFViewer/glTFViewer path/to/Duck.gltf --backend metal` - -## Screenshots - -Duck: - -![Duck](img/nxt-gltf-duck.jpg) - -VC (Virtual City): - -![Virtual City](img/nxt-gltf-vc.jpg) diff --git a/examples/glTFViewer/glTFViewer.cpp b/examples/glTFViewer/glTFViewer.cpp deleted file mode 100644 index 5dc6d0900d..0000000000 --- a/examples/glTFViewer/glTFViewer.cpp +++ /dev/null @@ -1,686 +0,0 @@ -// Copyright 2017 The Dawn Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Enable this before including any headers as we want inttypes.h to define -// format macros such as PRId64 that are used in picojson. -#ifndef __STDC_FORMAT_MACROS -#define __STDC_FORMAT_MACROS -#endif - -#include "../SampleUtils.h" - -#include "common/Assert.h" -#include "common/Math.h" -#include "common/Constants.h" -#include "utils/ComboRenderPipelineDescriptor.h" -#include "utils/DawnHelpers.h" -#include "utils/SystemUtils.h" - -#include -#define GLM_FORCE_DEPTH_ZERO_TO_ONE -#include -#include -#include -#include - -#define TINYGLTF_LOADER_IMPLEMENTATION -#define STB_IMAGE_IMPLEMENTATION -#define PICOJSON_ASSERT ASSERT -#undef __STDC_FORMAT_MACROS -#include - -#include "GLFW/glfw3.h" - -#include "Camera.inc" - -namespace gl { - enum { - Triangles = 0x0004, - UnsignedShort = 0x1403, - UnsignedInt = 0x1405, - Float = 0x1406, - RGBA = 0x1908, - Nearest = 0x2600, - Linear = 0x2601, - NearestMipmapNearest = 0x2700, - LinearMipmapNearest = 0x2701, - NearestMipmapLinear = 0x2702, - LinearMipmapLinear = 0x2703, - ArrayBuffer = 0x8892, - ElementArrayBuffer = 0x8893, - FragmentShader = 0x8B30, - VertexShader = 0x8B31, - FloatVec2 = 0x8B50, - FloatVec3 = 0x8B51, - FloatVec4 = 0x8B52, - }; -} - -struct MaterialInfo { - dawn::RenderPipeline pipeline; - dawn::BindGroup bindGroup0; - std::map slotSemantics; -}; - -struct u_transform_block { - glm::mat4 modelViewProj; - glm::mat4 modelInvTr; -}; - -dawn::Device device; -dawn::Queue queue; -dawn::SwapChain swapchain; -dawn::TextureView depthStencilView; - -dawn::Buffer defaultBuffer; -std::map buffers; -std::map commandBuffers; -std::map slotSemantics = {{0, "POSITION"}, {1, "NORMAL"}, {2, "TEXCOORD_0"}}; - -std::map samplers; -std::map textures; - -tinygltf::Scene scene; -glm::mat4 projection = glm::perspective(glm::radians(60.f), 640.f/480, 0.1f, 2000.f); -Camera camera; - -// Helpers -namespace { - std::string getFilePathExtension(const std::string &FileName) { - if (FileName.find_last_of(".") != std::string::npos) { - return FileName.substr(FileName.find_last_of(".") + 1); - } - return ""; - } - - bool techniqueParameterTypeToVertexFormat(int type, dawn::VertexFormat *format) { - switch (type) { - case gl::FloatVec2: - *format = dawn::VertexFormat::Float2; - return true; - case gl::FloatVec3: - *format = dawn::VertexFormat::Float3; - return true; - case gl::FloatVec4: - *format = dawn::VertexFormat::Float4; - return true; - default: - return false; - } - } -} - -// Initialization -namespace { - void initBuffers() { - dawn::BufferDescriptor descriptor; - descriptor.size = 256; - descriptor.usage = dawn::BufferUsageBit::Vertex | dawn::BufferUsageBit::Index; - defaultBuffer = device.CreateBuffer(&descriptor); - - for (const auto& bv : scene.bufferViews) { - const auto& iBufferViewID = bv.first; - const auto& iBufferView = bv.second; - - dawn::BufferUsageBit usage = dawn::BufferUsageBit::None; - switch (iBufferView.target) { - case gl::ArrayBuffer: - usage |= dawn::BufferUsageBit::Vertex; - break; - case gl::ElementArrayBuffer: - usage |= dawn::BufferUsageBit::Index; - break; - case 0: - fprintf(stderr, "TODO: buffer view has no target; skipping\n"); - continue; - default: - fprintf(stderr, "unsupported buffer view target %d\n", iBufferView.target); - continue; - } - const auto& iBuffer = scene.buffers.at(iBufferView.buffer); - - size_t iBufferViewSize = - iBufferView.byteLength ? iBufferView.byteLength : - (iBuffer.data.size() - iBufferView.byteOffset); - auto oBuffer = utils::CreateBufferFromData(device, &iBuffer.data.at(iBufferView.byteOffset), static_cast(iBufferViewSize), usage); - buffers[iBufferViewID] = std::move(oBuffer); - } - } - - const MaterialInfo& getMaterial(const std::string& iMaterialID, size_t stridePos, size_t strideNor, size_t strideTxc) { - static std::map, MaterialInfo> materials; - auto key = make_tuple(iMaterialID, stridePos, strideNor, strideTxc); - auto materialIterator = materials.find(key); - if (materialIterator != materials.end()) { - return materialIterator->second; - } - - const auto& iMaterial = scene.materials.at(iMaterialID); - const auto& iTechnique = scene.techniques.at(iMaterial.technique); - - bool hasTexture = false; - std::string iTextureID; - { - auto it = iMaterial.values.find("diffuse"); - if (it != iMaterial.values.end() && !it->second.string_value.empty()) { - hasTexture = true; - iTextureID = it->second.string_value; - } - } - - auto oVSModule = utils::CreateShaderModule(device, dawn::ShaderStage::Vertex, R"( - #version 450 - - layout(push_constant) uniform u_transform_block { - mat4 modelViewProj; - mat4 modelInvTr; - } u_transform; - - layout(location = 0) in vec4 a_position; - layout(location = 1) in vec3 a_normal; - layout(location = 2) in vec2 a_texcoord; - - layout(location = 0) out vec3 v_normal; - layout(location = 1) out vec2 v_texcoord; - - void main() { - v_normal = (u_transform.modelInvTr * vec4(normalize(a_normal), 0)).rgb; - v_texcoord = a_texcoord; - gl_Position = u_transform.modelViewProj * a_position; - })"); - - auto oFSSourceTextured = R"( - #version 450 - - layout(set = 0, binding = 0) uniform sampler u_samp; - layout(set = 0, binding = 1) uniform texture2D u_tex; - - layout(location = 0) in vec3 v_normal; - layout(location = 1) in vec2 v_texcoord; - - layout(location = 0) out vec4 fragcolor; - - void main() { - const vec3 lightdir = normalize(vec3(-1, -2, 3)); - vec3 normal = normalize(v_normal); - float diffuse = abs(dot(lightdir, normal)); - float diffamb = diffuse * 0.85 + 0.15; - vec3 albedo = texture(sampler2D(u_tex, u_samp), v_texcoord).rgb; - fragcolor = vec4(diffamb * albedo, 1); - })"; - auto oFSSourceUntextured = R"( - #version 450 - - layout(location = 0) in vec3 v_normal; - layout(location = 1) in vec2 v_texcoord; - - layout(location = 0) out vec4 fragcolor; - - void main() { - const vec3 lightdir = normalize(vec3(-1, -2, 3)); - vec3 normal = normalize(v_normal); - float diffuse = abs(dot(lightdir, normal)); - float diffamb = diffuse * 0.85 + 0.15; - fragcolor = vec4(vec3(diffamb), 1); - })"; - - auto oFSModule = utils::CreateShaderModule(device, dawn::ShaderStage::Fragment, hasTexture ? oFSSourceTextured : oFSSourceUntextured); - - utils::ComboRenderPipelineDescriptor descriptor(device); - descriptor.cInputState.indexFormat = dawn::IndexFormat::Uint16; - uint32_t numAttributes = 0; - uint32_t numInputs = 0; - std::bitset<3> slotsSet; - for (const auto& a : iTechnique.attributes) { - const auto iAttributeName = a.first; - const auto iParameter = iTechnique.parameters.at(a.second); - dawn::VertexFormat format; - if (!techniqueParameterTypeToVertexFormat(iParameter.type, &format)) { - fprintf(stderr, "unsupported technique parameter type %d\n", iParameter.type); - continue; - } - descriptor.cInputState.cAttributes[numAttributes].format = format; - - if (iParameter.semantic == "POSITION") { - descriptor.cInputState.cInputs[numInputs].stride = static_cast(stridePos); - numAttributes++; - numInputs++; - slotsSet.set(0); - } else if (iParameter.semantic == "NORMAL") { - descriptor.cInputState.cAttributes[numAttributes].shaderLocation = 1; - descriptor.cInputState.cAttributes[numAttributes].inputSlot = 1; - descriptor.cInputState.cInputs[numInputs].inputSlot = 1; - descriptor.cInputState.cInputs[numInputs].stride = static_cast(strideNor); - numAttributes++; - numInputs++; - slotsSet.set(1); - } else if (iParameter.semantic == "TEXCOORD_0") { - descriptor.cInputState.cAttributes[numAttributes].shaderLocation = 2; - descriptor.cInputState.cAttributes[numAttributes].inputSlot = 2; - descriptor.cInputState.cInputs[numInputs].inputSlot = 2; - descriptor.cInputState.cInputs[numInputs].stride = static_cast(strideTxc); - numAttributes++; - numInputs++; - slotsSet.set(2); - } else { - fprintf(stderr, "unsupported technique attribute semantic %s\n", iParameter.semantic.c_str()); - } - } - for (uint32_t i = 0; i < slotsSet.size(); i++) { - if (slotsSet[i]) { - continue; - } - descriptor.cInputState.cAttributes[numAttributes].shaderLocation = i; - descriptor.cInputState.cAttributes[numAttributes].inputSlot = i; - descriptor.cInputState.cAttributes[numAttributes].format = dawn::VertexFormat::Float4; - - descriptor.cInputState.cInputs[numInputs].inputSlot = i; - - numAttributes++; - numInputs++; - } - descriptor.cInputState.numAttributes = numAttributes; - descriptor.cInputState.numInputs = numInputs; - - constexpr dawn::ShaderStageBit kNoStages{}; - dawn::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( - device, { - {0, hasTexture ? dawn::ShaderStageBit::Fragment : kNoStages, - dawn::BindingType::Sampler}, - {1, hasTexture ? dawn::ShaderStageBit::Fragment : kNoStages, - dawn::BindingType::SampledTexture}, - }); - - auto pipelineLayout = utils::MakeBasicPipelineLayout(device, &bindGroupLayout); - - descriptor.layout = pipelineLayout; - descriptor.cVertexStage.module = oVSModule; - descriptor.cFragmentStage.module = oFSModule; - descriptor.depthStencilState = &descriptor.cDepthStencilState; - descriptor.cDepthStencilState.format = dawn::TextureFormat::D32FloatS8Uint; - descriptor.cColorStates[0]->format = GetPreferredSwapChainTextureFormat(); - descriptor.cDepthStencilState.depthWriteEnabled = true; - descriptor.cDepthStencilState.depthCompare = dawn::CompareFunction::Less; - - dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); - - dawn::BindGroup bindGroup; - - if (hasTexture) { - const auto& textureView = textures[iTextureID]; - const auto& iSamplerID = scene.textures[iTextureID].sampler; - bindGroup = utils::MakeBindGroup(device, bindGroupLayout, { - {0, samplers[iSamplerID]}, - {1, textureView} - }); - } else { - bindGroup = utils::MakeBindGroup(device, bindGroupLayout, {}); - } - - MaterialInfo material = { - pipeline, - bindGroup, - std::map(), - }; - materials[key] = std::move(material); - return materials.at(key); - } - - void initSamplers() { - for (const auto& s : scene.samplers) { - const auto& iSamplerID = s.first; - const auto& iSampler = s.second; - - dawn::SamplerDescriptor desc = utils::GetDefaultSamplerDescriptor(); - // TODO: wrap modes - - switch (iSampler.magFilter) { - case gl::Nearest: - desc.magFilter = dawn::FilterMode::Nearest; - break; - case gl::Linear: - desc.magFilter = dawn::FilterMode::Linear; - break; - default: - fprintf(stderr, "unsupported magFilter %d\n", iSampler.magFilter); - break; - } - switch (iSampler.minFilter) { - case gl::Nearest: - case gl::NearestMipmapNearest: - case gl::NearestMipmapLinear: - desc.minFilter = dawn::FilterMode::Nearest; - break; - case gl::Linear: - case gl::LinearMipmapNearest: - case gl::LinearMipmapLinear: - desc.minFilter = dawn::FilterMode::Linear; - break; - default: - fprintf(stderr, "unsupported minFilter %d\n", iSampler.magFilter); - break; - } - switch (iSampler.minFilter) { - case gl::NearestMipmapNearest: - case gl::LinearMipmapNearest: - desc.mipmapFilter = dawn::FilterMode::Nearest; - break; - case gl::NearestMipmapLinear: - case gl::LinearMipmapLinear: - desc.mipmapFilter = dawn::FilterMode::Linear; - break; - } - - samplers[iSamplerID] = device.CreateSampler(&desc); - } - } - - void initTextures() { - for (const auto& t : scene.textures) { - const auto& iTextureID = t.first; - const auto& iTexture = t.second; - const auto& iImage = scene.images[iTexture.source]; - - dawn::TextureFormat format = dawn::TextureFormat::R8G8B8A8Unorm; - switch (iTexture.format) { - case gl::RGBA: - format = dawn::TextureFormat::R8G8B8A8Unorm; - break; - default: - fprintf(stderr, "unsupported texture format %d\n", iTexture.format); - continue; - } - - dawn::TextureDescriptor descriptor; - descriptor.dimension = dawn::TextureDimension::e2D; - descriptor.size.width = iImage.width; - descriptor.size.height = iImage.height; - descriptor.size.depth = 1; - descriptor.arrayLayerCount = 1; - descriptor.sampleCount = 1; - descriptor.format = format; - descriptor.mipLevelCount = 1; - descriptor.usage = dawn::TextureUsageBit::TransferDst | dawn::TextureUsageBit::Sampled; - auto oTexture = device.CreateTexture(&descriptor); - // TODO: release this texture - - const uint8_t* origData = iImage.image.data(); - const uint8_t* data = nullptr; - std::vector newData; - - uint32_t width = static_cast(iImage.width); - uint32_t height = static_cast(iImage.height); - uint32_t rowSize = width * 4; - uint32_t rowPitch = Align(rowSize, kTextureRowPitchAlignment); - - if (iImage.component == 3 || iImage.component == 4) { - if (rowSize != rowPitch || iImage.component == 3) { - newData.resize(rowPitch * height); - uint32_t pixelsPerRow = rowPitch / 4; - for (uint32_t y = 0; y < height; ++y) { - for (uint32_t x = 0; x < width; ++x) { - size_t oldIndex = x + y * height; - size_t newIndex = x + y * pixelsPerRow; - if (iImage.component == 4) { - newData[4 * newIndex + 0] = origData[4 * oldIndex + 0]; - newData[4 * newIndex + 1] = origData[4 * oldIndex + 1]; - newData[4 * newIndex + 2] = origData[4 * oldIndex + 2]; - newData[4 * newIndex + 3] = origData[4 * oldIndex + 3]; - } else if (iImage.component == 3) { - newData[4 * newIndex + 0] = origData[3 * oldIndex + 0]; - newData[4 * newIndex + 1] = origData[3 * oldIndex + 1]; - newData[4 * newIndex + 2] = origData[3 * oldIndex + 2]; - newData[4 * newIndex + 3] = 255; - } - } - } - data = newData.data(); - } else { - data = origData; - } - } else { - fprintf(stderr, "unsupported image.component %d\n", iImage.component); - } - - dawn::Buffer staging = utils::CreateBufferFromData(device, data, rowPitch * iImage.height, dawn::BufferUsageBit::TransferSrc); - dawn::BufferCopyView bufferCopyView = - utils::CreateBufferCopyView(staging, 0, rowPitch, 0); - dawn::TextureCopyView textureCopyView = - utils::CreateTextureCopyView(oTexture, 0, 0, {0, 0, 0}); - dawn::Extent3D copySize = {iImage.width, iImage.height, 1}; - - dawn::CommandEncoder encoder = device.CreateCommandEncoder(); - encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, ©Size); - - dawn::CommandBuffer cmdbuf = encoder.Finish(); - queue.Submit(1, &cmdbuf); - - textures[iTextureID] = oTexture.CreateDefaultView(); - } - } - - void init() { - device = CreateCppDawnDevice(); - - queue = device.CreateQueue(); - swapchain = GetSwapChain(device); - swapchain.Configure(GetPreferredSwapChainTextureFormat(), - dawn::TextureUsageBit::OutputAttachment, 640, 480); - - depthStencilView = CreateDefaultDepthStencilView(device); - - initBuffers(); - initSamplers(); - initTextures(); - } -} - -// Drawing -namespace { - void drawMesh(dawn::RenderPassEncoder& pass, const tinygltf::Mesh& iMesh, const glm::mat4& model) { - for (const auto& iPrim : iMesh.primitives) { - if (iPrim.mode != gl::Triangles) { - fprintf(stderr, "unsupported primitive mode %d\n", iPrim.mode); - continue; - } - - u_transform_block transforms = { - (projection * camera.view() * model), - glm::inverseTranspose(model), - }; - - size_t strides[3] = {0}; - for (const auto& s : slotSemantics) { - if (s.first < 3) { - auto it = iPrim.attributes.find(s.second); - if (it == iPrim.attributes.end()) { - continue; - } - const auto& iAccessorName = it->second; - strides[s.first] = scene.accessors.at(iAccessorName).byteStride; - } - } - const MaterialInfo& material = getMaterial(iPrim.material, strides[0], strides[1], strides[2]); - pass.SetPipeline(material.pipeline); - pass.SetBindGroup(0, material.bindGroup0, 0, nullptr); - pass.SetPushConstants(dawn::ShaderStageBit::Vertex, - 0, sizeof(u_transform_block) / sizeof(uint32_t), - reinterpret_cast(&transforms)); - - uint32_t vertexCount = 0; - for (const auto& s : slotSemantics) { - uint32_t slot = s.first; - auto it = iPrim.attributes.find(s.second); - if (it == iPrim.attributes.end()) { - uint64_t zero = 0; - pass.SetVertexBuffers(slot, 1, &defaultBuffer, &zero); - continue; - } - const auto& iAccessor = scene.accessors.at(it->second); - if (iAccessor.componentType != gl::Float || - (iAccessor.type != TINYGLTF_TYPE_VEC4 && iAccessor.type != TINYGLTF_TYPE_VEC3 && iAccessor.type != TINYGLTF_TYPE_VEC2)) { - fprintf(stderr, "unsupported vertex accessor component type %d and type %d\n", iAccessor.componentType, iAccessor.type); - continue; - } - - if (vertexCount == 0) { - vertexCount = static_cast(iAccessor.count); - } - const auto& oBuffer = buffers.at(iAccessor.bufferView); - uint64_t iBufferOffset = static_cast(iAccessor.byteOffset); - pass.SetVertexBuffers(slot, 1, &oBuffer, &iBufferOffset); - } - - if (!iPrim.indices.empty()) { - const auto& iIndices = scene.accessors.at(iPrim.indices); - // DrawElements - if (iIndices.componentType != gl::UnsignedShort || iIndices.type != TINYGLTF_TYPE_SCALAR) { - fprintf(stderr, "unsupported index accessor component type %d and type %d\n", iIndices.componentType, iIndices.type); - continue; - } - const auto& oIndicesBuffer = buffers.at(iIndices.bufferView); - pass.SetIndexBuffer(oIndicesBuffer, static_cast(iIndices.byteOffset)); - pass.DrawIndexed(static_cast(iIndices.count), 1, 0, 0, 0); - } else { - // DrawArrays - pass.Draw(vertexCount, 1, 0, 0); - } - } - } - - void drawNode(dawn::RenderPassEncoder& pass, const tinygltf::Node& node, const glm::mat4& parent = glm::mat4()) { - glm::mat4 model; - if (node.matrix.size() == 16) { - model = glm::make_mat4(node.matrix.data()); - } else { - if (node.scale.size() == 3) { - glm::vec3 scale = glm::make_vec3(node.scale.data()); - model = glm::scale(model, scale); - } - if (node.rotation.size() == 4) { - glm::quat rotation = glm::make_quat(node.rotation.data()); - model = glm::mat4_cast(rotation) * model; - } - if (node.translation.size() == 3) { - glm::vec3 translation = glm::make_vec3(node.translation.data()); - model = glm::translate(model, translation); - } - } - model = parent * model; - - for (const auto& meshID : node.meshes) { - drawMesh(pass, scene.meshes[meshID], model); - } - for (const auto& child : node.children) { - drawNode(pass, scene.nodes.at(child), model); - } - } - - void frame() { - dawn::Texture backbuffer = swapchain.GetNextTexture(); - - const auto& defaultSceneNodes = scene.scenes.at(scene.defaultScene); - dawn::CommandEncoder encoder = device.CreateCommandEncoder(); - { - utils::ComboRenderPassDescriptor renderPass({backbuffer.CreateDefaultView()}, - depthStencilView); - dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - for (const auto& n : defaultSceneNodes) { - const auto& node = scene.nodes.at(n); - drawNode(pass, node); - } - pass.EndPass(); - } - - dawn::CommandBuffer commands = encoder.Finish(); - queue.Submit(1, &commands); - - swapchain.Present(backbuffer); - DoFlush(); - } -} - -// Mouse camera control -namespace { - bool buttons[GLFW_MOUSE_BUTTON_LAST + 1] = {0}; - - void mouseButtonCallback(GLFWwindow*, int button, int action, int) { - buttons[button] = (action == GLFW_PRESS); - } - - void cursorPosCallback(GLFWwindow*, double mouseX, double mouseY) { - static double oldX, oldY; - float dX = static_cast(mouseX - oldX); - float dY = static_cast(mouseY - oldY); - oldX = mouseX; - oldY = mouseY; - - if (buttons[2] || (buttons[0] && buttons[1])) { - camera.pan(-dX * 0.002f, dY * 0.002f); - } else if (buttons[0]) { - camera.rotate(dX * 0.01f, dY * 0.01f); - } else if (buttons[1]) { - camera.zoom(dY * -0.005f); - } - } - - void scrollCallback(GLFWwindow*, double, double yoffset) { - camera.zoom(static_cast(yoffset) * 0.04f); - } -} - -int main(int argc, const char* argv[]) { - if (!InitSample(argc, argv)) { - return 1; - } - if (argc < 2) { - fprintf(stderr, "Usage: %s model.gltf [... Dawn Options]\n", argv[0]); - return 1; - } - - tinygltf::TinyGLTFLoader loader; - std::string err; - std::string input_filename(argv[1]); - std::string ext = getFilePathExtension(input_filename); - - bool ret = false; - if (ext.compare("glb") == 0) { - // assume binary glTF. - ret = loader.LoadBinaryFromFile(&scene, &err, input_filename.c_str()); - } else { - // assume ascii glTF. - ret = loader.LoadASCIIFromFile(&scene, &err, input_filename.c_str()); - } - if (!err.empty()) { - fprintf(stderr, "ERR: %s\n", err.c_str()); - } - if (!ret) { - fprintf(stderr, "Failed to load .glTF : %s\n", argv[1]); - exit(-1); - } - - init(); - - GLFWwindow* window = GetGLFWWindow(); - glfwSetMouseButtonCallback(window, mouseButtonCallback); - glfwSetCursorPosCallback(window, cursorPosCallback); - glfwSetScrollCallback(window, scrollCallback); - - while (!ShouldQuit()) { - frame(); - utils::USleep(16000); - } - - // TODO release stuff -} diff --git a/examples/glTFViewer/img/nxt-gltf-duck.jpg b/examples/glTFViewer/img/nxt-gltf-duck.jpg deleted file mode 100644 index da78162a75b64140bb96f8f326550e507b312999..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16087 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 diff --git a/examples/glTFViewer/img/nxt-gltf-vc.jpg b/examples/glTFViewer/img/nxt-gltf-vc.jpg deleted file mode 100644 index 29f969497aee2a60b977f59f839fc830ed652bec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95119 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�(6=F^7X0IMSS^%r}-YVpAlwd;aHSLY= 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 z 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 z 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 xXRjyvseAhu@5lb2yaN9K7JYl` -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// for isnan/isinf -#if __cplusplus>=201103L -# include -#else -extern "C" { -# ifdef _MSC_VER -# include -# elif defined(__INTEL_COMPILER) -# include -# else -# include -# endif -} -#endif - -#ifndef PICOJSON_USE_RVALUE_REFERENCE -# if (defined(__cpp_rvalue_references) && __cpp_rvalue_references >= 200610) || (defined(_MSC_VER) && _MSC_VER >= 1600) -# define PICOJSON_USE_RVALUE_REFERENCE 1 -# else -# define PICOJSON_USE_RVALUE_REFERENCE 0 -# endif -#endif//PICOJSON_USE_RVALUE_REFERENCE - - -// experimental support for int64_t (see README.mkdn for detail) -#ifdef PICOJSON_USE_INT64 -# define __STDC_FORMAT_MACROS -# include -# include -#endif - -// to disable the use of localeconv(3), set PICOJSON_USE_LOCALE to 0 -#ifndef PICOJSON_USE_LOCALE -# define PICOJSON_USE_LOCALE 1 -#endif -#if PICOJSON_USE_LOCALE -extern "C" { -# include -} -#endif - -#ifndef PICOJSON_ASSERT -# define PICOJSON_ASSERT(e) do { if (! (e)) throw std::runtime_error(#e); } while (0) -#endif - -#ifdef _MSC_VER - #define SNPRINTF _snprintf_s - #pragma warning(push) - #pragma warning(disable : 4244) // conversion from int to char - #pragma warning(disable : 4127) // conditional expression is constant - #pragma warning(disable : 4702) // unreachable code -#else - #define SNPRINTF snprintf -#endif - -namespace picojson { - - enum { - null_type, - boolean_type, - number_type, - string_type, - array_type, - object_type -#ifdef PICOJSON_USE_INT64 - , int64_type -#endif - }; - - enum { - INDENT_WIDTH = 2 - }; - - struct null {}; - - class value { - public: - typedef std::vector array; - typedef std::map object; - union _storage { - bool boolean_; - double number_; -#ifdef PICOJSON_USE_INT64 - int64_t int64_; -#endif - std::string* string_; - array* array_; - object* object_; - }; - protected: - int type_; - _storage u_; - public: - value(); - value(int type, bool); - explicit value(bool b); -#ifdef PICOJSON_USE_INT64 - explicit value(int64_t i); -#endif - explicit value(double n); - explicit value(const std::string& s); - explicit value(const array& a); - explicit value(const object& o); - explicit value(const char* s); - value(const char* s, size_t len); - ~value(); - value(const value& x); - value& operator=(const value& x); -#if PICOJSON_USE_RVALUE_REFERENCE - value(value&& x)throw(); - value& operator=(value&& x)throw(); -#endif - void swap(value& x)throw(); - template bool is() const; - template const T& get() const; - template T& get(); - bool evaluate_as_boolean() const; - const value& get(size_t idx) const; - const value& get(const std::string& key) const; - value& get(size_t idx); - value& get(const std::string& key); - - bool contains(size_t idx) const; - bool contains(const std::string& key) const; - std::string to_str() const; - template void serialize(Iter os, bool prettify = false) const; - std::string serialize(bool prettify = false) const; - private: - template value(const T*); // intentionally defined to block implicit conversion of pointer to bool - template static void _indent(Iter os, int indent); - template void _serialize(Iter os, int indent) const; - std::string _serialize(int indent) const; - }; - - typedef value::array array; - typedef value::object object; - - inline value::value() : type_(null_type) {} - - inline value::value(int type, bool) : type_(type) { - switch (type) { -#define INIT(p, v) case p##type: u_.p = v; break - INIT(boolean_, false); - INIT(number_, 0.0); -#ifdef PICOJSON_USE_INT64 - INIT(int64_, 0); -#endif - INIT(string_, new std::string()); - INIT(array_, new array()); - INIT(object_, new object()); -#undef INIT - default: break; - } - } - - inline value::value(bool b) : type_(boolean_type) { - u_.boolean_ = b; - } - -#ifdef PICOJSON_USE_INT64 - inline value::value(int64_t i) : type_(int64_type) { - u_.int64_ = i; - } -#endif - - inline value::value(double n) : type_(number_type) { - if ( -#ifdef _MSC_VER - ! _finite(n) -#elif __cplusplus>=201103L || !(defined(isnan) && defined(isinf)) - std::isnan(n) || std::isinf(n) -#else - isnan(n) || isinf(n) -#endif - ) { - // throw std::overflow_error(""); - PICOJSON_ASSERT(false); - } - u_.number_ = n; - } - - inline value::value(const std::string& s) : type_(string_type) { - u_.string_ = new std::string(s); - } - - inline value::value(const array& a) : type_(array_type) { - u_.array_ = new array(a); - } - - inline value::value(const object& o) : type_(object_type) { - u_.object_ = new object(o); - } - - inline value::value(const char* s) : type_(string_type) { - u_.string_ = new std::string(s); - } - - inline value::value(const char* s, size_t len) : type_(string_type) { - u_.string_ = new std::string(s, len); - } - - inline value::~value() { - switch (type_) { -#define DEINIT(p) case p##type: delete u_.p; break - DEINIT(string_); - DEINIT(array_); - DEINIT(object_); -#undef DEINIT - default: break; - } - } - - inline value::value(const value& x) : type_(x.type_) { - switch (type_) { -#define INIT(p, v) case p##type: u_.p = v; break - INIT(string_, new std::string(*x.u_.string_)); - INIT(array_, new array(*x.u_.array_)); - INIT(object_, new object(*x.u_.object_)); -#undef INIT - default: - u_ = x.u_; - break; - } - } - - inline value& value::operator=(const value& x) { - if (this != &x) { - value t(x); - swap(t); - } - return *this; - } - -#if PICOJSON_USE_RVALUE_REFERENCE - inline value::value(value&& x)throw() : type_(null_type) { - swap(x); - } - inline value& value::operator=(value&& x)throw() { - swap(x); - return *this; - } -#endif - inline void value::swap(value& x)throw() { - std::swap(type_, x.type_); - std::swap(u_, x.u_); - } - -#define IS(ctype, jtype) \ - template <> inline bool value::is() const { \ - return type_ == jtype##_type; \ - } - IS(null, null) - IS(bool, boolean) -#ifdef PICOJSON_USE_INT64 - IS(int64_t, int64) -#endif - IS(std::string, string) - IS(array, array) - IS(object, object) -#undef IS - template <> inline bool value::is() const { - return type_ == number_type -#ifdef PICOJSON_USE_INT64 - || type_ == int64_type -#endif - ; - } - -#define GET(ctype, var) \ - template <> inline const ctype& value::get() const { \ - PICOJSON_ASSERT("type mismatch! call is() before get()" \ - && is()); \ - return var; \ - } \ - template <> inline ctype& value::get() { \ - PICOJSON_ASSERT("type mismatch! call is() before get()" \ - && is()); \ - return var; \ - } - GET(bool, u_.boolean_) - GET(std::string, *u_.string_) - GET(array, *u_.array_) - GET(object, *u_.object_) -#ifdef PICOJSON_USE_INT64 - GET(double, (type_ == int64_type && (const_cast(this)->type_ = number_type, const_cast(this)->u_.number_ = u_.int64_), u_.number_)) - GET(int64_t, u_.int64_) -#else - GET(double, u_.number_) -#endif -#undef GET - - inline bool value::evaluate_as_boolean() const { - switch (type_) { - case null_type: - return false; - case boolean_type: - return u_.boolean_; - case number_type: - return u_.number_ != 0; -#ifdef PICOJSON_USE_INT64 - case int64_type: - return u_.int64_ != 0; -#endif - case string_type: - return ! u_.string_->empty(); - default: - return true; - } - } - - inline const value& value::get(size_t idx) const { - static value s_null; - PICOJSON_ASSERT(is()); - return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; - } - - inline value& value::get(size_t idx) { - static value s_null; - PICOJSON_ASSERT(is()); - return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; - } - - inline const value& value::get(const std::string& key) const { - static value s_null; - PICOJSON_ASSERT(is()); - object::const_iterator i = u_.object_->find(key); - return i != u_.object_->end() ? i->second : s_null; - } - - inline value& value::get(const std::string& key) { - static value s_null; - PICOJSON_ASSERT(is()); - object::iterator i = u_.object_->find(key); - return i != u_.object_->end() ? i->second : s_null; - } - - inline bool value::contains(size_t idx) const { - PICOJSON_ASSERT(is()); - return idx < u_.array_->size(); - } - - inline bool value::contains(const std::string& key) const { - PICOJSON_ASSERT(is()); - object::const_iterator i = u_.object_->find(key); - return i != u_.object_->end(); - } - - inline std::string value::to_str() const { - switch (type_) { - case null_type: return "null"; - case boolean_type: return u_.boolean_ ? "true" : "false"; -#ifdef PICOJSON_USE_INT64 - case int64_type: { - char buf[sizeof("-9223372036854775808")]; - SNPRINTF(buf, sizeof(buf), "%" PRId64, u_.int64_); - return buf; - } -#endif - case number_type: { - char buf[256]; - double tmp; - SNPRINTF(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", u_.number_); -#if PICOJSON_USE_LOCALE - char *decimal_point = localeconv()->decimal_point; - if (strcmp(decimal_point, ".") != 0) { - size_t decimal_point_len = strlen(decimal_point); - for (char *p = buf; *p != '\0'; ++p) { - if (strncmp(p, decimal_point, decimal_point_len) == 0) { - return std::string(buf, p) + "." + (p + decimal_point_len); - } - } - } -#endif - return buf; - } - case string_type: return *u_.string_; - case array_type: return "array"; - case object_type: return "object"; - default: PICOJSON_ASSERT(0); -#ifdef _MSC_VER - __assume(0); -#endif - } - return std::string(); - } - - template void copy(const std::string& s, Iter oi) { - std::copy(s.begin(), s.end(), oi); - } - - template void serialize_str(const std::string& s, Iter oi) { - *oi++ = '"'; - for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) { - switch (*i) { -#define MAP(val, sym) case val: copy(sym, oi); break - MAP('"', "\\\""); - MAP('\\', "\\\\"); - MAP('/', "\\/"); - MAP('\b', "\\b"); - MAP('\f', "\\f"); - MAP('\n', "\\n"); - MAP('\r', "\\r"); - MAP('\t', "\\t"); -#undef MAP - default: - if (static_cast(*i) < 0x20 || *i == 0x7f) { - char buf[7]; - SNPRINTF(buf, sizeof(buf), "\\u%04x", *i & 0xff); - copy(buf, buf + 6, oi); - } else { - *oi++ = *i; - } - break; - } - } - *oi++ = '"'; - } - - template void value::serialize(Iter oi, bool prettify) const { - return _serialize(oi, prettify ? 0 : -1); - } - - inline std::string value::serialize(bool prettify) const { - return _serialize(prettify ? 0 : -1); - } - - template void value::_indent(Iter oi, int indent) { - *oi++ = '\n'; - for (int i = 0; i < indent * INDENT_WIDTH; ++i) { - *oi++ = ' '; - } - } - - template void value::_serialize(Iter oi, int indent) const { - switch (type_) { - case string_type: - serialize_str(*u_.string_, oi); - break; - case array_type: { - *oi++ = '['; - if (indent != -1) { - ++indent; - } - for (array::const_iterator i = u_.array_->begin(); - i != u_.array_->end(); - ++i) { - if (i != u_.array_->begin()) { - *oi++ = ','; - } - if (indent != -1) { - _indent(oi, indent); - } - i->_serialize(oi, indent); - } - if (indent != -1) { - --indent; - if (! u_.array_->empty()) { - _indent(oi, indent); - } - } - *oi++ = ']'; - break; - } - case object_type: { - *oi++ = '{'; - if (indent != -1) { - ++indent; - } - for (object::const_iterator i = u_.object_->begin(); - i != u_.object_->end(); - ++i) { - if (i != u_.object_->begin()) { - *oi++ = ','; - } - if (indent != -1) { - _indent(oi, indent); - } - serialize_str(i->first, oi); - *oi++ = ':'; - if (indent != -1) { - *oi++ = ' '; - } - i->second._serialize(oi, indent); - } - if (indent != -1) { - --indent; - if (! u_.object_->empty()) { - _indent(oi, indent); - } - } - *oi++ = '}'; - break; - } - default: - copy(to_str(), oi); - break; - } - if (indent == 0) { - *oi++ = '\n'; - } - } - - inline std::string value::_serialize(int indent) const { - std::string s; - _serialize(std::back_inserter(s), indent); - return s; - } - - template class input { - protected: - Iter cur_, end_; - int last_ch_; - bool ungot_; - int line_; - public: - input(const Iter& first, const Iter& last) : cur_(first), end_(last), last_ch_(-1), ungot_(false), line_(1) {} - int getc() { - if (ungot_) { - ungot_ = false; - return last_ch_; - } - if (cur_ == end_) { - last_ch_ = -1; - return -1; - } - if (last_ch_ == '\n') { - line_++; - } - last_ch_ = *cur_ & 0xff; - ++cur_; - return last_ch_; - } - void ungetc() { - if (last_ch_ != -1) { - PICOJSON_ASSERT(! ungot_); - ungot_ = true; - } - } - Iter cur() const { return cur_; } - int line() const { return line_; } - void skip_ws() { - while (1) { - int ch = getc(); - if (! (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { - ungetc(); - break; - } - } - } - bool expect(int expect) { - skip_ws(); - if (getc() != expect) { - ungetc(); - return false; - } - return true; - } - bool match(const std::string& pattern) { - for (std::string::const_iterator pi(pattern.begin()); - pi != pattern.end(); - ++pi) { - if (getc() != *pi) { - ungetc(); - return false; - } - } - return true; - } - }; - - template inline int _parse_quadhex(input &in) { - int uni_ch = 0, hex; - for (int i = 0; i < 4; i++) { - if ((hex = in.getc()) == -1) { - return -1; - } - if ('0' <= hex && hex <= '9') { - hex -= '0'; - } else if ('A' <= hex && hex <= 'F') { - hex -= 'A' - 0xa; - } else if ('a' <= hex && hex <= 'f') { - hex -= 'a' - 0xa; - } else { - in.ungetc(); - return -1; - } - uni_ch = uni_ch * 16 + hex; - } - return uni_ch; - } - - template inline bool _parse_codepoint(String& out, input& in) { - int uni_ch; - if ((uni_ch = _parse_quadhex(in)) == -1) { - return false; - } - if (0xd800 <= uni_ch && uni_ch <= 0xdfff) { - if (0xdc00 <= uni_ch) { - // a second 16-bit of a surrogate pair appeared - return false; - } - // first 16-bit of surrogate pair, get the next one - if (in.getc() != '\\' || in.getc() != 'u') { - in.ungetc(); - return false; - } - int second = _parse_quadhex(in); - if (! (0xdc00 <= second && second <= 0xdfff)) { - return false; - } - uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff); - uni_ch += 0x10000; - } - if (uni_ch < 0x80) { - out.push_back(uni_ch); - } else { - if (uni_ch < 0x800) { - out.push_back(0xc0 | (uni_ch >> 6)); - } else { - if (uni_ch < 0x10000) { - out.push_back(0xe0 | (uni_ch >> 12)); - } else { - out.push_back(0xf0 | (uni_ch >> 18)); - out.push_back(0x80 | ((uni_ch >> 12) & 0x3f)); - } - out.push_back(0x80 | ((uni_ch >> 6) & 0x3f)); - } - out.push_back(0x80 | (uni_ch & 0x3f)); - } - return true; - } - - template inline bool _parse_string(String& out, input& in) { - while (1) { - int ch = in.getc(); - if (ch < ' ') { - in.ungetc(); - return false; - } else if (ch == '"') { - return true; - } else if (ch == '\\') { - if ((ch = in.getc()) == -1) { - return false; - } - switch (ch) { -#define MAP(sym, val) case sym: out.push_back(val); break - MAP('"', '\"'); - MAP('\\', '\\'); - MAP('/', '/'); - MAP('b', '\b'); - MAP('f', '\f'); - MAP('n', '\n'); - MAP('r', '\r'); - MAP('t', '\t'); -#undef MAP - case 'u': - if (! _parse_codepoint(out, in)) { - return false; - } - break; - default: - return false; - } - } else { - out.push_back(ch); - } - } - return false; - } - - template inline bool _parse_array(Context& ctx, input& in) { - if (! ctx.parse_array_start()) { - return false; - } - size_t idx = 0; - if (in.expect(']')) { - return ctx.parse_array_stop(idx); - } - do { - if (! ctx.parse_array_item(in, idx)) { - return false; - } - idx++; - } while (in.expect(',')); - return in.expect(']') && ctx.parse_array_stop(idx); - } - - template inline bool _parse_object(Context& ctx, input& in) { - if (! ctx.parse_object_start()) { - return false; - } - if (in.expect('}')) { - return true; - } - do { - std::string key; - if (! in.expect('"') - || ! _parse_string(key, in) - || ! in.expect(':')) { - return false; - } - if (! ctx.parse_object_item(in, key)) { - return false; - } - } while (in.expect(',')); - return in.expect('}'); - } - - template inline std::string _parse_number(input& in) { - std::string num_str; - while (1) { - int ch = in.getc(); - if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' - || ch == 'e' || ch == 'E') { - num_str.push_back(ch); - } else if (ch == '.') { -#if PICOJSON_USE_LOCALE - num_str += localeconv()->decimal_point; -#else - num_str.push_back('.'); -#endif - } else { - in.ungetc(); - break; - } - } - return num_str; - } - - template inline bool _parse(Context& ctx, input& in) { - in.skip_ws(); - int ch = in.getc(); - switch (ch) { -#define IS(ch, text, op) case ch: \ - if (in.match(text) && op) { \ - return true; \ - } else { \ - return false; \ - } - IS('n', "ull", ctx.set_null()); - IS('f', "alse", ctx.set_bool(false)); - IS('t', "rue", ctx.set_bool(true)); -#undef IS - case '"': - return ctx.parse_string(in); - case '[': - return _parse_array(ctx, in); - case '{': - return _parse_object(ctx, in); - default: - if (('0' <= ch && ch <= '9') || ch == '-') { - double f; - char *endp; - in.ungetc(); - std::string num_str = _parse_number(in); - if (num_str.empty()) { - return false; - } -#ifdef PICOJSON_USE_INT64 - { - errno = 0; - intmax_t ival = strtoimax(num_str.c_str(), &endp, 10); - if (errno == 0 - && std::numeric_limits::min() <= ival - && ival <= std::numeric_limits::max() - && endp == num_str.c_str() + num_str.size()) { - ctx.set_int64(ival); - return true; - } - } -#endif - f = strtod(num_str.c_str(), &endp); - if (endp == num_str.c_str() + num_str.size()) { - ctx.set_number(f); - return true; - } - return false; - } - break; - } - in.ungetc(); - return false; - } - - class deny_parse_context { - public: - bool set_null() { return false; } - bool set_bool(bool) { return false; } -#ifdef PICOJSON_USE_INT64 - bool set_int64(int64_t) { return false; } -#endif - bool set_number(double) { return false; } - template bool parse_string(input&) { return false; } - bool parse_array_start() { return false; } - template bool parse_array_item(input&, size_t) { - return false; - } - bool parse_array_stop(size_t) { return false; } - bool parse_object_start() { return false; } - template bool parse_object_item(input&, const std::string&) { - return false; - } - }; - - class default_parse_context { - protected: - value* out_; - public: - default_parse_context(value* out) : out_(out) {} - bool set_null() { - *out_ = value(); - return true; - } - bool set_bool(bool b) { - *out_ = value(b); - return true; - } -#ifdef PICOJSON_USE_INT64 - bool set_int64(int64_t i) { - *out_ = value(i); - return true; - } -#endif - bool set_number(double f) { - *out_ = value(f); - return true; - } - template bool parse_string(input& in) { - *out_ = value(string_type, false); - return _parse_string(out_->get(), in); - } - bool parse_array_start() { - *out_ = value(array_type, false); - return true; - } - template bool parse_array_item(input& in, size_t) { - array& a = out_->get(); - a.push_back(value()); - default_parse_context ctx(&a.back()); - return _parse(ctx, in); - } - bool parse_array_stop(size_t) { return true; } - bool parse_object_start() { - *out_ = value(object_type, false); - return true; - } - template bool parse_object_item(input& in, const std::string& key) { - object& o = out_->get(); - default_parse_context ctx(&o[key]); - return _parse(ctx, in); - } - private: - default_parse_context(const default_parse_context&); - default_parse_context& operator=(const default_parse_context&); - }; - - class null_parse_context { - public: - struct dummy_str { - void push_back(int) {} - }; - public: - null_parse_context() {} - bool set_null() { return true; } - bool set_bool(bool) { return true; } -#ifdef PICOJSON_USE_INT64 - bool set_int64(int64_t) { return true; } -#endif - bool set_number(double) { return true; } - template bool parse_string(input& in) { - dummy_str s; - return _parse_string(s, in); - } - bool parse_array_start() { return true; } - template bool parse_array_item(input& in, size_t) { - return _parse(*this, in); - } - bool parse_array_stop(size_t) { return true; } - bool parse_object_start() { return true; } - template bool parse_object_item(input& in, const std::string&) { - return _parse(*this, in); - } - private: - null_parse_context(const null_parse_context&); - null_parse_context& operator=(const null_parse_context&); - }; - - // obsolete, use the version below - template inline std::string parse(value& out, Iter& pos, const Iter& last) { - std::string err; - pos = parse(out, pos, last, &err); - return err; - } - - template inline Iter _parse(Context& ctx, const Iter& first, const Iter& last, std::string* err) { - input in(first, last); - if (! _parse(ctx, in) && err != NULL) { - char buf[64]; - SNPRINTF(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); - *err = buf; - while (1) { - int ch = in.getc(); - if (ch == -1 || ch == '\n') { - break; - } else if (ch >= ' ') { - err->push_back(ch); - } - } - } - return in.cur(); - } - - template inline Iter parse(value& out, const Iter& first, const Iter& last, std::string* err) { - default_parse_context ctx(&out); - return _parse(ctx, first, last, err); - } - - inline std::string parse(value& out, const std::string& s) { - std::string err; - parse(out, s.begin(), s.end(), &err); - return err; - } - - inline std::string parse(value& out, std::istream& is) { - std::string err; - parse(out, std::istreambuf_iterator(is.rdbuf()), - std::istreambuf_iterator(), &err); - return err; - } - - template struct last_error_t { - static std::string s; - }; - template std::string last_error_t::s; - - inline void set_last_error(const std::string& s) { - last_error_t::s = s; - } - - inline const std::string& get_last_error() { - return last_error_t::s; - } - - inline bool operator==(const value& x, const value& y) { - if (x.is()) - return y.is(); -#define PICOJSON_CMP(type) \ - if (x.is()) \ - return y.is() && x.get() == y.get() - PICOJSON_CMP(bool); - PICOJSON_CMP(double); - PICOJSON_CMP(std::string); - PICOJSON_CMP(array); - PICOJSON_CMP(object); -#undef PICOJSON_CMP - PICOJSON_ASSERT(0); -#ifdef _MSC_VER - __assume(0); -#endif - return false; - } - - inline bool operator!=(const value& x, const value& y) { - return ! (x == y); - } -} - -#if !PICOJSON_USE_RVALUE_REFERENCE -namespace std { - template<> inline void swap(picojson::value& x, picojson::value& y) - { - x.swap(y); - } -} -#endif - -inline std::istream& operator>>(std::istream& is, picojson::value& x) -{ - picojson::set_last_error(std::string()); - std::string err = picojson::parse(x, is); - if (! err.empty()) { - picojson::set_last_error(err); - is.setstate(std::ios::failbit); - } - return is; -} - -inline std::ostream& operator<<(std::ostream& os, const picojson::value& x) -{ - x.serialize(std::ostream_iterator(os)); - return os; -} -#ifdef _MSC_VER - #pragma warning(pop) -#endif - -#endif diff --git a/third_party/tinygltfloader/tiny_gltf_loader.h b/third_party/tinygltfloader/tiny_gltf_loader.h deleted file mode 100644 index 4792721120..0000000000 --- a/third_party/tinygltfloader/tiny_gltf_loader.h +++ /dev/null @@ -1,2656 +0,0 @@ -// -// Tiny glTF loader. -// -// -// The MIT License (MIT) -// -// Copyright (c) 2015 - 2016 Syoyo Fujita and many contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// Version: -// - v0.9.5 Support parsing `extras` parameter. -// - v0.9.4 Support parsing `shader`, `program` and `tecnique` thanks to -// @lukesanantonio -// - v0.9.3 Support binary glTF -// - v0.9.2 Support parsing `texture` -// - v0.9.1 Support loading glTF asset from memory -// - v0.9.0 Initial -// -// Tiny glTF loader is using following third party libraries: -// -// - picojson: C++ JSON library. -// - base64: base64 decode/encode library. -// - stb_image: Image loading library. -// -#ifndef TINY_GLTF_LOADER_H_ -#define TINY_GLTF_LOADER_H_ - -#include -#include -#include -#include -#include - -namespace tinygltf { - -#define TINYGLTF_MODE_POINTS (0) -#define TINYGLTF_MODE_LINE (1) -#define TINYGLTF_MODE_LINE_LOOP (2) -#define TINYGLTF_MODE_TRIANGLES (4) -#define TINYGLTF_MODE_TRIANGLE_STRIP (5) -#define TINYGLTF_MODE_TRIANGLE_FAN (6) - -#define TINYGLTF_COMPONENT_TYPE_BYTE (5120) -#define TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE (5121) -#define TINYGLTF_COMPONENT_TYPE_SHORT (5122) -#define TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT (5123) -#define TINYGLTF_COMPONENT_TYPE_INT (5124) -#define TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT (5125) -#define TINYGLTF_COMPONENT_TYPE_FLOAT (5126) -#define TINYGLTF_COMPONENT_TYPE_DOUBLE (5127) - -#define TINYGLTF_TEXTURE_FILTER_NEAREST (9728) -#define TINYGLTF_TEXTURE_FILTER_LINEAR (9729) -#define TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST (9984) -#define TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST (9985) -#define TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR (9986) -#define TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR (9987) - -#define TINYGLTF_TEXTURE_WRAP_RPEAT (10497) -#define TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE (33071) -#define TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT (33648) - -// Redeclarations of the above for technique.parameters. -#define TINYGLTF_PARAMETER_TYPE_BYTE (5120) -#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE (5121) -#define TINYGLTF_PARAMETER_TYPE_SHORT (5122) -#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT (5123) -#define TINYGLTF_PARAMETER_TYPE_INT (5124) -#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT (5125) -#define TINYGLTF_PARAMETER_TYPE_FLOAT (5126) - -#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC2 (35664) -#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC3 (35665) -#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC4 (35666) - -#define TINYGLTF_PARAMETER_TYPE_INT_VEC2 (35667) -#define TINYGLTF_PARAMETER_TYPE_INT_VEC3 (35668) -#define TINYGLTF_PARAMETER_TYPE_INT_VEC4 (35669) - -#define TINYGLTF_PARAMETER_TYPE_BOOL (35670) -#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC2 (35671) -#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC3 (35672) -#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC4 (35673) - -#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT2 (35674) -#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT3 (35675) -#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT4 (35676) - -#define TINYGLTF_PARAMETER_TYPE_SAMPLER_2D (35678) - -// End parameter types - -#define TINYGLTF_TYPE_VEC2 (2) -#define TINYGLTF_TYPE_VEC3 (3) -#define TINYGLTF_TYPE_VEC4 (4) -#define TINYGLTF_TYPE_MAT2 (32 + 2) -#define TINYGLTF_TYPE_MAT3 (32 + 3) -#define TINYGLTF_TYPE_MAT4 (32 + 4) -#define TINYGLTF_TYPE_SCALAR (64 + 1) -#define TINYGLTF_TYPE_VECTOR (64 + 4) -#define TINYGLTF_TYPE_MATRIX (64 + 16) - -#define TINYGLTF_IMAGE_FORMAT_JPEG (0) -#define TINYGLTF_IMAGE_FORMAT_PNG (1) -#define TINYGLTF_IMAGE_FORMAT_BMP (2) -#define TINYGLTF_IMAGE_FORMAT_GIF (3) - -#define TINYGLTF_TEXTURE_FORMAT_ALPHA (6406) -#define TINYGLTF_TEXTURE_FORMAT_RGB (6407) -#define TINYGLTF_TEXTURE_FORMAT_RGBA (6408) -#define TINYGLTF_TEXTURE_FORMAT_LUMINANCE (6409) -#define TINYGLTF_TEXTURE_FORMAT_LUMINANCE_ALPHA (6410) - -#define TINYGLTF_TEXTURE_TARGET_TEXTURE2D (3553) -#define TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE (5121) - -#define TINYGLTF_TARGET_ARRAY_BUFFER (34962) -#define TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER (34963) - -#define TINYGLTF_SHADER_TYPE_VERTEX_SHADER (35633) -#define TINYGLTF_SHADER_TYPE_FRAGMENT_SHADER (35632) - -typedef enum { - NULL_TYPE = 0, - NUMBER_TYPE = 1, - INT_TYPE = 2, - BOOL_TYPE = 3, - STRING_TYPE = 4, - ARRAY_TYPE = 5, - BINARY_TYPE = 6, - OBJECT_TYPE = 7 -} Type; - -// Simple class to represent JSON object -class Value { - public: - typedef std::vector Array; - typedef std::map Object; - - Value() : type_(NULL_TYPE) {} - - explicit Value(bool b) : type_(BOOL_TYPE) { boolean_value_ = b; } - explicit Value(int i) : type_(INT_TYPE) { int_value_ = i; } - explicit Value(double n) : type_(NUMBER_TYPE) { number_value_ = n; } - explicit Value(const std::string &s) : type_(STRING_TYPE) { - string_value_ = s; - } - explicit Value(const unsigned char *p, size_t n) : type_(BINARY_TYPE) { - binary_value_.resize(n); - memcpy(binary_value_.data(), p, n); - } - explicit Value(const Array &a) : type_(ARRAY_TYPE) { - array_value_ = Array(a); - } - explicit Value(const Object &o) : type_(OBJECT_TYPE) { - object_value_ = Object(o); - } - - char Type() const { return static_cast(type_); } - - bool IsBool() const { return (type_ == BOOL_TYPE); } - - bool IsInt() const { return (type_ == INT_TYPE); } - - bool IsNumber() const { return (type_ == NUMBER_TYPE); } - - bool IsString() const { return (type_ == STRING_TYPE); } - - bool IsBinary() const { return (type_ == BINARY_TYPE); } - - bool IsArray() const { return (type_ == ARRAY_TYPE); } - - bool IsObject() const { return (type_ == OBJECT_TYPE); } - - // Accessor - template - const T &Get() const; - template - T &Get(); - - // Lookup value from an array - const Value &Get(int idx) const { - static Value &null_value = *(new Value()); - assert(IsArray()); - assert(idx >= 0); - return (static_cast(idx) < array_value_.size()) - ? array_value_[static_cast(idx)] - : null_value; - } - - // Lookup value from a key-value pair - const Value &Get(const std::string &key) const { - static Value &null_value = *(new Value()); - assert(IsObject()); - Object::const_iterator it = object_value_.find(key); - return (it != object_value_.end()) ? it->second : null_value; - } - - size_t ArrayLen() const { - if (!IsArray()) return 0; - return array_value_.size(); - } - - // Valid only for object type. - bool Has(const std::string &key) const { - if (!IsObject()) return false; - Object::const_iterator it = object_value_.find(key); - return (it != object_value_.end()) ? true : false; - } - - // List keys - std::vector Keys() const { - std::vector keys; - if (!IsObject()) return keys; // empty - - for (Object::const_iterator it = object_value_.begin(); - it != object_value_.end(); ++it) { - keys.push_back(it->first); - } - - return keys; - } - - protected: - int type_; - - int int_value_; - double number_value_; - std::string string_value_; - std::vector binary_value_; - Array array_value_; - Object object_value_; - bool boolean_value_; - char pad[3]; - - int pad0; -}; - -#define TINYGLTF_VALUE_GET(ctype, var) \ - template <> \ - inline const ctype &Value::Get() const { \ - return var; \ - } \ - template <> \ - inline ctype &Value::Get() { \ - return var; \ - } -TINYGLTF_VALUE_GET(bool, boolean_value_) -TINYGLTF_VALUE_GET(double, number_value_) -TINYGLTF_VALUE_GET(int, int_value_) -TINYGLTF_VALUE_GET(std::string, string_value_) -TINYGLTF_VALUE_GET(std::vector, binary_value_) -TINYGLTF_VALUE_GET(Value::Array, array_value_) -TINYGLTF_VALUE_GET(Value::Object, object_value_) -#undef TINYGLTF_VALUE_GET - -typedef struct { - std::string string_value; - std::vector number_array; -} Parameter; - -typedef std::map ParameterMap; - -typedef struct { - std::string sampler; - std::string target_id; - std::string target_path; - Value extras; -} AnimationChannel; - -typedef struct { - std::string input; - std::string interpolation; - std::string output; - Value extras; -} AnimationSampler; - -typedef struct { - std::string name; - std::vector channels; - std::map samplers; - ParameterMap parameters; - Value extras; -} Animation; - -typedef struct { - std::string name; - int minFilter; - int magFilter; - int wrapS; - int wrapT; - int wrapR; // TinyGLTF extension - int pad0; - Value extras; -} Sampler; - -typedef struct { - std::string name; - int width; - int height; - int component; - int pad0; - std::vector image; - - std::string bufferView; // KHR_binary_glTF extenstion. - std::string mimeType; // KHR_binary_glTF extenstion. - - Value extras; -} Image; - -typedef struct { - int format; - int internalFormat; - std::string sampler; // Required - std::string source; // Required - int target; - int type; - std::string name; - Value extras; -} Texture; - -typedef struct { - std::string name; - std::string technique; - ParameterMap values; - - Value extras; -} Material; - -typedef struct { - std::string name; - std::string buffer; // Required - size_t byteOffset; // Required - size_t byteLength; // default: 0 - int target; - int pad0; - Value extras; -} BufferView; - -typedef struct { - std::string bufferView; - std::string name; - size_t byteOffset; - size_t byteStride; - int componentType; // One of TINYGLTF_COMPONENT_TYPE_*** - int pad0; - size_t count; - int type; // One of TINYGLTF_TYPE_*** - int pad1; - std::vector minValues; // Optional - std::vector maxValues; // Optional - Value extras; -} Accessor; - -class Camera { - public: - Camera() {} - ~Camera() {} - - std::string name; - bool isOrthographic; // false = perspective. - - // Some common properties. - float aspectRatio; - float yFov; - float zFar; - float zNear; -}; - -typedef struct { - std::map attributes; // A dictionary object of - // strings, where each string - // is the ID of the accessor - // containing an attribute. - std::string material; // The ID of the material to apply to this primitive - // when rendering. - std::string indices; // The ID of the accessor that contains the indices. - int mode; // one of TINYGLTF_MODE_*** - int pad0; - - Value extras; // "extra" property -} Primitive; - -typedef struct { - std::string name; - std::vector primitives; - Value extras; -} Mesh; - -class Node { - public: - Node() {} - ~Node() {} - - std::string camera; // camera object referenced by this node. - - std::string name; - std::vector children; - std::vector rotation; // length must be 0 or 4 - std::vector scale; // length must be 0 or 3 - std::vector translation; // length must be 0 or 3 - std::vector matrix; // length must be 0 or 16 - std::vector meshes; - - Value extras; -}; - -typedef struct { - std::string name; - std::vector data; - Value extras; -} Buffer; - -typedef struct { - std::string name; - int type; - int pad0; - std::vector source; - - Value extras; -} Shader; - -typedef struct { - std::string name; - std::string vertexShader; - std::string fragmentShader; - std::vector attributes; - - Value extras; -} Program; - -typedef struct { - int count; - int pad0; - std::string node; - std::string semantic; - int type; - int pad1; - Parameter value; -} TechniqueParameter; - -typedef struct { - std::string name; - std::string program; - std::map parameters; - std::map attributes; - std::map uniforms; - - Value extras; -} Technique; - -typedef struct { - std::string generator; - std::string version; - std::string profile_api; - std::string profile_version; - bool premultipliedAlpha; - char pad[7]; - Value extras; -} Asset; - -class Scene { - public: - Scene() {} - ~Scene() {} - - std::map accessors; - std::map animations; - std::map buffers; - std::map bufferViews; - std::map materials; - std::map meshes; - std::map nodes; - std::map textures; - std::map images; - std::map shaders; - std::map programs; - std::map techniques; - std::map samplers; - std::map > scenes; // list of nodes - - std::string defaultScene; - - Asset asset; - - Value extras; -}; - -enum SectionCheck { - NO_REQUIRE = 0x00, - REQUIRE_SCENE = 0x01, - REQUIRE_SCENES = 0x02, - REQUIRE_NODES = 0x04, - REQUIRE_ACCESSORS = 0x08, - REQUIRE_BUFFERS = 0x10, - REQUIRE_BUFFER_VIEWS = 0x20, - REQUIRE_ALL = 0x3f -}; - -class TinyGLTFLoader { - public: - TinyGLTFLoader() : bin_data_(NULL), bin_size_(0), is_binary_(false) { - pad[0] = pad[1] = pad[2] = pad[3] = pad[4] = pad[5] = pad[6] = 0; - } - ~TinyGLTFLoader() {} - - /// Loads glTF ASCII asset from a file. - /// Returns false and set error string to `err` if there's an error. - bool LoadASCIIFromFile(Scene *scene, std::string *err, - const std::string &filename, - unsigned int check_sections = REQUIRE_ALL); - - /// Loads glTF ASCII asset from string(memory). - /// `length` = strlen(str); - /// Returns false and set error string to `err` if there's an error. - bool LoadASCIIFromString(Scene *scene, std::string *err, const char *str, - const unsigned int length, - const std::string &base_dir, - unsigned int check_sections = REQUIRE_ALL); - - /// Loads glTF binary asset from a file. - /// Returns false and set error string to `err` if there's an error. - bool LoadBinaryFromFile(Scene *scene, std::string *err, - const std::string &filename, - unsigned int check_sections = REQUIRE_ALL); - - /// Loads glTF binary asset from memory. - /// `length` = strlen(str); - /// Returns false and set error string to `err` if there's an error. - bool LoadBinaryFromMemory(Scene *scene, std::string *err, - const unsigned char *bytes, - const unsigned int length, - const std::string &base_dir = "", - unsigned int check_sections = REQUIRE_ALL); - - private: - /// Loads glTF asset from string(memory). - /// `length` = strlen(str); - /// Returns false and set error string to `err` if there's an error. - bool LoadFromString(Scene *scene, std::string *err, const char *str, - const unsigned int length, const std::string &base_dir, - unsigned int check_sections); - - const unsigned char *bin_data_; - size_t bin_size_; - bool is_binary_; - char pad[7]; -}; - -} // namespace tinygltf - -#ifdef TINYGLTF_LOADER_IMPLEMENTATION -#include -//#include -#include -#include - -#ifdef __clang__ -// Disable some warnings for external files. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wfloat-equal" -#pragma clang diagnostic ignored "-Wexit-time-destructors" -#pragma clang diagnostic ignored "-Wconversion" -#pragma clang diagnostic ignored "-Wold-style-cast" -#pragma clang diagnostic ignored "-Wdouble-promotion" -#pragma clang diagnostic ignored "-Wglobal-constructors" -#pragma clang diagnostic ignored "-Wreserved-id-macro" -#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" -#pragma clang diagnostic ignored "-Wpadded" -#endif - -#define PICOJSON_USE_INT64 -#include "picojson/picojson.h" -#include "stb/stb_image.h" -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -#ifdef _WIN32 -#include -#else -#include -#endif - -#if defined(__sparcv9) -// Big endian -#else -#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU -#define TINYGLTF_LITTLE_ENDIAN 1 -#endif -#endif - -namespace tinygltf { - -static void swap4(unsigned int *val) { -#ifdef TINYGLTF_LITTLE_ENDIAN - (void)val; -#else - unsigned int tmp = *val; - unsigned char *dst = reinterpret_cast(val); - unsigned char *src = reinterpret_cast(&tmp); - - dst[0] = src[3]; - dst[1] = src[2]; - dst[2] = src[1]; - dst[3] = src[0]; -#endif -} - -static bool FileExists(const std::string &abs_filename) { - bool ret; -#ifdef _WIN32 - FILE *fp; - errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb"); - if (err != 0) { - return false; - } -#else - FILE *fp = fopen(abs_filename.c_str(), "rb"); -#endif - if (fp) { - ret = true; - fclose(fp); - } else { - ret = false; - } - - return ret; -} - -static std::string ExpandFilePath(const std::string &filepath) { -#ifdef _WIN32 - DWORD len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0); - char *str = new char[len]; - ExpandEnvironmentStringsA(filepath.c_str(), str, len); - - std::string s(str); - - delete[] str; - - return s; -#else - -#if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) - // no expansion - std::string s = filepath; -#else - std::string s; - wordexp_t p; - - if (filepath.empty()) { - return ""; - } - - // char** w; - int ret = wordexp(filepath.c_str(), &p, 0); - if (ret) { - // err - s = filepath; - return s; - } - - // Use first element only. - if (p.we_wordv) { - s = std::string(p.we_wordv[0]); - wordfree(&p); - } else { - s = filepath; - } - -#endif - - return s; -#endif -} - -static std::string JoinPath(const std::string &path0, - const std::string &path1) { - if (path0.empty()) { - return path1; - } else { - // check '/' - char lastChar = *path0.rbegin(); - if (lastChar != '/') { - return path0 + std::string("/") + path1; - } else { - return path0 + path1; - } - } -} - -static std::string FindFile(const std::vector &paths, - const std::string &filepath) { - for (size_t i = 0; i < paths.size(); i++) { - std::string absPath = ExpandFilePath(JoinPath(paths[i], filepath)); - if (FileExists(absPath)) { - return absPath; - } - } - - return std::string(); -} - -// std::string GetFilePathExtension(const std::string& FileName) -//{ -// if(FileName.find_last_of(".") != std::string::npos) -// return FileName.substr(FileName.find_last_of(".")+1); -// return ""; -//} - -static std::string GetBaseDir(const std::string &filepath) { - if (filepath.find_last_of("/\\") != std::string::npos) - return filepath.substr(0, filepath.find_last_of("/\\")); - return ""; -} - -// std::string base64_encode(unsigned char const* , unsigned int len); -std::string base64_decode(std::string const &s); - -/* - base64.cpp and base64.h - - Copyright (C) 2004-2008 RenĂ© Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - RenĂ© Nyffenegger rene.nyffenegger@adp-gmbh.ch - -*/ - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wexit-time-destructors" -#pragma clang diagnostic ignored "-Wglobal-constructors" -#pragma clang diagnostic ignored "-Wsign-conversion" -#pragma clang diagnostic ignored "-Wconversion" -#endif -static const std::string base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - -static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); -} - -std::string base64_decode(std::string const &encoded_string) { - int in_len = static_cast(encoded_string.size()); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; - - while (in_len-- && (encoded_string[in_] != '=') && - is_base64(encoded_string[in_])) { - char_array_4[i++] = encoded_string[in_]; - in_++; - if (i == 4) { - for (i = 0; i < 4; i++) - char_array_4[i] = - static_cast(base64_chars.find(char_array_4[i])); - - char_array_3[0] = - (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = - ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (i = 0; (i < 3); i++) ret += char_array_3[i]; - i = 0; - } - } - - if (i) { - for (j = i; j < 4; j++) char_array_4[j] = 0; - - for (j = 0; j < 4; j++) - char_array_4[j] = - static_cast(base64_chars.find(char_array_4[j])); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = - ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; - } - - return ret; -} -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -static bool LoadExternalFile(std::vector *out, std::string *err, - const std::string &filename, - const std::string &basedir, size_t reqBytes, - bool checkSize) { - out->clear(); - - std::vector paths; - paths.push_back(basedir); - paths.push_back("."); - - std::string filepath = FindFile(paths, filename); - if (filepath.empty()) { - if (err) { - (*err) += "File not found : " + filename + "\n"; - } - return false; - } - - std::ifstream f(filepath.c_str(), std::ifstream::binary); - if (!f) { - if (err) { - (*err) += "File open error : " + filepath + "\n"; - } - return false; - } - - f.seekg(0, f.end); - size_t sz = static_cast(f.tellg()); - std::vector buf(sz); - - f.seekg(0, f.beg); - f.read(reinterpret_cast(&buf.at(0)), - static_cast(sz)); - f.close(); - - if (checkSize) { - if (reqBytes == sz) { - out->swap(buf); - return true; - } else { - std::stringstream ss; - ss << "File size mismatch : " << filepath << ", requestedBytes " - << reqBytes << ", but got " << sz << std::endl; - if (err) { - (*err) += ss.str(); - } - return false; - } - } - - out->swap(buf); - return true; -} - -static bool LoadImageData(Image *image, std::string *err, int req_width, - int req_height, const unsigned char *bytes, - int size) { - int w, h, comp; - unsigned char *data = stbi_load_from_memory(bytes, size, &w, &h, &comp, 0); - if (!data) { - if (err) { - (*err) += "Unknown image format.\n"; - } - return false; - } - - if (w < 1 || h < 1) { - free(data); - if (err) { - (*err) += "Unknown image format.\n"; - } - return false; - } - - if (req_width > 0) { - if (req_width != w) { - free(data); - if (err) { - (*err) += "Image width mismatch.\n"; - } - return false; - } - } - - if (req_height > 0) { - if (req_height != h) { - free(data); - if (err) { - (*err) += "Image height mismatch.\n"; - } - return false; - } - } - - image->width = w; - image->height = h; - image->component = comp; - image->image.resize(static_cast(w * h * comp)); - std::copy(data, data + w * h * comp, image->image.begin()); - - free(data); - - return true; -} - -static bool IsDataURI(const std::string &in) { - std::string header = "data:application/octet-stream;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:image/png;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:image/jpeg;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:text/plain;base64,"; - if (in.find(header) == 0) { - return true; - } - - return false; -} - -static bool DecodeDataURI(std::vector *out, - const std::string &in, size_t reqBytes, - bool checkSize) { - std::string header = "data:application/octet-stream;base64,"; - std::string data; - if (in.find(header) == 0) { - data = base64_decode(in.substr(header.size())); // cut mime string. - } - - if (data.empty()) { - header = "data:image/jpeg;base64,"; - if (in.find(header) == 0) { - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } - - if (data.empty()) { - header = "data:image/png;base64,"; - if (in.find(header) == 0) { - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } - - if (data.empty()) { - header = "data:text/plain;base64,"; - if (in.find(header) == 0) { - data = base64_decode(in.substr(header.size())); - } - } - - if (data.empty()) { - return false; - } - - if (checkSize) { - if (data.size() != reqBytes) { - return false; - } - out->resize(reqBytes); - } else { - out->resize(data.size()); - } - std::copy(data.begin(), data.end(), out->begin()); - return true; -} - -static void ParseObjectProperty(Value *ret, const picojson::object &o) { - tinygltf::Value::Object vo; - picojson::object::const_iterator it(o.begin()); - picojson::object::const_iterator itEnd(o.end()); - - for (; it != itEnd; it++) { - picojson::value v = it->second; - - if (v.is()) { - vo[it->first] = tinygltf::Value(v.get()); - } else if (v.is()) { - vo[it->first] = tinygltf::Value(v.get()); - } else if (v.is()) { - vo[it->first] = - tinygltf::Value(static_cast(v.get())); // truncate - } else if (v.is()) { - vo[it->first] = tinygltf::Value(v.get()); - } else if (v.is()) { - tinygltf::Value child_value; - ParseObjectProperty(&child_value, v.get()); - } - // TODO(syoyo) binary, array - } - - (*ret) = tinygltf::Value(vo); -} - -static bool ParseExtrasProperty(Value *ret, const picojson::object &o) { - picojson::object::const_iterator it = o.find("extras"); - if (it == o.end()) { - return false; - } - - // FIXME(syoyo) Currently we only support `object` type for extras property. - if (!it->second.is()) { - return false; - } - - ParseObjectProperty(ret, it->second.get()); - - return true; -} - -static bool ParseBooleanProperty(bool *ret, std::string *err, - const picojson::object &o, - const std::string &property, bool required) { - picojson::object::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing.\n"; - } - } - return false; - } - - if (!it->second.is()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a bool type.\n"; - } - } - return false; - } - - if (ret) { - (*ret) = it->second.get(); - } - - return true; -} - -static bool ParseNumberProperty(double *ret, std::string *err, - const picojson::object &o, - const std::string &property, bool required) { - picojson::object::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing.\n"; - } - } - return false; - } - - if (!it->second.is()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a number type.\n"; - } - } - return false; - } - - if (ret) { - (*ret) = it->second.get(); - } - - return true; -} - -static bool ParseNumberArrayProperty(std::vector *ret, std::string *err, - const picojson::object &o, - const std::string &property, - bool required) { - picojson::object::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing.\n"; - } - } - return false; - } - - if (!it->second.is()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not an array.\n"; - } - } - return false; - } - - ret->clear(); - const picojson::array &arr = it->second.get(); - for (size_t i = 0; i < arr.size(); i++) { - if (!arr[i].is()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a number.\n"; - } - } - return false; - } - ret->push_back(arr[i].get()); - } - - return true; -} - -static bool ParseStringProperty( - std::string *ret, std::string *err, const picojson::object &o, - const std::string &property, bool required, - const std::string &parent_node = std::string()) { - picojson::object::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (parent_node.empty()) { - (*err) += ".\n"; - } else { - (*err) += " in `" + parent_node + "'.\n"; - } - } - } - return false; - } - - if (!it->second.is()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a string type.\n"; - } - } - return false; - } - - if (ret) { - (*ret) = it->second.get(); - } - - return true; -} - -static bool ParseStringArrayProperty(std::vector *ret, - std::string *err, - const picojson::object &o, - const std::string &property, - bool required) { - picojson::object::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing.\n"; - } - } - return false; - } - - if (!it->second.is()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not an array.\n"; - } - } - return false; - } - - ret->clear(); - const picojson::array &arr = it->second.get(); - for (size_t i = 0; i < arr.size(); i++) { - if (!arr[i].is()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a string.\n"; - } - } - return false; - } - ret->push_back(arr[i].get()); - } - - return true; -} - -static bool ParseStringMapProperty(std::map *ret, - std::string *err, const picojson::object &o, - const std::string &property, bool required) { - picojson::object::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing.\n"; - } - } - return false; - } - - // Make sure we are dealing with an object / dictionary. - if (!it->second.is()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not an object.\n"; - } - } - return false; - } - - ret->clear(); - const picojson::object &dict = it->second.get(); - - picojson::object::const_iterator dictIt(dict.begin()); - picojson::object::const_iterator dictItEnd(dict.end()); - - for (; dictIt != dictItEnd; ++dictIt) { - // Check that the value is a string. - if (!dictIt->second.is()) { - if (required) { - if (err) { - (*err) += "'" + property + "' value is not a string.\n"; - } - } - return false; - } - - // Insert into the list. - (*ret)[dictIt->first] = dictIt->second.get(); - } - return true; -} - -static bool ParseKHRBinaryExtension(const picojson::object &o, std::string *err, - std::string *buffer_view, - std::string *mime_type, int *image_width, - int *image_height) { - picojson::object j = o; - - if (j.find("extensions") == j.end()) { - if (err) { - (*err) += "`extensions' property is missing.\n"; - } - return false; - } - - if (!(j["extensions"].is())) { - if (err) { - (*err) += "Invalid `extensions' property.\n"; - } - return false; - } - - picojson::object ext = j["extensions"].get(); - - if (ext.find("KHR_binary_glTF") == ext.end()) { - if (err) { - (*err) += - "`KHR_binary_glTF' property is missing in extension property.\n"; - } - return false; - } - - if (!(ext["KHR_binary_glTF"].is())) { - if (err) { - (*err) += "Invalid `KHR_binary_glTF' property.\n"; - } - return false; - } - - picojson::object k = ext["KHR_binary_glTF"].get(); - - if (!ParseStringProperty(buffer_view, err, k, "bufferView", true)) { - return false; - } - - if (mime_type) { - ParseStringProperty(mime_type, err, k, "mimeType", false); - } - - if (image_width) { - double width = 0.0; - if (ParseNumberProperty(&width, err, k, "width", false)) { - (*image_width) = static_cast(width); - } - } - - if (image_height) { - double height = 0.0; - if (ParseNumberProperty(&height, err, k, "height", false)) { - (*image_height) = static_cast(height); - } - } - - return true; -} - -static bool ParseAsset(Asset *asset, std::string *err, - const picojson::object &o) { - ParseStringProperty(&asset->generator, err, o, "generator", false); - ParseBooleanProperty(&asset->premultipliedAlpha, err, o, "premultipliedAlpha", - false); - - ParseStringProperty(&asset->version, err, o, "version", false); - - picojson::object::const_iterator profile = o.find("profile"); - if (profile != o.end()) { - const picojson::value &v = profile->second; - if (v.contains("api") & v.get("api").is()) { - asset->profile_api = v.get("api").get(); - } - if (v.contains("version") & v.get("version").is()) { - asset->profile_version = v.get("version").get(); - } - } - - return true; -} - -static bool ParseImage(Image *image, std::string *err, - const picojson::object &o, const std::string &basedir, - bool is_binary, const unsigned char *bin_data, - size_t bin_size) { - std::string uri; - if (!ParseStringProperty(&uri, err, o, "uri", true)) { - return false; - } - - ParseStringProperty(&image->name, err, o, "name", false); - - std::vector img; - - if (is_binary) { - // Still binary glTF accepts external dataURI. First try external resources. - bool loaded = false; - if (IsDataURI(uri)) { - loaded = DecodeDataURI(&img, uri, 0, false); - } else { - // Assume external .bin file. - loaded = LoadExternalFile(&img, err, uri, basedir, 0, false); - } - - if (!loaded) { - // load data from (embedded) binary data - - if ((bin_size == 0) || (bin_data == NULL)) { - if (err) { - (*err) += "Invalid binary data.\n"; - } - return false; - } - - // There should be "extensions" property. - // "extensions":{"KHR_binary_glTF":{"bufferView": "id", ... - - std::string buffer_view; - std::string mime_type; - int image_width; - int image_height; - bool ret = ParseKHRBinaryExtension(o, err, &buffer_view, &mime_type, - &image_width, &image_height); - if (!ret) { - return false; - } - - if (uri.compare("data:,") == 0) { - // ok - } else { - if (err) { - (*err) += "Invalid URI for binary data.\n"; - } - return false; - } - - // Just only save some information here. Loading actual image data from - // bufferView is done in other place. - image->bufferView = buffer_view; - image->mimeType = mime_type; - image->width = image_width; - image->height = image_height; - - return true; - } - } else { - if (IsDataURI(uri)) { - if (!DecodeDataURI(&img, uri, 0, false)) { - if (err) { - (*err) += "Failed to decode 'uri' for image parameter.\n"; - } - return false; - } - } else { - // Assume external file - if (!LoadExternalFile(&img, err, uri, basedir, 0, false)) { - if (err) { - (*err) += "Failed to load external 'uri'. for image parameter\n"; - } - return false; - } - if (img.empty()) { - if (err) { - (*err) += "Image is empty.\n"; - } - return false; - } - } - } - - return LoadImageData(image, err, 0, 0, &img.at(0), - static_cast(img.size())); -} - -static bool ParseTexture(Texture *texture, std::string *err, - const picojson::object &o, - const std::string &basedir) { - (void)basedir; - - if (!ParseStringProperty(&texture->sampler, err, o, "sampler", true)) { - return false; - } - - if (!ParseStringProperty(&texture->source, err, o, "source", true)) { - return false; - } - - ParseStringProperty(&texture->name, err, o, "name", false); - - double format = TINYGLTF_TEXTURE_FORMAT_RGBA; - ParseNumberProperty(&format, err, o, "format", false); - - double internalFormat = TINYGLTF_TEXTURE_FORMAT_RGBA; - ParseNumberProperty(&internalFormat, err, o, "internalFormat", false); - - double target = TINYGLTF_TEXTURE_TARGET_TEXTURE2D; - ParseNumberProperty(&target, err, o, "target", false); - - double type = TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE; - ParseNumberProperty(&type, err, o, "type", false); - - texture->format = static_cast(format); - texture->internalFormat = static_cast(internalFormat); - texture->target = static_cast(target); - texture->type = static_cast(type); - - return true; -} - -static bool ParseBuffer(Buffer *buffer, std::string *err, - const picojson::object &o, const std::string &basedir, - bool is_binary = false, - const unsigned char *bin_data = NULL, - size_t bin_size = 0) { - double byteLength; - if (!ParseNumberProperty(&byteLength, err, o, "byteLength", true)) { - return false; - } - - std::string uri; - if (!ParseStringProperty(&uri, err, o, "uri", true)) { - return false; - } - - picojson::object::const_iterator type = o.find("type"); - if (type != o.end()) { - if (type->second.is()) { - const std::string &ty = (type->second).get(); - if (ty.compare("arraybuffer") == 0) { - // buffer.type = "arraybuffer"; - } - } - } - - size_t bytes = static_cast(byteLength); - if (is_binary) { - // Still binary glTF accepts external dataURI. First try external resources. - bool loaded = false; - if (IsDataURI(uri)) { - loaded = DecodeDataURI(&buffer->data, uri, bytes, true); - } else { - // Assume external .bin file. - loaded = LoadExternalFile(&buffer->data, err, uri, basedir, bytes, true); - } - - if (!loaded) { - // load data from (embedded) binary data - - if ((bin_size == 0) || (bin_data == NULL)) { - if (err) { - (*err) += "Invalid binary data.\n"; - } - return false; - } - - if (byteLength > bin_size) { - if (err) { - std::stringstream ss; - ss << "Invalid `byteLength'. Must be equal or less than binary size: " - "`byteLength' = " - << byteLength << ", binary size = " << bin_size << std::endl; - (*err) += ss.str(); - } - return false; - } - - if (uri.compare("data:,") == 0) { - // @todo { check uri } - buffer->data.resize(static_cast(byteLength)); - memcpy(&(buffer->data.at(0)), bin_data, - static_cast(byteLength)); - - } else { - if (err) { - (*err) += "Invalid URI for binary data.\n"; - } - return false; - } - } - - } else { - if (IsDataURI(uri)) { - if (!DecodeDataURI(&buffer->data, uri, bytes, true)) { - if (err) { - (*err) += "Failed to decode 'uri'.\n"; - } - return false; - } - } else { - // Assume external .bin file. - if (!LoadExternalFile(&buffer->data, err, uri, basedir, bytes, true)) { - return false; - } - } - } - - ParseStringProperty(&buffer->name, err, o, "name", false); - - return true; -} - -static bool ParseBufferView(BufferView *bufferView, std::string *err, - const picojson::object &o) { - std::string buffer; - if (!ParseStringProperty(&buffer, err, o, "buffer", true)) { - return false; - } - - double byteOffset; - if (!ParseNumberProperty(&byteOffset, err, o, "byteOffset", true)) { - return false; - } - - double byteLength = 0.0; - ParseNumberProperty(&byteLength, err, o, "byteLength", false); - - double target = 0.0; - ParseNumberProperty(&target, err, o, "target", false); - int targetValue = static_cast(target); - if ((targetValue == TINYGLTF_TARGET_ARRAY_BUFFER) || - (targetValue == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)) { - // OK - } else { - targetValue = 0; - } - bufferView->target = targetValue; - - ParseStringProperty(&bufferView->name, err, o, "name", false); - - bufferView->buffer = buffer; - bufferView->byteOffset = static_cast(byteOffset); - bufferView->byteLength = static_cast(byteLength); - - return true; -} - -static bool ParseAccessor(Accessor *accessor, std::string *err, - const picojson::object &o) { - std::string bufferView; - if (!ParseStringProperty(&bufferView, err, o, "bufferView", true)) { - return false; - } - - double byteOffset; - if (!ParseNumberProperty(&byteOffset, err, o, "byteOffset", true)) { - return false; - } - - double componentType; - if (!ParseNumberProperty(&componentType, err, o, "componentType", true)) { - return false; - } - - double count = 0.0; - if (!ParseNumberProperty(&count, err, o, "count", true)) { - return false; - } - - std::string type; - if (!ParseStringProperty(&type, err, o, "type", true)) { - return false; - } - - if (type.compare("SCALAR") == 0) { - accessor->type = TINYGLTF_TYPE_SCALAR; - } else if (type.compare("VEC2") == 0) { - accessor->type = TINYGLTF_TYPE_VEC2; - } else if (type.compare("VEC3") == 0) { - accessor->type = TINYGLTF_TYPE_VEC3; - } else if (type.compare("VEC4") == 0) { - accessor->type = TINYGLTF_TYPE_VEC4; - } else if (type.compare("MAT2") == 0) { - accessor->type = TINYGLTF_TYPE_MAT2; - } else if (type.compare("MAT3") == 0) { - accessor->type = TINYGLTF_TYPE_MAT3; - } else if (type.compare("MAT4") == 0) { - accessor->type = TINYGLTF_TYPE_MAT4; - } else { - std::stringstream ss; - ss << "Unsupported `type` for accessor object. Got \"" << type << "\"\n"; - if (err) { - (*err) += ss.str(); - } - return false; - } - - double byteStride = 0.0; - ParseNumberProperty(&byteStride, err, o, "byteStride", false); - - ParseStringProperty(&accessor->name, err, o, "name", false); - - accessor->minValues.clear(); - accessor->maxValues.clear(); - ParseNumberArrayProperty(&accessor->minValues, err, o, "min", false); - ParseNumberArrayProperty(&accessor->maxValues, err, o, "max", false); - - accessor->count = static_cast(count); - accessor->bufferView = bufferView; - accessor->byteOffset = static_cast(byteOffset); - accessor->byteStride = static_cast(byteStride); - - { - int comp = static_cast(componentType); - if (comp >= TINYGLTF_COMPONENT_TYPE_BYTE && - comp <= TINYGLTF_COMPONENT_TYPE_DOUBLE) { - // OK - accessor->componentType = comp; - } else { - std::stringstream ss; - ss << "Invalid `componentType` in accessor. Got " << comp << "\n"; - if (err) { - (*err) += ss.str(); - } - return false; - } - } - - ParseExtrasProperty(&(accessor->extras), o); - - return true; -} - -static bool ParsePrimitive(Primitive *primitive, std::string *err, - const picojson::object &o) { - if (!ParseStringProperty(&primitive->material, err, o, "material", true, - "mesh.primitive")) { - return false; - } - - double mode = static_cast(TINYGLTF_MODE_TRIANGLES); - ParseNumberProperty(&mode, err, o, "mode", false); - - int primMode = static_cast(mode); - primitive->mode = primMode; - - primitive->indices = ""; - ParseStringProperty(&primitive->indices, err, o, "indices", false); - - ParseStringMapProperty(&primitive->attributes, err, o, "attributes", false); - - ParseExtrasProperty(&(primitive->extras), o); - - return true; -} - -static bool ParseMesh(Mesh *mesh, std::string *err, const picojson::object &o) { - ParseStringProperty(&mesh->name, err, o, "name", false); - - mesh->primitives.clear(); - picojson::object::const_iterator primObject = o.find("primitives"); - if ((primObject != o.end()) && (primObject->second).is()) { - const picojson::array &primArray = - (primObject->second).get(); - for (size_t i = 0; i < primArray.size(); i++) { - Primitive primitive; - if (ParsePrimitive(&primitive, err, - primArray[i].get())) { - // Only add the primitive if the parsing succeeds. - mesh->primitives.push_back(primitive); - } - } - } - - ParseExtrasProperty(&(mesh->extras), o); - - return true; -} - -static bool ParseNode(Node *node, std::string *err, const picojson::object &o) { - ParseStringProperty(&node->name, err, o, "name", false); - - ParseNumberArrayProperty(&node->rotation, err, o, "rotation", false); - ParseNumberArrayProperty(&node->scale, err, o, "scale", false); - ParseNumberArrayProperty(&node->translation, err, o, "translation", false); - ParseNumberArrayProperty(&node->matrix, err, o, "matrix", false); - ParseStringArrayProperty(&node->meshes, err, o, "meshes", false); - - node->children.clear(); - picojson::object::const_iterator childrenObject = o.find("children"); - if ((childrenObject != o.end()) && - (childrenObject->second).is()) { - const picojson::array &childrenArray = - (childrenObject->second).get(); - for (size_t i = 0; i < childrenArray.size(); i++) { - if (!childrenArray[i].is()) { - if (err) { - (*err) += "Invalid `children` array.\n"; - } - return false; - } - const std::string &childrenNode = childrenArray[i].get(); - node->children.push_back(childrenNode); - } - } - - ParseExtrasProperty(&(node->extras), o); - - return true; -} - -static bool ParseParameterProperty(Parameter *param, std::string *err, - const picojson::object &o, - const std::string &prop, bool required) { - double num_val; - - // A parameter value can either be a string or an array of either a boolean or - // a number. Booleans of any kind aren't supported here. Granted, it - // complicates the Parameter structure and breaks it semantically in the sense - // that the client probably works off the assumption that if the string is - // empty the vector is used, etc. Would a tagged union work? - if (ParseStringProperty(¶m->string_value, err, o, prop, false)) { - // Found string property. - return true; - } else if (ParseNumberArrayProperty(¶m->number_array, err, o, prop, - false)) { - // Found a number array. - return true; - } else if (ParseNumberProperty(&num_val, err, o, prop, false)) { - param->number_array.push_back(num_val); - return true; - } else { - if (required) { - if (err) { - (*err) += "parameter must be a string or number / number array.\n"; - } - } - return false; - } -} - -static bool ParseMaterial(Material *material, std::string *err, - const picojson::object &o) { - ParseStringProperty(&material->name, err, o, "name", false); - ParseStringProperty(&material->technique, err, o, "technique", false); - - material->values.clear(); - picojson::object::const_iterator valuesIt = o.find("values"); - - if ((valuesIt != o.end()) && (valuesIt->second).is()) { - const picojson::object &values_object = - (valuesIt->second).get(); - - picojson::object::const_iterator it(values_object.begin()); - picojson::object::const_iterator itEnd(values_object.end()); - - for (; it != itEnd; it++) { - Parameter param; - if (ParseParameterProperty(¶m, err, values_object, it->first, - false)) { - material->values[it->first] = param; - } - } - } - - ParseExtrasProperty(&(material->extras), o); - - return true; -} - -static bool ParseShader(Shader *shader, std::string *err, - const picojson::object &o, const std::string &basedir, - bool is_binary = false, - const unsigned char *bin_data = NULL, - size_t bin_size = 0) { - std::string uri; - if (!ParseStringProperty(&uri, err, o, "uri", true)) { - return false; - } - - if (is_binary) { - // Still binary glTF accepts external dataURI. First try external resources. - bool loaded = false; - if (IsDataURI(uri)) { - loaded = DecodeDataURI(&shader->source, uri, 0, false); - } else { - // Assume external .bin file. - loaded = LoadExternalFile(&shader->source, err, uri, basedir, 0, false); - } - - if (!loaded) { - // load data from (embedded) binary data - - if ((bin_size == 0) || (bin_data == NULL)) { - if (err) { - (*err) += "Invalid binary data.\n"; - } - return false; - } - - // There should be "extensions" property. - // "extensions":{"KHR_binary_glTF":{"bufferView": "id", ... - - std::string buffer_view; - std::string mime_type; - int image_width; - int image_height; - bool ret = ParseKHRBinaryExtension(o, err, &buffer_view, &mime_type, - &image_width, &image_height); - if (!ret) { - return false; - } - - if (uri.compare("data:,") == 0) { - // ok - } else { - if (err) { - (*err) += "Invalid URI for binary data.\n"; - } - return false; - } - } - } else { - // Load shader source from data uri - // TODO(syoyo): Support ascii or utf-8 data uris. - if (IsDataURI(uri)) { - if (!DecodeDataURI(&shader->source, uri, 0, false)) { - if (err) { - (*err) += "Failed to decode 'uri' for shader parameter.\n"; - } - return false; - } - } else { - // Assume external file - if (!LoadExternalFile(&shader->source, err, uri, basedir, 0, false)) { - if (err) { - (*err) += "Failed to load external 'uri' for shader parameter.\n"; - } - return false; - } - if (shader->source.empty()) { - if (err) { - (*err) += "shader is empty.\n"; // This may be OK? - } - return false; - } - } - } - - double type; - if (!ParseNumberProperty(&type, err, o, "type", true)) { - return false; - } - - shader->type = static_cast(type); - - ParseExtrasProperty(&(shader->extras), o); - - return true; -} - -static bool ParseProgram(Program *program, std::string *err, - const picojson::object &o) { - ParseStringProperty(&program->name, err, o, "name", false); - - if (!ParseStringProperty(&program->vertexShader, err, o, "vertexShader", - true)) { - return false; - } - if (!ParseStringProperty(&program->fragmentShader, err, o, "fragmentShader", - true)) { - return false; - } - - // I suppose the list of attributes isn't needed, but a technique doesn't - // really make sense without it. - ParseStringArrayProperty(&program->attributes, err, o, "attributes", false); - - ParseExtrasProperty(&(program->extras), o); - - return true; -} - -static bool ParseTechniqueParameter(TechniqueParameter *param, std::string *err, - const picojson::object &o) { - double count = 1; - ParseNumberProperty(&count, err, o, "count", false); - - double type; - if (!ParseNumberProperty(&type, err, o, "type", true)) { - return false; - } - - ParseStringProperty(¶m->node, err, o, "node", false); - ParseStringProperty(¶m->semantic, err, o, "semantic", false); - - ParseParameterProperty(¶m->value, err, o, "value", false); - - param->count = static_cast(count); - param->type = static_cast(type); - - return true; -} - -static bool ParseTechnique(Technique *technique, std::string *err, - const picojson::object &o) { - ParseStringProperty(&technique->name, err, o, "name", false); - - if (!ParseStringProperty(&technique->program, err, o, "program", true)) { - return false; - } - - ParseStringMapProperty(&technique->attributes, err, o, "attributes", false); - ParseStringMapProperty(&technique->uniforms, err, o, "uniforms", false); - - technique->parameters.clear(); - picojson::object::const_iterator paramsIt = o.find("parameters"); - - // Verify parameters is an object - if ((paramsIt != o.end()) && (paramsIt->second).is()) { - // For each parameter in params_object. - const picojson::object ¶ms_object = - (paramsIt->second).get(); - - picojson::object::const_iterator it(params_object.begin()); - picojson::object::const_iterator itEnd(params_object.end()); - - for (; it != itEnd; it++) { - TechniqueParameter param; - - // Skip non-objects - if (!it->second.is()) continue; - - // Parse the technique parameter - const picojson::object ¶m_obj = it->second.get(); - if (ParseTechniqueParameter(¶m, err, param_obj)) { - // Add if successful - technique->parameters[it->first] = param; - } - } - } - - ParseExtrasProperty(&(technique->extras), o); - - return true; -} - -static bool ParseAnimationChannel(AnimationChannel *channel, std::string *err, - const picojson::object &o) { - if (!ParseStringProperty(&channel->sampler, err, o, "sampler", true)) { - if (err) { - (*err) += "`sampler` field is missing in animation channels\n"; - } - return false; - } - - picojson::object::const_iterator targetIt = o.find("target"); - if ((targetIt != o.end()) && (targetIt->second).is()) { - const picojson::object &target_object = - (targetIt->second).get(); - - if (!ParseStringProperty(&channel->target_id, err, target_object, "id", - true)) { - if (err) { - (*err) += "`id` field is missing in animation.channels.target\n"; - } - return false; - } - - if (!ParseStringProperty(&channel->target_path, err, target_object, "path", - true)) { - if (err) { - (*err) += "`path` field is missing in animation.channels.target\n"; - } - return false; - } - } - - ParseExtrasProperty(&(channel->extras), o); - - return true; -} - -static bool ParseAnimation(Animation *animation, std::string *err, - const picojson::object &o) { - { - picojson::object::const_iterator channelsIt = o.find("channels"); - if ((channelsIt != o.end()) && (channelsIt->second).is()) { - const picojson::array &channelArray = - (channelsIt->second).get(); - for (size_t i = 0; i < channelArray.size(); i++) { - AnimationChannel channel; - if (ParseAnimationChannel(&channel, err, - channelArray[i].get())) { - // Only add the channel if the parsing succeeds. - animation->channels.push_back(channel); - } - } - } - } - - { - picojson::object::const_iterator samplerIt = o.find("samplers"); - if ((samplerIt != o.end()) && (samplerIt->second).is()) { - const picojson::object &sampler_object = - (samplerIt->second).get(); - - picojson::object::const_iterator it = sampler_object.begin(); - picojson::object::const_iterator itEnd = sampler_object.end(); - - for (; it != itEnd; it++) { - // Skip non-objects - if (!it->second.is()) continue; - - const picojson::object &s = it->second.get(); - - AnimationSampler sampler; - if (!ParseStringProperty(&sampler.input, err, s, "input", true)) { - if (err) { - (*err) += "`input` field is missing in animation.sampler\n"; - } - return false; - } - if (!ParseStringProperty(&sampler.interpolation, err, s, - "interpolation", true)) { - if (err) { - (*err) += "`interpolation` field is missing in animation.sampler\n"; - } - return false; - } - if (!ParseStringProperty(&sampler.output, err, s, "output", true)) { - if (err) { - (*err) += "`output` field is missing in animation.sampler\n"; - } - return false; - } - - animation->samplers[it->first] = sampler; - } - } - } - - picojson::object::const_iterator parametersIt = o.find("parameters"); - if ((parametersIt != o.end()) && - (parametersIt->second).is()) { - const picojson::object ¶meters_object = - (parametersIt->second).get(); - - picojson::object::const_iterator it(parameters_object.begin()); - picojson::object::const_iterator itEnd(parameters_object.end()); - - for (; it != itEnd; it++) { - Parameter param; - if (ParseParameterProperty(¶m, err, parameters_object, it->first, - false)) { - animation->parameters[it->first] = param; - } - } - } - ParseStringProperty(&animation->name, err, o, "name", false); - - ParseExtrasProperty(&(animation->extras), o); - - return true; -} - -static bool ParseSampler(Sampler *sampler, std::string *err, - const picojson::object &o) { - ParseStringProperty(&sampler->name, err, o, "name", false); - - double minFilter = - static_cast(TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR); - double magFilter = static_cast(TINYGLTF_TEXTURE_FILTER_LINEAR); - double wrapS = static_cast(TINYGLTF_TEXTURE_WRAP_RPEAT); - double wrapT = static_cast(TINYGLTF_TEXTURE_WRAP_RPEAT); - ParseNumberProperty(&minFilter, err, o, "minFilter", false); - ParseNumberProperty(&magFilter, err, o, "magFilter", false); - ParseNumberProperty(&wrapS, err, o, "wrapS", false); - ParseNumberProperty(&wrapT, err, o, "wrapT", false); - - sampler->minFilter = static_cast(minFilter); - sampler->magFilter = static_cast(magFilter); - sampler->wrapS = static_cast(wrapS); - sampler->wrapT = static_cast(wrapT); - - ParseExtrasProperty(&(sampler->extras), o); - - return true; -} - -bool TinyGLTFLoader::LoadFromString(Scene *scene, std::string *err, - const char *str, unsigned int length, - const std::string &base_dir, - unsigned int check_sections) { - picojson::value v; - std::string perr = picojson::parse(v, str, str + length); - - if (!perr.empty()) { - if (err) { - (*err) = perr; - } - return false; - } - - if (v.contains("scene") && v.get("scene").is()) { - // OK - } else if (check_sections & REQUIRE_SCENE) { - if (err) { - (*err) += "\"scene\" object not found in .gltf\n"; - } - return false; - } - - if (v.contains("scenes") && v.get("scenes").is()) { - // OK - } else if (check_sections & REQUIRE_SCENES) { - if (err) { - (*err) += "\"scenes\" object not found in .gltf\n"; - } - return false; - } - - if (v.contains("nodes") && v.get("nodes").is()) { - // OK - } else if (check_sections & REQUIRE_NODES) { - if (err) { - (*err) += "\"nodes\" object not found in .gltf\n"; - } - return false; - } - - if (v.contains("accessors") && v.get("accessors").is()) { - // OK - } else if (check_sections & REQUIRE_ACCESSORS) { - if (err) { - (*err) += "\"accessors\" object not found in .gltf\n"; - } - return false; - } - - if (v.contains("buffers") && v.get("buffers").is()) { - // OK - } else if (check_sections & REQUIRE_BUFFERS) { - if (err) { - (*err) += "\"buffers\" object not found in .gltf\n"; - } - return false; - } - - if (v.contains("bufferViews") && - v.get("bufferViews").is()) { - // OK - } else if (check_sections & REQUIRE_BUFFER_VIEWS) { - if (err) { - (*err) += "\"bufferViews\" object not found in .gltf\n"; - } - return false; - } - - scene->buffers.clear(); - scene->bufferViews.clear(); - scene->accessors.clear(); - scene->meshes.clear(); - scene->nodes.clear(); - scene->defaultScene = ""; - - // 0. Parse Asset - if (v.contains("asset") && v.get("asset").is()) { - const picojson::object &root = v.get("asset").get(); - - ParseAsset(&scene->asset, err, root); - } - - // 1. Parse Buffer - if (v.contains("buffers") && v.get("buffers").is()) { - const picojson::object &root = v.get("buffers").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - Buffer buffer; - if (!ParseBuffer(&buffer, err, (it->second).get(), - base_dir, is_binary_, bin_data_, bin_size_)) { - return false; - } - - scene->buffers[it->first] = buffer; - } - } - - // 2. Parse BufferView - if (v.contains("bufferViews") && - v.get("bufferViews").is()) { - const picojson::object &root = v.get("bufferViews").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - BufferView bufferView; - if (!ParseBufferView(&bufferView, err, - (it->second).get())) { - return false; - } - - scene->bufferViews[it->first] = bufferView; - } - } - - // 3. Parse Accessor - if (v.contains("accessors") && v.get("accessors").is()) { - const picojson::object &root = v.get("accessors").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - Accessor accessor; - if (!ParseAccessor(&accessor, err, - (it->second).get())) { - return false; - } - - scene->accessors[it->first] = accessor; - } - } - - // 4. Parse Mesh - if (v.contains("meshes") && v.get("meshes").is()) { - const picojson::object &root = v.get("meshes").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - Mesh mesh; - if (!ParseMesh(&mesh, err, (it->second).get())) { - return false; - } - - scene->meshes[it->first] = mesh; - } - } - - // 5. Parse Node - if (v.contains("nodes") && v.get("nodes").is()) { - const picojson::object &root = v.get("nodes").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - Node node; - if (!ParseNode(&node, err, (it->second).get())) { - return false; - } - - scene->nodes[it->first] = node; - } - } - - // 6. Parse scenes. - if (v.contains("scenes") && v.get("scenes").is()) { - const picojson::object &root = v.get("scenes").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!((it->second).is())) { - if (err) { - (*err) += "`scenes' does not contain an object."; - } - return false; - } - const picojson::object &o = (it->second).get(); - std::vector nodes; - if (!ParseStringArrayProperty(&nodes, err, o, "nodes", false)) { - return false; - } - - scene->scenes[it->first] = nodes; - } - } - - // 7. Parse default scenes. - if (v.contains("scene") && v.get("scene").is()) { - const std::string defaultScene = v.get("scene").get(); - - scene->defaultScene = defaultScene; - } - - // 8. Parse Material - if (v.contains("materials") && v.get("materials").is()) { - const picojson::object &root = v.get("materials").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - Material material; - if (!ParseMaterial(&material, err, - (it->second).get())) { - return false; - } - - scene->materials[it->first] = material; - } - } - - // 9. Parse Image - if (v.contains("images") && v.get("images").is()) { - const picojson::object &root = v.get("images").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - Image image; - if (!ParseImage(&image, err, (it->second).get(), - base_dir, is_binary_, bin_data_, bin_size_)) { - return false; - } - - if (!image.bufferView.empty()) { - // Load image from the buffer view. - if (scene->bufferViews.find(image.bufferView) == - scene->bufferViews.end()) { - if (err) { - std::stringstream ss; - ss << "bufferView \"" << image.bufferView - << "\" not found in the scene." << std::endl; - (*err) += ss.str(); - } - return false; - } - - const BufferView &bufferView = scene->bufferViews[image.bufferView]; - const Buffer &buffer = scene->buffers[bufferView.buffer]; - - bool ret = LoadImageData(&image, err, image.width, image.height, - &buffer.data[bufferView.byteOffset], - static_cast(bufferView.byteLength)); - if (!ret) { - return false; - } - } - - scene->images[it->first] = image; - } - } - - // 10. Parse Texture - if (v.contains("textures") && v.get("textures").is()) { - const picojson::object &root = v.get("textures").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - Texture texture; - if (!ParseTexture(&texture, err, (it->second).get(), - base_dir)) { - return false; - } - - scene->textures[it->first] = texture; - } - } - - // 11. Parse Shader - if (v.contains("shaders") && v.get("shaders").is()) { - const picojson::object &root = v.get("shaders").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - Shader shader; - if (!ParseShader(&shader, err, (it->second).get(), - base_dir, is_binary_, bin_data_, bin_size_)) { - return false; - } - - scene->shaders[it->first] = shader; - } - } - - // 12. Parse Program - if (v.contains("programs") && v.get("programs").is()) { - const picojson::object &root = v.get("programs").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - Program program; - if (!ParseProgram(&program, err, (it->second).get())) { - return false; - } - - scene->programs[it->first] = program; - } - } - - // 13. Parse Technique - if (v.contains("techniques") && v.get("techniques").is()) { - const picojson::object &root = v.get("techniques").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - Technique technique; - if (!ParseTechnique(&technique, err, - (it->second).get())) { - return false; - } - - scene->techniques[it->first] = technique; - } - } - - // 14. Parse Animation - if (v.contains("animations") && v.get("animations").is()) { - const picojson::object &root = v.get("animations").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - Animation animation; - if (!ParseAnimation(&animation, err, - (it->second).get())) { - return false; - } - - scene->animations[it->first] = animation; - } - } - - // 15. Parse Sampler - if (v.contains("samplers") && v.get("samplers").is()) { - const picojson::object &root = v.get("samplers").get(); - - picojson::object::const_iterator it(root.begin()); - picojson::object::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - Sampler sampler; - if (!ParseSampler(&sampler, err, (it->second).get())) { - return false; - } - - scene->samplers[it->first] = sampler; - } - } - return true; -} - -bool TinyGLTFLoader::LoadASCIIFromString(Scene *scene, std::string *err, - const char *str, unsigned int length, - const std::string &base_dir, - unsigned int check_sections) { - is_binary_ = false; - bin_data_ = NULL; - bin_size_ = 0; - - return LoadFromString(scene, err, str, length, base_dir, check_sections); -} - -bool TinyGLTFLoader::LoadASCIIFromFile(Scene *scene, std::string *err, - const std::string &filename, - unsigned int check_sections) { - std::stringstream ss; - - std::ifstream f(filename.c_str()); - if (!f) { - ss << "Failed to open file: " << filename << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - f.seekg(0, f.end); - size_t sz = static_cast(f.tellg()); - std::vector buf(sz); - - if (sz == 0) { - if (err) { - (*err) = "Empty file."; - } - return false; - } - - f.seekg(0, f.beg); - f.read(&buf.at(0), static_cast(sz)); - f.close(); - - std::string basedir = GetBaseDir(filename); - - bool ret = LoadASCIIFromString(scene, err, &buf.at(0), - static_cast(buf.size()), basedir, - check_sections); - - return ret; -} - -bool TinyGLTFLoader::LoadBinaryFromMemory(Scene *scene, std::string *err, - const unsigned char *bytes, - unsigned int size, - const std::string &base_dir, - unsigned int check_sections) { - if (size < 20) { - if (err) { - (*err) = "Too short data size for glTF Binary."; - } - return false; - } - - if (bytes[0] == 'g' && bytes[1] == 'l' && bytes[2] == 'T' && - bytes[3] == 'F') { - // ok - } else { - if (err) { - (*err) = "Invalid magic."; - } - return false; - } - - unsigned int version; // 4 bytes - unsigned int length; // 4 bytes - unsigned int scene_length; // 4 bytes - unsigned int scene_format; // 4 bytes; - - // @todo { Endian swap for big endian machine. } - memcpy(&version, bytes + 4, 4); - swap4(&version); - memcpy(&length, bytes + 8, 4); - swap4(&length); - memcpy(&scene_length, bytes + 12, 4); - swap4(&scene_length); - memcpy(&scene_format, bytes + 16, 4); - swap4(&scene_format); - - if ((20 + scene_length >= size) || (scene_length < 1) || - (scene_format != 0)) { // 0 = JSON format. - if (err) { - (*err) = "Invalid glTF binary."; - } - return false; - } - - // Extract JSON string. - std::string jsonString(reinterpret_cast(&bytes[20]), - scene_length); - - is_binary_ = true; - bin_data_ = bytes + 20 + scene_length; - bin_size_ = - length - (20 + scene_length); // extract header + JSON scene data. - - bool ret = - LoadFromString(scene, err, reinterpret_cast(&bytes[20]), - scene_length, base_dir, check_sections); - if (!ret) { - return ret; - } - - return true; -} - -bool TinyGLTFLoader::LoadBinaryFromFile(Scene *scene, std::string *err, - const std::string &filename, - unsigned int check_sections) { - std::stringstream ss; - - std::ifstream f(filename.c_str(), std::ios::binary); - if (!f) { - ss << "Failed to open file: " << filename << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - f.seekg(0, f.end); - size_t sz = static_cast(f.tellg()); - std::vector buf(sz); - - f.seekg(0, f.beg); - f.read(&buf.at(0), static_cast(sz)); - f.close(); - - std::string basedir = GetBaseDir(filename); - - bool ret = LoadBinaryFromMemory( - scene, err, reinterpret_cast(&buf.at(0)), - static_cast(buf.size()), basedir, check_sections); - - return ret; -} - -} // namespace tinygltf - -#endif // TINYGLTF_LOADER_IMPLEMENTATION - -#endif // TINY_GLTF_LOADER_H_