From 45e344cbfa06357187458c06b98fba6324f7a8e7 Mon Sep 17 00:00:00 2001 From: turanszkij Date: Sat, 16 Mar 2019 18:19:49 +0000 Subject: [PATCH] added feature: multiple uv sets; gltf updates; editor updates; --- Editor/CameraWindow.cpp | 2 +- Editor/Editor.cpp | 64 +- Editor/ForceFieldWindow.cpp | 2 +- Editor/LightWindow.cpp | 2 +- Editor/MaterialWindow.cpp | 218 +- Editor/MaterialWindow.h | 11 + Editor/MeshWindow.cpp | 5 +- Editor/ModelImporter_GLTF.cpp | 124 +- Editor/ModelImporter_OBJ.cpp | 2 +- Editor/ObjectWindow.cpp | 33 +- Editor/OceanWindow.cpp | 2 +- Editor/PostprocessWindow.cpp | 2 +- Editor/WeatherWindow.cpp | 2 +- Editor/tiny_gltf.h | 8760 +++++++++-------- WickedEngine/ArchiveVersionHistory.txt | 1 + WickedEngine/ShaderInterop_BVH.h | 2 +- WickedEngine/ShaderInterop_Renderer.h | 10 + WickedEngine/ShaderInterop_TracedRendering.h | 16 +- WickedEngine/bvh_classificationCS.hlsl | 13 +- WickedEngine/captureImpostorPS_albedo.hlsl | 5 +- WickedEngine/captureImpostorPS_normal.hlsl | 7 +- WickedEngine/captureImpostorPS_surface.hlsl | 8 +- WickedEngine/cubeShadowGS.hlsl | 8 +- WickedEngine/cubeShadowGS_alphatest.hlsl | 14 +- WickedEngine/cubeShadowPS_alphatest.hlsl | 4 +- WickedEngine/cubeShadowVS_alphatest.hlsl | 8 +- WickedEngine/envMapGS.hlsl | 2 +- WickedEngine/envMapHF.hlsli | 4 +- WickedEngine/envMapVS.hlsl | 2 +- WickedEngine/globals.hlsli | 7 + WickedEngine/objectDS.hlsl | 28 +- WickedEngine/objectGS_voxelizer.hlsl | 6 +- WickedEngine/objectHF.hlsli | 54 +- WickedEngine/objectHS.hlsl | 29 +- WickedEngine/objectInputLayoutHF.hlsli | 12 +- WickedEngine/objectPS_hologram.hlsl | 5 +- WickedEngine/objectPS_voxelizer.hlsl | 8 +- WickedEngine/objectVS_common.hlsl | 2 +- .../objectVS_common_tessellation.hlsl | 6 +- WickedEngine/objectVS_simple.hlsl | 2 +- .../objectVS_simple_tessellation.hlsl | 6 +- WickedEngine/objectVS_voxelizer.hlsl | 4 +- WickedEngine/raySceneIntersectHF.hlsli | 26 +- WickedEngine/raytrace_lightsamplingCS.hlsl | 23 +- WickedEngine/shadowPS_alphatest.hlsl | 4 +- WickedEngine/shadowPS_transparent.hlsl | 12 +- WickedEngine/shadowPS_water.hlsl | 12 +- WickedEngine/shadowVS_alphatest.hlsl | 4 +- WickedEngine/shadowVS_transparent.hlsl | 4 +- WickedEngine/tracedRenderingHF.hlsli | 4 +- WickedEngine/waterVS.hlsl | 2 +- WickedEngine/wiArchive.cpp | 2 +- WickedEngine/wiGPUBVH.cpp | 13 +- WickedEngine/wiRenderer.cpp | 72 +- WickedEngine/wiSceneSystem.cpp | 116 +- WickedEngine/wiSceneSystem.h | 29 +- WickedEngine/wiSceneSystem_Serializers.cpp | 46 +- WickedEngine/wiVersion.cpp | 2 +- 58 files changed, 5670 insertions(+), 4203 deletions(-) diff --git a/Editor/CameraWindow.cpp b/Editor/CameraWindow.cpp index 917acbf19..93c5a907a 100644 --- a/Editor/CameraWindow.cpp +++ b/Editor/CameraWindow.cpp @@ -140,7 +140,7 @@ CameraWindow::CameraWindow(wiGUI* gui) :GUI(gui) SetEntity(INVALID_ENTITY); - cameraWindow->Translate(XMFLOAT3(800, 500, 0)); + cameraWindow->Translate(XMFLOAT3(screenW - 720, 500, 0)); cameraWindow->SetVisible(false); } diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 437703e3f..6025a790f 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -166,15 +166,15 @@ void EditorComponent::Load() float screenW = (float)wiRenderer::GetDevice()->GetScreenWidth(); float screenH = (float)wiRenderer::GetDevice()->GetScreenHeight(); - float step = 105, x = -step; - + XMFLOAT2 option_size = XMFLOAT2(100, 28); + float step = (option_size.y + 5) * -1, x = screenW - option_size.x, y = screenH - option_size.y; wiButton* rendererWnd_Toggle = new wiButton("Renderer"); rendererWnd_Toggle->SetTooltip("Renderer settings window"); - rendererWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - rendererWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + rendererWnd_Toggle->SetPos(XMFLOAT2(x, y)); + rendererWnd_Toggle->SetSize(option_size); rendererWnd_Toggle->OnClick([=](wiEventArgs args) { rendererWnd->rendererWindow->SetVisible(!rendererWnd->rendererWindow->IsVisible()); }); @@ -182,8 +182,8 @@ void EditorComponent::Load() wiButton* weatherWnd_Toggle = new wiButton("Weather"); weatherWnd_Toggle->SetTooltip("World settings window"); - weatherWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - weatherWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + weatherWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + weatherWnd_Toggle->SetSize(option_size); weatherWnd_Toggle->OnClick([=](wiEventArgs args) { weatherWnd->weatherWindow->SetVisible(!weatherWnd->weatherWindow->IsVisible()); }); @@ -191,8 +191,8 @@ void EditorComponent::Load() wiButton* objectWnd_Toggle = new wiButton("Object"); objectWnd_Toggle->SetTooltip("Object settings window"); - objectWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - objectWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + objectWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + objectWnd_Toggle->SetSize(option_size); objectWnd_Toggle->OnClick([=](wiEventArgs args) { objectWnd->objectWindow->SetVisible(!objectWnd->objectWindow->IsVisible()); }); @@ -200,8 +200,8 @@ void EditorComponent::Load() wiButton* meshWnd_Toggle = new wiButton("Mesh"); meshWnd_Toggle->SetTooltip("Mesh settings window"); - meshWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - meshWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + meshWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + meshWnd_Toggle->SetSize(option_size); meshWnd_Toggle->OnClick([=](wiEventArgs args) { meshWnd->meshWindow->SetVisible(!meshWnd->meshWindow->IsVisible()); }); @@ -209,8 +209,8 @@ void EditorComponent::Load() wiButton* materialWnd_Toggle = new wiButton("Material"); materialWnd_Toggle->SetTooltip("Material settings window"); - materialWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - materialWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + materialWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + materialWnd_Toggle->SetSize(option_size); materialWnd_Toggle->OnClick([=](wiEventArgs args) { materialWnd->materialWindow->SetVisible(!materialWnd->materialWindow->IsVisible()); }); @@ -218,8 +218,8 @@ void EditorComponent::Load() wiButton* postprocessWnd_Toggle = new wiButton("PostProcess"); postprocessWnd_Toggle->SetTooltip("Postprocess settings window"); - postprocessWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - postprocessWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + postprocessWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + postprocessWnd_Toggle->SetSize(option_size); postprocessWnd_Toggle->OnClick([=](wiEventArgs args) { postprocessWnd->ppWindow->SetVisible(!postprocessWnd->ppWindow->IsVisible()); }); @@ -227,8 +227,8 @@ void EditorComponent::Load() wiButton* cameraWnd_Toggle = new wiButton("Camera"); cameraWnd_Toggle->SetTooltip("Camera settings window"); - cameraWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - cameraWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + cameraWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + cameraWnd_Toggle->SetSize(option_size); cameraWnd_Toggle->OnClick([=](wiEventArgs args) { cameraWnd->cameraWindow->SetVisible(!cameraWnd->cameraWindow->IsVisible()); }); @@ -236,8 +236,8 @@ void EditorComponent::Load() wiButton* envProbeWnd_Toggle = new wiButton("EnvProbe"); envProbeWnd_Toggle->SetTooltip("Environment probe settings window"); - envProbeWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - envProbeWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + envProbeWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + envProbeWnd_Toggle->SetSize(option_size); envProbeWnd_Toggle->OnClick([=](wiEventArgs args) { envProbeWnd->envProbeWindow->SetVisible(!envProbeWnd->envProbeWindow->IsVisible()); }); @@ -245,8 +245,8 @@ void EditorComponent::Load() wiButton* decalWnd_Toggle = new wiButton("Decal"); decalWnd_Toggle->SetTooltip("Decal settings window"); - decalWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - decalWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + decalWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + decalWnd_Toggle->SetSize(option_size); decalWnd_Toggle->OnClick([=](wiEventArgs args) { decalWnd->decalWindow->SetVisible(!decalWnd->decalWindow->IsVisible()); }); @@ -254,8 +254,8 @@ void EditorComponent::Load() wiButton* lightWnd_Toggle = new wiButton("Light"); lightWnd_Toggle->SetTooltip("Light settings window"); - lightWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - lightWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + lightWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + lightWnd_Toggle->SetSize(option_size); lightWnd_Toggle->OnClick([=](wiEventArgs args) { lightWnd->lightWindow->SetVisible(!lightWnd->lightWindow->IsVisible()); }); @@ -263,8 +263,8 @@ void EditorComponent::Load() wiButton* animWnd_Toggle = new wiButton("Animation"); animWnd_Toggle->SetTooltip("Animation inspector window"); - animWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - animWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + animWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + animWnd_Toggle->SetSize(option_size); animWnd_Toggle->OnClick([=](wiEventArgs args) { animWnd->animWindow->SetVisible(!animWnd->animWindow->IsVisible()); }); @@ -272,8 +272,8 @@ void EditorComponent::Load() wiButton* emitterWnd_Toggle = new wiButton("Emitter"); emitterWnd_Toggle->SetTooltip("Emitter Particle System properties"); - emitterWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - emitterWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + emitterWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + emitterWnd_Toggle->SetSize(option_size); emitterWnd_Toggle->OnClick([=](wiEventArgs args) { emitterWnd->emitterWindow->SetVisible(!emitterWnd->emitterWindow->IsVisible()); }); @@ -281,8 +281,8 @@ void EditorComponent::Load() wiButton* hairWnd_Toggle = new wiButton("HairParticle"); hairWnd_Toggle->SetTooltip("Emitter Particle System properties"); - hairWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - hairWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + hairWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + hairWnd_Toggle->SetSize(option_size); hairWnd_Toggle->OnClick([=](wiEventArgs args) { hairWnd->hairWindow->SetVisible(!hairWnd->hairWindow->IsVisible()); }); @@ -290,8 +290,8 @@ void EditorComponent::Load() wiButton* forceFieldWnd_Toggle = new wiButton("ForceField"); forceFieldWnd_Toggle->SetTooltip("Force Field properties"); - forceFieldWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - forceFieldWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + forceFieldWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + forceFieldWnd_Toggle->SetSize(option_size); forceFieldWnd_Toggle->OnClick([=](wiEventArgs args) { forceFieldWnd->forceFieldWindow->SetVisible(!forceFieldWnd->forceFieldWindow->IsVisible()); }); @@ -299,8 +299,8 @@ void EditorComponent::Load() wiButton* oceanWnd_Toggle = new wiButton("Ocean"); oceanWnd_Toggle->SetTooltip("Ocean Simulator properties"); - oceanWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); - oceanWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + oceanWnd_Toggle->SetPos(XMFLOAT2(x, y += step)); + oceanWnd_Toggle->SetSize(option_size); oceanWnd_Toggle->OnClick([=](wiEventArgs args) { oceanWnd->oceanWindow->SetVisible(!oceanWnd->oceanWindow->IsVisible()); }); diff --git a/Editor/ForceFieldWindow.cpp b/Editor/ForceFieldWindow.cpp index 15b5b7476..793ce0791 100644 --- a/Editor/ForceFieldWindow.cpp +++ b/Editor/ForceFieldWindow.cpp @@ -92,7 +92,7 @@ ForceFieldWindow::ForceFieldWindow(wiGUI* gui) : GUI(gui) - forceFieldWindow->Translate(XMFLOAT3(810, 50, 0)); + forceFieldWindow->Translate(XMFLOAT3(screenW - 720, 50, 0)); forceFieldWindow->SetVisible(false); SetEntity(INVALID_ENTITY); diff --git a/Editor/LightWindow.cpp b/Editor/LightWindow.cpp index 9997eef68..5914a6ab5 100644 --- a/Editor/LightWindow.cpp +++ b/Editor/LightWindow.cpp @@ -218,7 +218,7 @@ LightWindow::LightWindow(wiGUI* gui) : GUI(gui) lightWindow->AddWidget(typeSelectorComboBox); - lightWindow->Translate(XMFLOAT3(30, 30, 0)); + lightWindow->Translate(XMFLOAT3(120, 30, 0)); lightWindow->SetVisible(false); SetEntity(INVALID_ENTITY); diff --git a/Editor/MaterialWindow.cpp b/Editor/MaterialWindow.cpp index 45af97809..19f7fd07d 100644 --- a/Editor/MaterialWindow.cpp +++ b/Editor/MaterialWindow.cpp @@ -19,7 +19,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) float screenH = (float)wiRenderer::GetDevice()->GetScreenHeight(); materialWindow = new wiWindow(GUI, "Material Window"); - materialWindow->SetSize(XMFLOAT2(760, 840)); + materialWindow->SetSize(XMFLOAT2(760, 860)); materialWindow->SetEnabled(false); GUI->AddWidget(materialWindow); @@ -40,7 +40,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) waterCheckBox = new wiCheckBox("Water: "); waterCheckBox->SetTooltip("Set material as special water material."); - waterCheckBox->SetPos(XMFLOAT2(570, y += step)); + waterCheckBox->SetPos(XMFLOAT2(670, y += step)); waterCheckBox->OnClick([&](wiEventArgs args) { MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); if (material != nullptr) @@ -50,7 +50,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) planarReflCheckBox = new wiCheckBox("Planar Reflections: "); planarReflCheckBox->SetTooltip("Enable planar reflections. The mesh should be a single plane for best results."); - planarReflCheckBox->SetPos(XMFLOAT2(570, y += step)); + planarReflCheckBox->SetPos(XMFLOAT2(670, y += step)); planarReflCheckBox->OnClick([&](wiEventArgs args) { MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); if (material != nullptr) @@ -60,7 +60,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) shadowCasterCheckBox = new wiCheckBox("Cast Shadow: "); shadowCasterCheckBox->SetTooltip("The subset will contribute to the scene shadows if enabled."); - shadowCasterCheckBox->SetPos(XMFLOAT2(570, y += step)); + shadowCasterCheckBox->SetPos(XMFLOAT2(670, y += step)); shadowCasterCheckBox->OnClick([&](wiEventArgs args) { MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); if (material != nullptr) @@ -70,7 +70,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) flipNormalMapCheckBox = new wiCheckBox("Flip Normal Map: "); flipNormalMapCheckBox->SetTooltip("The normal map green channel will be inverted. Useful for imported models coming from OpenGL space (such as GLTF)."); - flipNormalMapCheckBox->SetPos(XMFLOAT2(570, y += step)); + flipNormalMapCheckBox->SetPos(XMFLOAT2(670, y += step)); flipNormalMapCheckBox->OnClick([&](wiEventArgs args) { MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); if (material != nullptr) @@ -80,7 +80,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) useVertexColorsCheckBox = new wiCheckBox("Use vertex colors: "); useVertexColorsCheckBox->SetTooltip("Enable if you want to render the mesh with vertex colors (must have appropriate vertex buffer)"); - useVertexColorsCheckBox->SetPos(XMFLOAT2(570, y += step)); + useVertexColorsCheckBox->SetPos(XMFLOAT2(670, y += step)); useVertexColorsCheckBox->OnClick([&](wiEventArgs args) { MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); if (material != nullptr) @@ -88,6 +88,16 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) }); materialWindow->AddWidget(useVertexColorsCheckBox); + specularGlossinessCheckBox = new wiCheckBox("Specular-glossiness workflow: "); + specularGlossinessCheckBox->SetTooltip("If enabled, surface map will be viewed like it contains specular color (RGB) and smoothness (A)"); + specularGlossinessCheckBox->SetPos(XMFLOAT2(670, y += step)); + specularGlossinessCheckBox->OnClick([&](wiEventArgs args) { + MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); + if (material != nullptr) + material->SetUseSpecularGlossinessWorkflow(args.bValue); + }); + materialWindow->AddWidget(specularGlossinessCheckBox); + normalMapSlider = new wiSlider(0, 4, 1, 4000, "Normalmap: "); normalMapSlider->SetTooltip("How much the normal map should distort the face normals (bumpiness)."); normalMapSlider->SetSize(XMFLOAT2(100, 30)); @@ -188,7 +198,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) materialWindow->AddWidget(sssSlider); pomSlider = new wiSlider(0, 0.1f, 0.0f, 1000, "Parallax Occlusion Mapping: "); - pomSlider->SetTooltip("Adjust how much the bump map should affect the object (slow)."); + pomSlider->SetTooltip("Adjust how much the bump map should modulate the surface parallax effect."); pomSlider->SetSize(XMFLOAT2(100, 30)); pomSlider->SetPos(XMFLOAT2(x, y += step)); pomSlider->OnSlide([&](wiEventArgs args) { @@ -198,6 +208,17 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) }); materialWindow->AddWidget(pomSlider); + displacementMappingSlider = new wiSlider(0, 0.1f, 0.0f, 1000, "Displacement Mapping: "); + displacementMappingSlider->SetTooltip("Adjust how much the bump map should modulate the geometry when using tessellation."); + displacementMappingSlider->SetSize(XMFLOAT2(100, 30)); + displacementMappingSlider->SetPos(XMFLOAT2(x, y += step)); + displacementMappingSlider->OnSlide([&](wiEventArgs args) { + MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); + if (material != nullptr) + material->SetDisplacementMapping(args.fValue); + }); + materialWindow->AddWidget(displacementMappingSlider); + texAnimFrameRateSlider = new wiSlider(0, 60, 0, 60, "Texcoord anim FPS: "); texAnimFrameRateSlider->SetTooltip("Adjust the texture animation frame rate (frames per second). Any value above 0 will make the material dynamic."); texAnimFrameRateSlider->SetSize(XMFLOAT2(100, 30)); @@ -267,7 +288,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) baseColorPicker = new wiColorPicker(GUI, "Base Color"); - baseColorPicker->SetPos(XMFLOAT2(10, 280)); + baseColorPicker->SetPos(XMFLOAT2(10, 300)); baseColorPicker->RemoveWidgets(); baseColorPicker->SetVisible(true); baseColorPicker->SetEnabled(true); @@ -283,7 +304,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) emissiveColorPicker = new wiColorPicker(GUI, "Emissive Color"); - emissiveColorPicker->SetPos(XMFLOAT2(10, 550)); + emissiveColorPicker->SetPos(XMFLOAT2(10, 570)); emissiveColorPicker->RemoveWidgets(); emissiveColorPicker->SetVisible(true); emissiveColorPicker->SetEnabled(true); @@ -362,6 +383,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) { material->baseColorMap = nullptr; material->baseColorMapName = ""; + material->SetDirty(); texture_baseColor_Button->SetText(""); } else @@ -387,6 +409,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) string fileName = ofn.lpstrFile; material->baseColorMap = (Texture2D*)wiResourceManager::GetGlobal().add(fileName); material->baseColorMapName = fileName; + material->SetDirty(); fileName = wiHelper::GetFileNameFromPath(fileName); texture_baseColor_Button->SetText(fileName); } @@ -394,6 +417,20 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) }); materialWindow->AddWidget(texture_baseColor_Button); + texture_baseColor_uvset_Field = new wiTextInputField("uvset_baseColor"); + texture_baseColor_uvset_Field->SetText(""); + texture_baseColor_uvset_Field->SetTooltip("uv set number"); + texture_baseColor_uvset_Field->SetPos(XMFLOAT2(x + 392, y)); + texture_baseColor_uvset_Field->SetSize(XMFLOAT2(20, 20)); + texture_baseColor_uvset_Field->OnInputAccepted([&](wiEventArgs args) { + MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); + if (material != nullptr) + { + material->SetUVSet_BaseColorMap(args.iValue); + } + }); + materialWindow->AddWidget(texture_baseColor_uvset_Field); + texture_normal_Label = new wiLabel("NormalMap: "); @@ -415,6 +452,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) { material->normalMap = nullptr; material->normalMapName = ""; + material->SetDirty(); texture_normal_Button->SetText(""); } else @@ -440,6 +478,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) string fileName = ofn.lpstrFile; material->normalMap = (Texture2D*)wiResourceManager::GetGlobal().add(fileName); material->normalMapName = fileName; + material->SetDirty(); fileName = wiHelper::GetFileNameFromPath(fileName); texture_normal_Button->SetText(fileName); } @@ -447,6 +486,20 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) }); materialWindow->AddWidget(texture_normal_Button); + texture_normal_uvset_Field = new wiTextInputField("uvset_normal"); + texture_normal_uvset_Field->SetText(""); + texture_normal_uvset_Field->SetTooltip("uv set number"); + texture_normal_uvset_Field->SetPos(XMFLOAT2(x + 392, y)); + texture_normal_uvset_Field->SetSize(XMFLOAT2(20, 20)); + texture_normal_uvset_Field->OnInputAccepted([&](wiEventArgs args) { + MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); + if (material != nullptr) + { + material->SetUVSet_NormalMap(args.iValue); + } + }); + materialWindow->AddWidget(texture_normal_uvset_Field); + texture_surface_Label = new wiLabel("SurfaceMap: "); @@ -468,6 +521,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) { material->surfaceMap = nullptr; material->surfaceMapName = ""; + material->SetDirty(); texture_surface_Button->SetText(""); } else @@ -493,6 +547,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) string fileName = ofn.lpstrFile; material->surfaceMap = (Texture2D*)wiResourceManager::GetGlobal().add(fileName); material->surfaceMapName = fileName; + material->SetDirty(); fileName = wiHelper::GetFileNameFromPath(fileName); texture_surface_Button->SetText(fileName); } @@ -500,6 +555,20 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) }); materialWindow->AddWidget(texture_surface_Button); + texture_surface_uvset_Field = new wiTextInputField("uvset_surface"); + texture_surface_uvset_Field->SetText(""); + texture_surface_uvset_Field->SetTooltip("uv set number"); + texture_surface_uvset_Field->SetPos(XMFLOAT2(x + 392, y)); + texture_surface_uvset_Field->SetSize(XMFLOAT2(20, 20)); + texture_surface_uvset_Field->OnInputAccepted([&](wiEventArgs args) { + MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); + if (material != nullptr) + { + material->SetUVSet_SurfaceMap(args.iValue); + } + }); + materialWindow->AddWidget(texture_surface_uvset_Field); + texture_displacement_Label = new wiLabel("DisplacementMap: "); @@ -521,6 +590,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) { material->displacementMap = nullptr; material->displacementMapName = ""; + material->SetDirty(); texture_displacement_Button->SetText(""); } else @@ -546,6 +616,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) string fileName = ofn.lpstrFile; material->displacementMap = (Texture2D*)wiResourceManager::GetGlobal().add(fileName); material->displacementMapName = fileName; + material->SetDirty(); fileName = wiHelper::GetFileNameFromPath(fileName); texture_displacement_Button->SetText(fileName); } @@ -553,6 +624,20 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) }); materialWindow->AddWidget(texture_displacement_Button); + texture_displacement_uvset_Field = new wiTextInputField("uvset_displacement"); + texture_displacement_uvset_Field->SetText(""); + texture_displacement_uvset_Field->SetTooltip("uv set number"); + texture_displacement_uvset_Field->SetPos(XMFLOAT2(x + 392, y)); + texture_displacement_uvset_Field->SetSize(XMFLOAT2(20, 20)); + texture_displacement_uvset_Field->OnInputAccepted([&](wiEventArgs args) { + MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); + if (material != nullptr) + { + material->SetUVSet_DisplacementMap(args.iValue); + } + }); + materialWindow->AddWidget(texture_displacement_uvset_Field); + texture_emissive_Label = new wiLabel("EmissiveMap: "); @@ -574,6 +659,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) { material->emissiveMap = nullptr; material->emissiveMapName = ""; + material->SetDirty(); texture_emissive_Button->SetText(""); } else @@ -599,6 +685,7 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) string fileName = ofn.lpstrFile; material->emissiveMap = (Texture2D*)wiResourceManager::GetGlobal().add(fileName); material->emissiveMapName = fileName; + material->SetDirty(); fileName = wiHelper::GetFileNameFromPath(fileName); texture_emissive_Button->SetText(fileName); } @@ -606,8 +693,92 @@ MaterialWindow::MaterialWindow(wiGUI* gui) : GUI(gui) }); materialWindow->AddWidget(texture_emissive_Button); + texture_emissive_uvset_Field = new wiTextInputField("uvset_emissive"); + texture_emissive_uvset_Field->SetText(""); + texture_emissive_uvset_Field->SetTooltip("uv set number"); + texture_emissive_uvset_Field->SetPos(XMFLOAT2(x + 392, y)); + texture_emissive_uvset_Field->SetSize(XMFLOAT2(20, 20)); + texture_emissive_uvset_Field->OnInputAccepted([&](wiEventArgs args) { + MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); + if (material != nullptr) + { + material->SetUVSet_EmissiveMap(args.iValue); + } + }); + materialWindow->AddWidget(texture_emissive_uvset_Field); - materialWindow->Translate(XMFLOAT3(screenW - 760, 120, 0)); + + + + texture_occlusion_Label = new wiLabel("OcclusionMap: "); + texture_occlusion_Label->SetPos(XMFLOAT2(x, y += step)); + texture_occlusion_Label->SetSize(XMFLOAT2(120, 20)); + materialWindow->AddWidget(texture_occlusion_Label); + + texture_occlusion_Button = new wiButton("OcclusionMap"); + texture_occlusion_Button->SetText(""); + texture_occlusion_Button->SetTooltip("Load the occlusion map texture. R: occlusion factor"); + texture_occlusion_Button->SetPos(XMFLOAT2(x + 122, y)); + texture_occlusion_Button->SetSize(XMFLOAT2(260, 20)); + texture_occlusion_Button->OnClick([&](wiEventArgs args) { + MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); + if (material == nullptr) + return; + + if (material->occlusionMap != nullptr) + { + material->occlusionMap = nullptr; + material->occlusionMapName = ""; + material->SetDirty(); + texture_occlusion_Button->SetText(""); + } + else + { + char szFile[260]; + + OPENFILENAMEA ofn; + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = nullptr; + ofn.lpstrFile = szFile; + // Set lpstrFile[0] to '\0' so that GetOpenFileName does not + // use the contents of szFile to initialize itself. + ofn.lpstrFile[0] = '\0'; + ofn.nMaxFile = sizeof(szFile); + ofn.lpstrFilter = "Texture\0*.dds;*.png;*.jpg;*.tga;\0"; + ofn.nFilterIndex = 1; + ofn.lpstrFileTitle = NULL; + ofn.nMaxFileTitle = 0; + ofn.lpstrInitialDir = NULL; + ofn.Flags = 0; + if (GetSaveFileNameA(&ofn) == TRUE) { + string fileName = ofn.lpstrFile; + material->occlusionMap = (Texture2D*)wiResourceManager::GetGlobal().add(fileName); + material->occlusionMapName = fileName; + material->SetDirty(); + fileName = wiHelper::GetFileNameFromPath(fileName); + texture_occlusion_Button->SetText(fileName); + } + } + }); + materialWindow->AddWidget(texture_occlusion_Button); + + texture_occlusion_uvset_Field = new wiTextInputField("uvset_occlusion"); + texture_occlusion_uvset_Field->SetText(""); + texture_occlusion_uvset_Field->SetTooltip("uv set number"); + texture_occlusion_uvset_Field->SetPos(XMFLOAT2(x + 392, y)); + texture_occlusion_uvset_Field->SetSize(XMFLOAT2(20, 20)); + texture_occlusion_uvset_Field->OnInputAccepted([&](wiEventArgs args) { + MaterialComponent* material = wiRenderer::GetScene().materials.GetComponent(entity); + if (material != nullptr) + { + material->SetUVSet_OcclusionMap(args.iValue); + } + }); + materialWindow->AddWidget(texture_occlusion_uvset_Field); + + + materialWindow->Translate(XMFLOAT3(screenW - 880, 120, 0)); materialWindow->SetVisible(false); SetEntity(INVALID_ENTITY); @@ -646,6 +817,7 @@ void MaterialWindow::SetEntity(Entity entity) shadowCasterCheckBox->SetCheck(material->IsCastingShadow()); flipNormalMapCheckBox->SetCheck(material->IsFlipNormalMap()); useVertexColorsCheckBox->SetCheck(material->IsUsingVertexColors()); + specularGlossinessCheckBox->SetCheck(material->IsUsingSpecularGlossinessWorkflow()); normalMapSlider->SetValue(material->normalMapStrength); roughnessSlider->SetValue(material->roughness); reflectanceSlider->SetValue(material->reflectance); @@ -655,6 +827,7 @@ void MaterialWindow::SetEntity(Entity entity) emissiveSlider->SetValue(material->emissiveColor.w); sssSlider->SetValue(material->subsurfaceScattering); pomSlider->SetValue(material->parallaxOcclusionMapping); + displacementMappingSlider->SetValue(material->displacementMapping); texAnimFrameRateSlider->SetValue(material->texAnimFrameRate); texAnimDirectionSliderU->SetValue(material->texAnimDirection.x); texAnimDirectionSliderV->SetValue(material->texAnimDirection.y); @@ -672,6 +845,23 @@ void MaterialWindow::SetEntity(Entity entity) texture_surface_Button->SetText(wiHelper::GetFileNameFromPath(material->surfaceMapName)); texture_displacement_Button->SetText(wiHelper::GetFileNameFromPath(material->displacementMapName)); texture_emissive_Button->SetText(wiHelper::GetFileNameFromPath(material->emissiveMapName)); + texture_occlusion_Button->SetText(wiHelper::GetFileNameFromPath(material->occlusionMapName)); + + ss.str(""); + ss << material->uvset_baseColorMap; + texture_baseColor_uvset_Field->SetText(ss.str()); + ss.str(""); + ss << material->uvset_normalMap; + texture_normal_uvset_Field->SetText(ss.str()); + ss.str(""); + ss << material->uvset_surfaceMap; + texture_surface_uvset_Field->SetText(ss.str()); + ss.str(""); + ss << material->uvset_emissiveMap; + texture_emissive_uvset_Field->SetText(ss.str()); + ss.str(""); + ss << material->uvset_occlusionMap; + texture_occlusion_uvset_Field->SetText(ss.str()); } else { @@ -685,5 +875,13 @@ void MaterialWindow::SetEntity(Entity entity) texture_surface_Button->SetText(""); texture_displacement_Button->SetText(""); texture_emissive_Button->SetText(""); + texture_occlusion_Button->SetText(""); + + texture_baseColor_uvset_Field->SetText(""); + texture_normal_uvset_Field->SetText(""); + texture_surface_uvset_Field->SetText(""); + texture_displacement_uvset_Field->SetText(""); + texture_emissive_uvset_Field->SetText(""); + texture_occlusion_uvset_Field->SetText(""); } } diff --git a/Editor/MaterialWindow.h b/Editor/MaterialWindow.h index b4179ac5a..5c7ab1fdd 100644 --- a/Editor/MaterialWindow.h +++ b/Editor/MaterialWindow.h @@ -28,6 +28,7 @@ public: wiCheckBox* shadowCasterCheckBox; wiCheckBox* flipNormalMapCheckBox; wiCheckBox* useVertexColorsCheckBox; + wiCheckBox* specularGlossinessCheckBox; wiSlider* normalMapSlider; wiSlider* roughnessSlider; wiSlider* reflectanceSlider; @@ -37,6 +38,7 @@ public: wiSlider* emissiveSlider; wiSlider* sssSlider; wiSlider* pomSlider; + wiSlider* displacementMappingSlider; wiSlider* texAnimFrameRateSlider; wiSlider* texAnimDirectionSliderU; wiSlider* texAnimDirectionSliderV; @@ -53,11 +55,20 @@ public: wiLabel* texture_surface_Label; wiLabel* texture_displacement_Label; wiLabel* texture_emissive_Label; + wiLabel* texture_occlusion_Label; wiButton* texture_baseColor_Button; wiButton* texture_normal_Button; wiButton* texture_surface_Button; wiButton* texture_displacement_Button; wiButton* texture_emissive_Button; + wiButton* texture_occlusion_Button; + + wiTextInputField* texture_baseColor_uvset_Field; + wiTextInputField* texture_normal_uvset_Field; + wiTextInputField* texture_surface_uvset_Field; + wiTextInputField* texture_displacement_uvset_Field; + wiTextInputField* texture_emissive_uvset_Field; + wiTextInputField* texture_occlusion_uvset_Field; }; diff --git a/Editor/MeshWindow.cpp b/Editor/MeshWindow.cpp index 67cc10e8f..6f1019a37 100644 --- a/Editor/MeshWindow.cpp +++ b/Editor/MeshWindow.cpp @@ -224,7 +224,7 @@ MeshWindow::MeshWindow(wiGUI* gui) : GUI(gui) - meshWindow->Translate(XMFLOAT3(1300, 520, 0)); + meshWindow->Translate(XMFLOAT3(screenW - 910, 520, 0)); meshWindow->SetVisible(false); SetEntity(INVALID_ENTITY); @@ -257,7 +257,8 @@ void MeshWindow::SetEntity(Entity entity) ss << "Subset count: " << mesh->subsets.size() << endl; ss << endl << "Vertex buffers: "; if (mesh->vertexBuffer_POS != nullptr) ss << "position; "; - if (mesh->vertexBuffer_TEX != nullptr) ss << "texcoord; "; + if (mesh->vertexBuffer_UV0 != nullptr) ss << "uvset_0; "; + if (mesh->vertexBuffer_UV1 != nullptr) ss << "uvset_1; "; if (mesh->vertexBuffer_ATL != nullptr) ss << "atlas; "; if (mesh->vertexBuffer_COL != nullptr) ss << "color; "; if (mesh->vertexBuffer_PRE != nullptr) ss << "prevPos; "; diff --git a/Editor/ModelImporter_GLTF.cpp b/Editor/ModelImporter_GLTF.cpp index 56475290c..08cc119a8 100644 --- a/Editor/ModelImporter_GLTF.cpp +++ b/Editor/ModelImporter_GLTF.cpp @@ -26,9 +26,9 @@ static const bool transform_to_LH = true; namespace tinygltf { - bool LoadImageData(Image *image, std::string *err, std::string *warn, - int req_width, int req_height, const unsigned char *bytes, - int size, void *) + bool LoadImageData(Image *image, const int image_idx, std::string *err, + std::string *warn, int req_width, int req_height, + const unsigned char *bytes, int size, void *) { (void)warn; @@ -319,15 +319,17 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene) material.metalness = 1.0f; material.reflectance = 0.02f; + // metallic-roughness workflow: auto& baseColorTexture = x.values.find("baseColorTexture"); auto& metallicRoughnessTexture = x.values.find("metallicRoughnessTexture"); - auto& normalTexture = x.additionalValues.find("normalTexture"); - auto& emissiveTexture = x.additionalValues.find("emissiveTexture"); - auto& occlusionTexture = x.additionalValues.find("occlusionTexture"); - auto& baseColorFactor = x.values.find("baseColorFactor"); auto& roughnessFactor = x.values.find("roughnessFactor"); auto& metallicFactor = x.values.find("metallicFactor"); + + // common workflow: + auto& normalTexture = x.additionalValues.find("normalTexture"); + auto& emissiveTexture = x.additionalValues.find("emissiveTexture"); + auto& occlusionTexture = x.additionalValues.find("occlusionTexture"); auto& emissiveFactor = x.additionalValues.find("emissiveFactor"); auto& alphaCutoff = x.additionalValues.find("alphaCutoff"); auto& alphaMode = x.additionalValues.find("alphaMode"); @@ -338,23 +340,16 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene) auto& img = state.gltfModel.images[tex.source]; RegisterTexture2D(&img, "basecolor"); material.baseColorMapName = img.uri; + material.uvset_baseColorMap = baseColorTexture->second.TextureTexCoord(); } - else if(!state.gltfModel.images.empty()) - { - // For some reason, we don't have diffuse texture, but have other textures - // I have a problem, because one model viewer displays textures on a model which has no basecolor set in its material... - // This is probably not how it should be (todo) - RegisterTexture2D(&state.gltfModel.images[0], "basecolor"); - material.baseColorMapName = state.gltfModel.images[0].uri; - } - if (normalTexture != x.additionalValues.end()) { auto& tex = state.gltfModel.textures[normalTexture->second.TextureIndex()]; auto& img = state.gltfModel.images[tex.source]; RegisterTexture2D(&img, "normal"); material.normalMapName = img.uri; - material.SetFlipNormalMap(true); + material.SetFlipNormalMap(true); // gltf import will always flip normal map by default + material.uvset_normalMap = normalTexture->second.TextureTexCoord(); } if (metallicRoughnessTexture != x.values.end()) { @@ -362,6 +357,7 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene) auto& img = state.gltfModel.images[tex.source]; RegisterTexture2D(&img, "roughness_metallic"); material.surfaceMapName = img.uri; + material.uvset_surfaceMap = metallicRoughnessTexture->second.TextureTexCoord(); } if (emissiveTexture != x.additionalValues.end()) { @@ -369,17 +365,16 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene) auto& img = state.gltfModel.images[tex.source]; RegisterTexture2D(&img, "emissive"); material.emissiveMapName = img.uri; + material.uvset_emissiveMap = emissiveTexture->second.TextureTexCoord(); + } + if (occlusionTexture != x.additionalValues.end()) + { + auto& tex = state.gltfModel.textures[occlusionTexture->second.TextureIndex()]; + auto& img = state.gltfModel.images[tex.source]; + RegisterTexture2D(&img, "occlusion"); + material.occlusionMapName = img.uri; + material.uvset_occlusionMap = occlusionTexture->second.TextureTexCoord(); } - - // Retrieve textures by name: - if (!material.baseColorMapName.empty()) - material.baseColorMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.baseColorMapName); - if (!material.normalMapName.empty()) - material.normalMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.normalMapName); - if (!material.surfaceMapName.empty()) - material.surfaceMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.surfaceMapName); - if (!material.emissiveMapName.empty()) - material.emissiveMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.emissiveMapName); if (baseColorFactor != x.values.end()) { @@ -415,6 +410,69 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene) } } + // specular-glossiness workflow (todo): + auto& specularGlossinessWorkflow = x.extensions.find("KHR_materials_pbrSpecularGlossiness"); + if (specularGlossinessWorkflow != x.extensions.end()) + { + material.SetUseSpecularGlossinessWorkflow(true); + + if (specularGlossinessWorkflow->second.Has("diffuseTexture")) + { + int index = specularGlossinessWorkflow->second.Get("diffuseTexture").Get("index").Get(); + auto& tex = state.gltfModel.textures[index]; + auto& img = state.gltfModel.images[tex.source]; + RegisterTexture2D(&img, "diffuse"); + material.baseColorMapName = img.uri; + material.uvset_baseColorMap = (uint32_t)specularGlossinessWorkflow->second.Get("diffuseTexture").Get("texCoord").Get(); + } + if (specularGlossinessWorkflow->second.Has("specularGlossinessTexture")) + { + int index = specularGlossinessWorkflow->second.Get("specularGlossinessTexture").Get("index").Get(); + auto& tex = state.gltfModel.textures[index]; + auto& img = state.gltfModel.images[tex.source]; + RegisterTexture2D(&img, "specular_glossiness"); + material.surfaceMapName = img.uri; + material.uvset_surfaceMap = (uint32_t)specularGlossinessWorkflow->second.Get("specularGlossinessTexture").Get("texCoord").Get(); + } + + if (specularGlossinessWorkflow->second.Has("diffuseFactor")) + { + auto& factor = specularGlossinessWorkflow->second.Get("diffuseFactor"); + material.baseColor.x = factor.ArrayLen() > 0 ? float(factor.Get(0).IsNumber() ? factor.Get(0).Get() : factor.Get(0).Get()) : 1.0f; + material.baseColor.y = factor.ArrayLen() > 1 ? float(factor.Get(1).IsNumber() ? factor.Get(1).Get() : factor.Get(1).Get()) : 1.0f; + material.baseColor.z = factor.ArrayLen() > 2 ? float(factor.Get(2).IsNumber() ? factor.Get(2).Get() : factor.Get(2).Get()) : 1.0f; + material.baseColor.w = factor.ArrayLen() > 3 ? float(factor.Get(3).IsNumber() ? factor.Get(3).Get() : factor.Get(3).Get()) : 1.0f; + } + //if (specularGlossinessWorkflow->second.Has("specularFactor")) + //{ + // auto& factor = specularGlossinessWorkflow->second.Get("specularFactor"); + // material.baseColor.x = factor.ArrayLen() > 0 ? float(factor.Get(0).IsNumber() ? factor.Get(0).Get() : factor.Get(0).Get()) : 1.0f; + // material.baseColor.y = factor.ArrayLen() > 0 ? float(factor.Get(1).IsNumber() ? factor.Get(1).Get() : factor.Get(1).Get()) : 1.0f; + // material.baseColor.z = factor.ArrayLen() > 0 ? float(factor.Get(2).IsNumber() ? factor.Get(2).Get() : factor.Get(2).Get()) : 1.0f; + // material.baseColor.w = factor.ArrayLen() > 0 ? float(factor.Get(3).IsNumber() ? factor.Get(3).Get() : factor.Get(3).Get()) : 1.0f; + //} + if (specularGlossinessWorkflow->second.Has("glossinessFactor")) + { + auto& factor = specularGlossinessWorkflow->second.Get("glossinessFactor"); + material.roughness = 1 - float(factor.IsNumber() ? factor.Get() : factor.Get()); + } + } + + // Avoid zero roughness factors: + material.roughness = max(0.001f, material.roughness); + + // Retrieve textures by name: + if (!material.baseColorMapName.empty()) + material.baseColorMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.baseColorMapName); + if (!material.normalMapName.empty()) + material.normalMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.normalMapName); + if (!material.surfaceMapName.empty()) + material.surfaceMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.surfaceMapName); + if (!material.emissiveMapName.empty()) + material.emissiveMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.emissiveMapName); + if (!material.occlusionMapName.empty()) + material.occlusionMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.occlusionMapName); + } if (scene.materials.GetCount() == 0) @@ -532,26 +590,26 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene) } else if (!attr_name.compare("TEXCOORD_0")) { - mesh.vertex_texcoords.resize(vertexOffset + vertexCount); + mesh.vertex_uvset_0.resize(vertexOffset + vertexCount); assert(stride == 8); for (size_t i = 0; i < vertexCount; ++i) { const XMFLOAT2& tex = ((XMFLOAT2*)data)[i]; - mesh.vertex_texcoords[vertexOffset + i].x = tex.x; - mesh.vertex_texcoords[vertexOffset + i].y = tex.y; + mesh.vertex_uvset_0[vertexOffset + i].x = tex.x; + mesh.vertex_uvset_0[vertexOffset + i].y = tex.y; } } else if (!attr_name.compare("TEXCOORD_1")) { - mesh.vertex_atlas.resize(vertexOffset + vertexCount); + mesh.vertex_uvset_1.resize(vertexOffset + vertexCount); assert(stride == 8); for (size_t i = 0; i < vertexCount; ++i) { const XMFLOAT2& tex = ((XMFLOAT2*)data)[i]; - mesh.vertex_atlas[vertexOffset + i].x = tex.x; - mesh.vertex_atlas[vertexOffset + i].y = tex.y; + mesh.vertex_uvset_1[vertexOffset + i].x = tex.x; + mesh.vertex_uvset_1[vertexOffset + i].y = tex.y; } } else if (!attr_name.compare("JOINTS_0")) diff --git a/Editor/ModelImporter_OBJ.cpp b/Editor/ModelImporter_OBJ.cpp index 4c5a5e66f..f0a9ea524 100644 --- a/Editor/ModelImporter_OBJ.cpp +++ b/Editor/ModelImporter_OBJ.cpp @@ -177,7 +177,7 @@ void ImportModel_OBJ(const std::string& fileName, Scene& scene) uniqueVertices[vertexHash] = (uint32_t)mesh.vertex_positions.size(); mesh.vertex_positions.push_back(pos); mesh.vertex_normals.push_back(nor); - mesh.vertex_texcoords.push_back(tex); + mesh.vertex_uvset_0.push_back(tex); } mesh.indices.push_back(uniqueVertices[vertexHash]); mesh.subsets.back().indexCount++; diff --git a/Editor/ObjectWindow.cpp b/Editor/ObjectWindow.cpp index 2d532c304..8195d7a48 100644 --- a/Editor/ObjectWindow.cpp +++ b/Editor/ObjectWindow.cpp @@ -83,8 +83,8 @@ static Atlas_Dim GenerateMeshAtlas(MeshComponent& meshcomponent, uint32_t resolu mesh.vertexNormalData = meshcomponent.vertex_normals.data(); mesh.vertexNormalStride = sizeof(float) * 3; } - if (!meshcomponent.vertex_texcoords.empty()) { - mesh.vertexUvData = meshcomponent.vertex_texcoords.data(); + if (!meshcomponent.vertex_uvset_0.empty()) { + mesh.vertexUvData = meshcomponent.vertex_uvset_0.data(); mesh.vertexUvStride = sizeof(float) * 2; } mesh.indexCount = (int)meshcomponent.indices.size(); @@ -116,7 +116,8 @@ static Atlas_Dim GenerateMeshAtlas(MeshComponent& meshcomponent, uint32_t resolu std::vector positions(mesh->vertexCount); std::vector atlas(mesh->vertexCount); std::vector normals; - std::vector texcoords; + std::vector uvset_0; + std::vector uvset_1; std::vector colors; std::vector boneindices; std::vector boneweights; @@ -124,9 +125,13 @@ static Atlas_Dim GenerateMeshAtlas(MeshComponent& meshcomponent, uint32_t resolu { normals.resize(mesh->vertexCount); } - if (!meshcomponent.vertex_texcoords.empty()) + if (!meshcomponent.vertex_uvset_0.empty()) { - texcoords.resize(mesh->vertexCount); + uvset_0.resize(mesh->vertexCount); + } + if (!meshcomponent.vertex_uvset_1.empty()) + { + uvset_1.resize(mesh->vertexCount); } if (!meshcomponent.vertex_colors.empty()) { @@ -153,9 +158,13 @@ static Atlas_Dim GenerateMeshAtlas(MeshComponent& meshcomponent, uint32_t resolu { normals[ind] = meshcomponent.vertex_normals[v.xref]; } - if (!texcoords.empty()) + if (!uvset_0.empty()) { - texcoords[ind] = meshcomponent.vertex_texcoords[v.xref]; + uvset_0[ind] = meshcomponent.vertex_uvset_0[v.xref]; + } + if (!uvset_1.empty()) + { + uvset_1[ind] = meshcomponent.vertex_uvset_1[v.xref]; } if (!colors.empty()) { @@ -177,9 +186,13 @@ static Atlas_Dim GenerateMeshAtlas(MeshComponent& meshcomponent, uint32_t resolu { meshcomponent.vertex_normals = normals; } - if (!texcoords.empty()) + if (!uvset_0.empty()) { - meshcomponent.vertex_texcoords = texcoords; + meshcomponent.vertex_uvset_0 = uvset_0; + } + if (!uvset_1.empty()) + { + meshcomponent.vertex_uvset_1 = uvset_1; } if (!colors.empty()) { @@ -521,7 +534,7 @@ ObjectWindow::ObjectWindow(EditorComponent* editor) : editor(editor) - objectWindow->Translate(XMFLOAT3(1300, 120, 0)); + objectWindow->Translate(XMFLOAT3(screenW - 720, 120, 0)); objectWindow->SetVisible(false); SetEntity(INVALID_ENTITY); diff --git a/Editor/OceanWindow.cpp b/Editor/OceanWindow.cpp index 08d77ee70..8ebf01aaf 100644 --- a/Editor/OceanWindow.cpp +++ b/Editor/OceanWindow.cpp @@ -163,7 +163,7 @@ OceanWindow::OceanWindow(wiGUI* gui) :GUI(gui) oceanWindow->AddWidget(colorPicker); - oceanWindow->Translate(XMFLOAT3(800, 50, 0)); + oceanWindow->Translate(XMFLOAT3(screenW - 820, 50, 0)); oceanWindow->SetVisible(false); } diff --git a/Editor/PostprocessWindow.cpp b/Editor/PostprocessWindow.cpp index b898bb5c5..5353c1430 100644 --- a/Editor/PostprocessWindow.cpp +++ b/Editor/PostprocessWindow.cpp @@ -296,7 +296,7 @@ PostprocessWindow::PostprocessWindow(wiGUI* gui, RenderPath3D* comp) : GUI(gui), ppWindow->AddWidget(outlineThicknessSlider); - ppWindow->Translate(XMFLOAT3(screenW - 380, 50, 0)); + ppWindow->Translate(XMFLOAT3(screenW - 500, 50, 0)); ppWindow->SetVisible(false); } diff --git a/Editor/WeatherWindow.cpp b/Editor/WeatherWindow.cpp index 54357fdc7..641e455c4 100644 --- a/Editor/WeatherWindow.cpp +++ b/Editor/WeatherWindow.cpp @@ -277,7 +277,7 @@ WeatherWindow::WeatherWindow(wiGUI* gui) : GUI(gui) weatherWindow->AddWidget(zenithColorPicker); - weatherWindow->Translate(XMFLOAT3(30, 30, 0)); + weatherWindow->Translate(XMFLOAT3(130, 30, 0)); weatherWindow->SetVisible(false); } diff --git a/Editor/tiny_gltf.h b/Editor/tiny_gltf.h index 633f4b3fe..c87f4ee64 100644 --- a/Editor/tiny_gltf.h +++ b/Editor/tiny_gltf.h @@ -4,7 +4,7 @@ // // The MIT License (MIT) // -// Copyright (c) 2015 - 2018 Syoyo Fujita, AurĂ©lien Chatelain and many +// Copyright (c) 2015 - 2019 Syoyo Fujita, AurĂ©lien Chatelain and many // contributors. // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -26,6 +26,10 @@ // THE SOFTWARE. // Version: +// - v2.2.0 Add loading 16bit PNG support. Add Sparse accessor support(Thanks +// to @Ybalrid) +// - v2.1.0 Add draco compression. +// - v2.0.1 Add comparsion feature(Thanks to @Selmar). // - v2.0.0 glTF 2.0!. // // Tiny glTF loader is using following third party libraries: @@ -40,16 +44,24 @@ #include #include #include +#include #include #include #include #include +#ifdef __ANDROID__ +#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS +#include +#endif +#endif + namespace tinygltf { #define TINYGLTF_MODE_POINTS (0) #define TINYGLTF_MODE_LINE (1) #define TINYGLTF_MODE_LINE_LOOP (2) +#define TINYGLTF_MODE_LINE_STRIP (3) #define TINYGLTF_MODE_TRIANGLES (4) #define TINYGLTF_MODE_TRIANGLE_STRIP (5) #define TINYGLTF_MODE_TRIANGLE_FAN (6) @@ -74,7 +86,7 @@ namespace tinygltf { #define TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE (33071) #define TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT (33648) -// Redeclarations of the above for technique.parameters. + // 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) @@ -134,172 +146,202 @@ namespace tinygltf { #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; +#define TINYGLTF_DOUBLE_EPS (1.e-12) +#define TINYGLTF_DOUBLE_EQUAL(a, b) (std::fabs((b) - (a)) < TINYGLTF_DOUBLE_EPS) -static inline int32_t GetComponentSizeInBytes(uint32_t componentType) { - if (componentType == TINYGLTF_COMPONENT_TYPE_BYTE) { - return 1; - } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - return 1; - } else if (componentType == TINYGLTF_COMPONENT_TYPE_SHORT) { - return 2; - } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - return 2; - } else if (componentType == TINYGLTF_COMPONENT_TYPE_INT) { - return 4; - } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { - return 4; - } else if (componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { - return 4; - } else if (componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) { - return 8; - } else { - // Unknown componenty type - return -1; - } -} +#ifdef __ANDROID__ +#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS + AAssetManager *asset_manager = nullptr; +#endif +#endif -static inline int32_t GetTypeSizeInBytes(uint32_t ty) { - if (ty == TINYGLTF_TYPE_SCALAR) { - return 1; - } else if (ty == TINYGLTF_TYPE_VEC2) { - return 2; - } else if (ty == TINYGLTF_TYPE_VEC3) { - return 3; - } else if (ty == TINYGLTF_TYPE_VEC4) { - return 4; - } else if (ty == TINYGLTF_TYPE_MAT2) { - return 4; - } else if (ty == TINYGLTF_TYPE_MAT3) { - return 9; - } else if (ty == TINYGLTF_TYPE_MAT4) { - return 16; - } else { - // Unknown componenty type - return -1; - } -} + 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; + + static inline int32_t GetComponentSizeInBytes(uint32_t componentType) { + if (componentType == TINYGLTF_COMPONENT_TYPE_BYTE) { + return 1; + } + else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + return 1; + } + else if (componentType == TINYGLTF_COMPONENT_TYPE_SHORT) { + return 2; + } + else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + return 2; + } + else if (componentType == TINYGLTF_COMPONENT_TYPE_INT) { + return 4; + } + else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { + return 4; + } + else if (componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { + return 4; + } + else if (componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) { + return 8; + } + else { + // Unknown componenty type + return -1; + } + } + + static inline int32_t GetTypeSizeInBytes(uint32_t ty) { + if (ty == TINYGLTF_TYPE_SCALAR) { + return 1; + } + else if (ty == TINYGLTF_TYPE_VEC2) { + return 2; + } + else if (ty == TINYGLTF_TYPE_VEC3) { + return 3; + } + else if (ty == TINYGLTF_TYPE_VEC4) { + return 4; + } + else if (ty == TINYGLTF_TYPE_MAT2) { + return 4; + } + else if (ty == TINYGLTF_TYPE_MAT3) { + return 9; + } + else if (ty == TINYGLTF_TYPE_MAT4) { + return 16; + } + else { + // Unknown componenty type + return -1; + } + } + + bool IsDataURI(const std::string &in); + bool DecodeDataURI(std::vector *out, std::string &mime_type, + const std::string &in, size_t reqBytes, bool checkSize); #ifdef __clang__ #pragma clang diagnostic push -// Suppress warning for : static Value null_value -// https://stackoverflow.com/questions/15708411/how-to-deal-with-global-constructor-warning-in-clang + // Suppress warning for : static Value null_value + // https://stackoverflow.com/questions/15708411/how-to-deal-with-global-constructor-warning-in-clang #pragma clang diagnostic ignored "-Wexit-time-destructors" #pragma clang diagnostic ignored "-Wpadded" #endif // Simple class to represent JSON object -class Value { - public: - typedef std::vector Array; - typedef std::map Object; + class Value { + public: + typedef std::vector Array; + typedef std::map Object; - Value() : type_(NULL_TYPE) {} + 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); - } + 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_); } + char Type() const { return static_cast(type_); } - bool IsBool() const { return (type_ == BOOL_TYPE); } + bool IsBool() const { return (type_ == BOOL_TYPE); } - bool IsInt() const { return (type_ == INT_TYPE); } + bool IsInt() const { return (type_ == INT_TYPE); } - bool IsNumber() const { return (type_ == NUMBER_TYPE); } + bool IsNumber() const { return (type_ == NUMBER_TYPE); } - bool IsString() const { return (type_ == STRING_TYPE); } + bool IsString() const { return (type_ == STRING_TYPE); } - bool IsBinary() const { return (type_ == BINARY_TYPE); } + bool IsBinary() const { return (type_ == BINARY_TYPE); } - bool IsArray() const { return (type_ == ARRAY_TYPE); } + bool IsArray() const { return (type_ == ARRAY_TYPE); } - bool IsObject() const { return (type_ == OBJECT_TYPE); } + bool IsObject() const { return (type_ == OBJECT_TYPE); } - // Accessor - template - const T &Get() const; - template - T &Get(); + // Accessor + template + const T &Get() const; + template + T &Get(); - // Lookup value from an array - const Value &Get(int idx) const { - static Value null_value; - assert(IsArray()); - assert(idx >= 0); - return (static_cast(idx) < array_value_.size()) - ? array_value_[static_cast(idx)] - : null_value; - } + // Lookup value from an array + const Value &Get(int idx) const { + static Value null_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; - assert(IsObject()); - Object::const_iterator it = object_value_.find(key); - return (it != object_value_.end()) ? it->second : null_value; - } + // Lookup value from a key-value pair + const Value &Get(const std::string &key) const { + static Value null_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(); - } + 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; - } + // 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 + // 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); - } + for (Object::const_iterator it = object_value_.begin(); + it != object_value_.end(); ++it) { + keys.push_back(it->first); + } - return keys; - } + return keys; + } - size_t Size() const { return (IsArray() ? ArrayLen() : Keys().size()); } + size_t Size() const { return (IsArray() ? ArrayLen() : Keys().size()); } - protected: - int type_; + bool operator==(const tinygltf::Value &other) const; - int int_value_; - double number_value_; - std::string string_value_; - std::vector binary_value_; - Array array_value_; - Object object_value_; - bool boolean_value_; -}; + 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_; + }; #ifdef __clang__ #pragma clang diagnostic pop @@ -314,13 +356,13 @@ class Value { 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_) + 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 #ifdef __clang__ @@ -329,47 +371,60 @@ TINYGLTF_VALUE_GET(Value::Object, object_value_) #pragma clang diagnostic ignored "-Wpadded" #endif -/// Agregate object for representing a color -using ColorValue = std::array; + /// Agregate object for representing a color + using ColorValue = std::array; -struct Parameter { - bool bool_value; - bool has_number_value = false; - std::string string_value; - std::vector number_array; - std::map json_double_value; - double number_value; - // context sensitive methods. depending the type of the Parameter you are - // accessing, these are either valid or not - // If this parameter represent a texture map in a material, will return the - // texture index + struct Parameter { + bool bool_value = false; + bool has_number_value = false; + std::string string_value; + std::vector number_array; + std::map json_double_value; + double number_value = 0.0; + // context sensitive methods. depending the type of the Parameter you are + // accessing, these are either valid or not + // If this parameter represent a texture map in a material, will return the + // texture index - /// Return the index of a texture if this Parameter is a texture map. - /// Returned value is only valid if the parameter represent a texture from a - /// material - int TextureIndex() const { - const auto it = json_double_value.find("index"); - if (it != std::end(json_double_value)) { - return int(it->second); - } - return -1; - } + /// Return the index of a texture if this Parameter is a texture map. + /// Returned value is only valid if the parameter represent a texture from a + /// material + int TextureIndex() const { + const auto it = json_double_value.find("index"); + if (it != std::end(json_double_value)) { + return int(it->second); + } + return -1; + } - /// Material factor, like the roughness or metalness of a material - /// Returned value is only valid if the parameter represent a texture from a - /// material - double Factor() const { return number_value; } + /// Return the index of a texture coordinate set if this Parameter is a + /// texture map. Returned value is only valid if the parameter represent a + /// texture from a material + int TextureTexCoord() const { + const auto it = json_double_value.find("texCoord"); + if (it != std::end(json_double_value)) { + return int(it->second); + } + return 0; + } - /// Return the color of a material - /// Returned value is only valid if the parameter represent a texture from a - /// material - ColorValue ColorFactor() const { - return { - {// this agregate intialize the std::array object, and uses C++11 RVO. - number_array[0], number_array[1], number_array[2], - (number_array.size() > 3 ? number_array[3] : 1.0)}}; - } -}; + /// Material factor, like the roughness or metalness of a material + /// Returned value is only valid if the parameter represent a texture from a + /// material + double Factor() const { return number_value; } + + /// Return the color of a material + /// Returned value is only valid if the parameter represent a texture from a + /// material + ColorValue ColorFactor() const { + return { + {// this agregate intialize the std::array object, and uses C++11 RVO. + number_array[0], number_array[1], number_array[2], + (number_array.size() > 3 ? number_array[3] : 1.0)} }; + } + + bool operator==(const Parameter &) const; + }; #ifdef __clang__ #pragma clang diagnostic pop @@ -380,556 +435,621 @@ struct Parameter { #pragma clang diagnostic ignored "-Wpadded" #endif -typedef std::map ParameterMap; -typedef std::map ExtensionMap; + typedef std::map ParameterMap; + typedef std::map ExtensionMap; -struct AnimationChannel { - int sampler; // required - int target_node; // required (index of the node to target) - std::string target_path; // required in ["translation", "rotation", "scale", - // "weights"] - Value extras; + struct AnimationChannel { + int sampler; // required + int target_node; // required (index of the node to target) + std::string target_path; // required in ["translation", "rotation", "scale", + // "weights"] + Value extras; - AnimationChannel() : sampler(-1), target_node(-1) {} -}; + AnimationChannel() : sampler(-1), target_node(-1) {} + bool operator==(const AnimationChannel &) const; + }; -struct AnimationSampler { - int input; // required - int output; // required - std::string interpolation; // in ["LINEAR", "STEP", "CATMULLROMSPLINE", - // "CUBICSPLINE"], default "LINEAR" - Value extras; + struct AnimationSampler { + int input; // required + int output; // required + std::string interpolation; // in ["LINEAR", "STEP", "CATMULLROMSPLINE", + // "CUBICSPLINE"], default "LINEAR" + Value extras; - AnimationSampler() : input(-1), output(-1), interpolation("LINEAR") {} -}; + AnimationSampler() : input(-1), output(-1), interpolation("LINEAR") {} + bool operator==(const AnimationSampler &) const; + }; -struct Animation { - std::string name; - std::vector channels; - std::vector samplers; - Value extras; -}; + struct Animation { + std::string name; + std::vector channels; + std::vector samplers; + Value extras; -struct Skin { - std::string name; - int inverseBindMatrices; // required here but not in the spec - int skeleton; // The index of the node used as a skeleton root - std::vector joints; // Indices of skeleton nodes + bool operator==(const Animation &) const; + }; - Skin() { - inverseBindMatrices = -1; - skeleton = -1; - } -}; + struct Skin { + std::string name; + int inverseBindMatrices; // required here but not in the spec + int skeleton; // The index of the node used as a skeleton root + std::vector joints; // Indices of skeleton nodes -struct Sampler { - std::string name; - int minFilter; // ["NEAREST", "LINEAR", "NEAREST_MIPMAP_LINEAR", - // "LINEAR_MIPMAP_NEAREST", "NEAREST_MIPMAP_LINEAR", - // "LINEAR_MIPMAP_LINEAR"] - int magFilter; // ["NEAREST", "LINEAR"] - int wrapS; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", "REPEAT"], default - // "REPEAT" - int wrapT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", "REPEAT"], default - // "REPEAT" - int wrapR; // TinyGLTF extension - Value extras; + Skin() { + inverseBindMatrices = -1; + skeleton = -1; + } + bool operator==(const Skin &) const; + }; - Sampler() - : wrapS(TINYGLTF_TEXTURE_WRAP_REPEAT), - wrapT(TINYGLTF_TEXTURE_WRAP_REPEAT) {} -}; + struct Sampler { + std::string name; + int minFilter; // ["NEAREST", "LINEAR", "NEAREST_MIPMAP_LINEAR", + // "LINEAR_MIPMAP_NEAREST", "NEAREST_MIPMAP_LINEAR", + // "LINEAR_MIPMAP_LINEAR"] + int magFilter; // ["NEAREST", "LINEAR"] + int wrapS; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", "REPEAT"], default + // "REPEAT" + int wrapT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", "REPEAT"], default + // "REPEAT" + int wrapR; // TinyGLTF extension + Value extras; -struct Image { - std::string name; - int width; - int height; - int component; - std::vector image; - int bufferView; // (required if no uri) - std::string mimeType; // (required if no uri) ["image/jpeg", "image/png", - // "image/bmp", "image/gif"] - std::string uri; // (required if no mimeType) - Value extras; + Sampler() + : minFilter(TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR), + magFilter(TINYGLTF_TEXTURE_FILTER_LINEAR), + wrapS(TINYGLTF_TEXTURE_WRAP_REPEAT), + wrapT(TINYGLTF_TEXTURE_WRAP_REPEAT), + wrapR(TINYGLTF_TEXTURE_WRAP_REPEAT) {} + bool operator==(const Sampler &) const; + }; - Image() { bufferView = -1; } -}; + struct Image { + std::string name; + int width; + int height; + int component; + int bits; // bit depth per channel. 8(byte), 16 or 32. + int pixel_type; // pixel type(TINYGLTF_COMPONENT_TYPE_***). usually + // UBYTE(bits = 8) or USHORT(bits = 16) + std::vector image; + int bufferView; // (required if no uri) + std::string mimeType; // (required if no uri) ["image/jpeg", "image/png", + // "image/bmp", "image/gif"] + std::string uri; // (required if no mimeType) + Value extras; + ExtensionMap extensions; -struct Texture { - std::string name; + // When this flag is true, data is stored to `image` in as-is format(e.g. jpeg + // compressed for "image/jpeg" mime) This feature is good if you use custom + // image loader function. (e.g. delayed decoding of images for faster glTF + // parsing) Default parser for Image does not provide as-is loading feature at + // the moment. (You can manipulate this by providing your own LoadImageData + // function) + bool as_is; - int sampler; - int source; // Required (not specified in the spec ?) - Value extras; - ExtensionMap extensions; + Image() : as_is(false) { + bufferView = -1; + width = -1; + height = -1; + component = -1; + } + bool operator==(const Image &) const; + }; - Texture() : sampler(-1), source(-1) {} -}; + struct Texture { + std::string name; -// Each extension should be stored in a ParameterMap. -// members not in the values could be included in the ParameterMap -// to keep a single material model -struct Material { - std::string name; + int sampler; + int source; + Value extras; + ExtensionMap extensions; - ParameterMap values; // PBR metal/roughness workflow - ParameterMap additionalValues; // normal/occlusion/emissive values + Texture() : sampler(-1), source(-1) {} + bool operator==(const Texture &) const; + }; - ExtensionMap extensions; - Value extras; -}; + // Each extension should be stored in a ParameterMap. + // members not in the values could be included in the ParameterMap + // to keep a single material model + struct Material { + std::string name; -struct BufferView { - std::string name; - int buffer; // Required - size_t byteOffset; // minimum 0, default 0 - size_t byteLength; // required, minimum 1 - size_t byteStride; // minimum 4, maximum 252 (multiple of 4), default 0 = - // understood to be tightly packed - int target; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] - Value extras; + ParameterMap values; // PBR metal/roughness workflow + ParameterMap additionalValues; // normal/occlusion/emissive values - BufferView() : byteOffset(0), byteStride(0) {} -}; + ExtensionMap extensions; + Value extras; -struct Accessor { - int bufferView; // optional in spec but required here since sparse accessor - // are not supported - std::string name; - size_t byteOffset; - bool normalized; // optinal. - int componentType; // (required) One of TINYGLTF_COMPONENT_TYPE_*** - size_t count; // required - int type; // (required) One of TINYGLTF_TYPE_*** .. - Value extras; + bool operator==(const Material &) const; + }; - std::vector minValues; // optional - std::vector maxValues; // optional + struct BufferView { + std::string name; + int buffer; // Required + size_t byteOffset; // minimum 0, default 0 + size_t byteLength; // required, minimum 1 + size_t byteStride; // minimum 4, maximum 252 (multiple of 4), default 0 = + // understood to be tightly packed + int target; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] + Value extras; + bool dracoDecoded; // Flag indicating this has been draco decoded - // TODO(syoyo): "sparse" + BufferView() : byteOffset(0), byteStride(0), dracoDecoded(false) {} + bool operator==(const BufferView &) const; + }; - /// - /// Utility function to compute byteStride for a given bufferView object. - /// Returns -1 upon invalid glTF value or parameter configuration. - /// - int ByteStride(const BufferView &bufferViewObject) const { - if (bufferViewObject.byteStride == 0) { - // Assume data is tightly packed. - int componentSizeInBytes = - GetComponentSizeInBytes(static_cast(componentType)); - if (componentSizeInBytes <= 0) { - return -1; - } + struct Accessor { + int bufferView; // optional in spec but required here since sparse accessor + // are not supported + std::string name; + size_t byteOffset; + bool normalized; // optinal. + int componentType; // (required) One of TINYGLTF_COMPONENT_TYPE_*** + size_t count; // required + int type; // (required) One of TINYGLTF_TYPE_*** .. + Value extras; - int typeSizeInBytes = GetTypeSizeInBytes(static_cast(type)); - if (typeSizeInBytes <= 0) { - return -1; - } + std::vector minValues; // optional + std::vector maxValues; // optional - return componentSizeInBytes * typeSizeInBytes; - } else { - // Check if byteStride is a mulple of the size of the accessor's component - // type. - int componentSizeInBytes = - GetComponentSizeInBytes(static_cast(componentType)); - if (componentSizeInBytes <= 0) { - return -1; - } + struct { + int count; + bool isSparse; + struct { + int byteOffset; + int bufferView; + int componentType; // a TINYGLTF_COMPONENT_TYPE_ value + } indices; + struct { + int bufferView; + int byteOffset; + } values; + } sparse; - if ((bufferViewObject.byteStride % uint32_t(componentSizeInBytes)) != 0) { - return -1; - } - return static_cast(bufferViewObject.byteStride); - } + /// + /// Utility function to compute byteStride for a given bufferView object. + /// Returns -1 upon invalid glTF value or parameter configuration. + /// + int ByteStride(const BufferView &bufferViewObject) const { + if (bufferViewObject.byteStride == 0) { + // Assume data is tightly packed. + int componentSizeInBytes = + GetComponentSizeInBytes(static_cast(componentType)); + if (componentSizeInBytes <= 0) { + return -1; + } - return 0; - } + int typeSizeInBytes = GetTypeSizeInBytes(static_cast(type)); + if (typeSizeInBytes <= 0) { + return -1; + } - Accessor() { bufferView = -1; } -}; + return componentSizeInBytes * typeSizeInBytes; + } + else { + // Check if byteStride is a mulple of the size of the accessor's component + // type. + int componentSizeInBytes = + GetComponentSizeInBytes(static_cast(componentType)); + if (componentSizeInBytes <= 0) { + return -1; + } -struct PerspectiveCamera { - float aspectRatio; // min > 0 - float yfov; // required. min > 0 - float zfar; // min > 0 - float znear; // required. min > 0 + if ((bufferViewObject.byteStride % uint32_t(componentSizeInBytes)) != 0) { + return -1; + } + return static_cast(bufferViewObject.byteStride); + } - PerspectiveCamera() - : aspectRatio(0.0f), - yfov(0.0f), - zfar(0.0f) // 0 = use infinite projecton matrix - , - znear(0.0f) {} + return 0; + } - ExtensionMap extensions; - Value extras; -}; + Accessor() { + bufferView = -1; + sparse.isSparse = false; + } + bool operator==(const tinygltf::Accessor &) const; + }; -struct OrthographicCamera { - float xmag; // required. must not be zero. - float ymag; // required. must not be zero. - float zfar; // required. `zfar` must be greater than `znear`. - float znear; // required + struct PerspectiveCamera { + double aspectRatio; // min > 0 + double yfov; // required. min > 0 + double zfar; // min > 0 + double znear; // required. min > 0 - OrthographicCamera() : xmag(0.0f), ymag(0.0f), zfar(0.0f), znear(0.0f) {} + PerspectiveCamera() + : aspectRatio(0.0), + yfov(0.0), + zfar(0.0) // 0 = use infinite projecton matrix + , + znear(0.0) {} + bool operator==(const PerspectiveCamera &) const; - ExtensionMap extensions; - Value extras; -}; + ExtensionMap extensions; + Value extras; + }; -struct Camera { - std::string type; // required. "perspective" or "orthographic" - std::string name; + struct OrthographicCamera { + double xmag; // required. must not be zero. + double ymag; // required. must not be zero. + double zfar; // required. `zfar` must be greater than `znear`. + double znear; // required - PerspectiveCamera perspective; - OrthographicCamera orthographic; + OrthographicCamera() : xmag(0.0), ymag(0.0), zfar(0.0), znear(0.0) {} + bool operator==(const OrthographicCamera &) const; - Camera() {} + ExtensionMap extensions; + Value extras; + }; - ExtensionMap extensions; - Value extras; -}; + struct Camera { + std::string type; // required. "perspective" or "orthographic" + std::string name; -struct Primitive { - std::map attributes; // (required) A dictionary object of - // integer, where each integer - // is the index of the accessor - // containing an attribute. - int material; // The index of the material to apply to this primitive - // when rendering. - int indices; // The index of the accessor that contains the indices. - int mode; // one of TINYGLTF_MODE_*** - std::vector> targets; // array of morph targets, - // where each target is a dict with attribues in ["POSITION, "NORMAL", - // "TANGENT"] pointing - // to their corresponding accessors - Value extras; + PerspectiveCamera perspective; + OrthographicCamera orthographic; - Primitive() { - material = -1; - indices = -1; - } -}; + Camera() {} + bool operator==(const Camera &) const; -struct Mesh { - std::string name; - std::vector primitives; - std::vector weights; // weights to be applied to the Morph Targets - std::vector> targets; - ExtensionMap extensions; - Value extras; -}; + ExtensionMap extensions; + Value extras; + }; -class Node { - public: - Node() : camera(-1), skin(-1), mesh(-1) {} + struct Primitive { + std::map attributes; // (required) A dictionary object of + // integer, where each integer + // is the index of the accessor + // containing an attribute. + int material; // The index of the material to apply to this primitive + // when rendering. + int indices; // The index of the accessor that contains the indices. + int mode; // one of TINYGLTF_MODE_*** + std::vector > targets; // array of morph targets, + // where each target is a dict with attribues in ["POSITION, "NORMAL", + // "TANGENT"] pointing + // to their corresponding accessors + ExtensionMap extensions; + Value extras; - Node(const Node &rhs) { - camera = rhs.camera; + Primitive() { + material = -1; + indices = -1; + } + bool operator==(const Primitive &) const; + }; - name = rhs.name; - skin = rhs.skin; - mesh = rhs.mesh; - children = rhs.children; - rotation = rhs.rotation; - scale = rhs.scale; - translation = rhs.translation; - matrix = rhs.matrix; - weights = rhs.weights; + struct Mesh { + std::string name; + std::vector primitives; + std::vector weights; // weights to be applied to the Morph Targets + std::vector > targets; + ExtensionMap extensions; + Value extras; - extensions = rhs.extensions; - extras = rhs.extras; - } + bool operator==(const Mesh &) const; + }; - ~Node() {} + class Node { + public: + Node() : camera(-1), skin(-1), mesh(-1) {} - int camera; // the index of the camera referenced by this node + Node(const Node &rhs) { + camera = rhs.camera; - std::string name; - int skin; - int mesh; - 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 weights; // The weights of the instantiated Morph Target + name = rhs.name; + skin = rhs.skin; + mesh = rhs.mesh; + children = rhs.children; + rotation = rhs.rotation; + scale = rhs.scale; + translation = rhs.translation; + matrix = rhs.matrix; + weights = rhs.weights; - ExtensionMap extensions; - Value extras; -}; + extensions = rhs.extensions; + extras = rhs.extras; + } + ~Node() {} + bool operator==(const Node &) const; -struct Buffer { - std::string name; - std::vector data; - std::string - uri; // considered as required here but not in the spec (need to clarify) - Value extras; -}; + int camera; // the index of the camera referenced by this node -struct Asset { - std::string version; // required - std::string generator; - std::string minVersion; - std::string copyright; - ExtensionMap extensions; - Value extras; -}; + std::string name; + int skin; + int mesh; + 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 weights; // The weights of the instantiated Morph Target -struct Scene { - std::string name; - std::vector nodes; + ExtensionMap extensions; + Value extras; + }; - ExtensionMap extensions; - Value extras; -}; + struct Buffer { + std::string name; + std::vector data; + std::string + uri; // considered as required here but not in the spec (need to clarify) + Value extras; -struct Light { - std::string name; - std::vector color; - std::string type; -}; + bool operator==(const Buffer &) const; + }; -class Model { - public: - Model() {} - ~Model() {} + struct Asset { + std::string version; // required + std::string generator; + std::string minVersion; + std::string copyright; + ExtensionMap extensions; + Value extras; - std::vector accessors; - std::vector animations; - std::vector buffers; - std::vector bufferViews; - std::vector materials; - std::vector meshes; - std::vector nodes; - std::vector textures; - std::vector images; - std::vector skins; - std::vector samplers; - std::vector cameras; - std::vector scenes; - std::vector lights; - ExtensionMap extensions; + bool operator==(const Asset &) const; + }; - int defaultScene; - std::vector extensionsUsed; - std::vector extensionsRequired; + struct Scene { + std::string name; + std::vector nodes; - Asset asset; + ExtensionMap extensions; + Value extras; - Value extras; -}; + bool operator==(const Scene &) const; + }; -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 -}; + struct Light { + std::string name; + std::vector color; + std::string type; -/// -/// LoadImageDataFunction type. Signature for custom image loading callbacks. -/// -typedef bool (*LoadImageDataFunction)(Image *, std::string *, std::string *, - int, int, const unsigned char *, int, - void *); + bool operator==(const Light &) const; + }; -/// -/// WriteImageDataFunction type. Signature for custom image writing callbacks. -/// -typedef bool (*WriteImageDataFunction)(const std::string *, const std::string *, - Image *, bool, void *); + class Model { + public: + Model() {} + ~Model() {} + bool operator==(const Model &) const; + + std::vector accessors; + std::vector animations; + std::vector buffers; + std::vector bufferViews; + std::vector materials; + std::vector meshes; + std::vector nodes; + std::vector textures; + std::vector images; + std::vector skins; + std::vector samplers; + std::vector cameras; + std::vector scenes; + std::vector lights; + ExtensionMap extensions; + + int defaultScene; + std::vector extensionsUsed; + std::vector extensionsRequired; + + 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 + }; + + /// + /// LoadImageDataFunction type. Signature for custom image loading callbacks. + /// + typedef bool(*LoadImageDataFunction)(Image *, const int, std::string *, + std::string *, int, int, + const unsigned char *, int, void *); + + /// + /// WriteImageDataFunction type. Signature for custom image writing callbacks. + /// + typedef bool(*WriteImageDataFunction)(const std::string *, const std::string *, + Image *, bool, void *); #ifndef TINYGLTF_NO_STB_IMAGE -// Declaration of default image loader callback -bool LoadImageData(Image *image, std::string *err, std::string *warn, - int req_width, int req_height, const unsigned char *bytes, - int size, void *); + // Declaration of default image loader callback + bool LoadImageData(Image *image, const int image_idx, std::string *err, + std::string *warn, int req_width, int req_height, + const unsigned char *bytes, int size, void *); #endif #ifndef TINYGLTF_NO_STB_IMAGE_WRITE -// Declaration of default image writer callback -bool WriteImageData(const std::string *basepath, const std::string *filename, - Image *image, bool embedImages, void *); + // Declaration of default image writer callback + bool WriteImageData(const std::string *basepath, const std::string *filename, + Image *image, bool embedImages, void *); #endif -/// -/// FilExistsFunction type. Signature for custom filesystem callbacks. -/// -typedef bool (*FileExistsFunction)(const std::string &abs_filename, void *); + /// + /// FilExistsFunction type. Signature for custom filesystem callbacks. + /// + typedef bool(*FileExistsFunction)(const std::string &abs_filename, void *); -/// -/// ExpandFilePathFunction type. Signature for custom filesystem callbacks. -/// -typedef std::string (*ExpandFilePathFunction)(const std::string &, void *); + /// + /// ExpandFilePathFunction type. Signature for custom filesystem callbacks. + /// + typedef std::string(*ExpandFilePathFunction)(const std::string &, void *); -/// -/// ReadWholeFileFunction type. Signature for custom filesystem callbacks. -/// -typedef bool (*ReadWholeFileFunction)(std::vector *, - std::string *, const std::string &, - void *); + /// + /// ReadWholeFileFunction type. Signature for custom filesystem callbacks. + /// + typedef bool(*ReadWholeFileFunction)(std::vector *, + std::string *, const std::string &, + void *); -/// -/// WriteWholeFileFunction type. Signature for custom filesystem callbacks. -/// -typedef bool (*WriteWholeFileFunction)(std::string *, const std::string &, - const std::vector &, - void *); + /// + /// WriteWholeFileFunction type. Signature for custom filesystem callbacks. + /// + typedef bool(*WriteWholeFileFunction)(std::string *, const std::string &, + const std::vector &, + void *); -/// -/// A structure containing all required filesystem callbacks and a pointer to -/// their user data. -/// -struct FsCallbacks { - FileExistsFunction FileExists; - ExpandFilePathFunction ExpandFilePath; - ReadWholeFileFunction ReadWholeFile; - WriteWholeFileFunction WriteWholeFile; + /// + /// A structure containing all required filesystem callbacks and a pointer to + /// their user data. + /// + struct FsCallbacks { + FileExistsFunction FileExists; + ExpandFilePathFunction ExpandFilePath; + ReadWholeFileFunction ReadWholeFile; + WriteWholeFileFunction WriteWholeFile; - void *user_data; // An argument that is passed to all fs callbacks -}; + void *user_data; // An argument that is passed to all fs callbacks + }; #ifndef TINYGLTF_NO_FS -// Declaration of default filesystem callbacks + // Declaration of default filesystem callbacks -bool FileExists(const std::string &abs_filename, void *); + bool FileExists(const std::string &abs_filename, void *); -std::string ExpandFilePath(const std::string &filepath, void *); + std::string ExpandFilePath(const std::string &filepath, void *); -bool ReadWholeFile(std::vector *out, std::string *err, - const std::string &filepath, void *); + bool ReadWholeFile(std::vector *out, std::string *err, + const std::string &filepath, void *); -bool WriteWholeFile(std::string *err, const std::string &filepath, - const std::vector &contents, void *); + bool WriteWholeFile(std::string *err, const std::string &filepath, + const std::vector &contents, void *); #endif -class TinyGLTF { - public: + class TinyGLTF { + public: #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc++98-compat" #endif - TinyGLTF() : bin_data_(nullptr), bin_size_(0), is_binary_(false) {} + TinyGLTF() : bin_data_(nullptr), bin_size_(0), is_binary_(false) {} #ifdef __clang__ #pragma clang diagnostic pop #endif - ~TinyGLTF() {} + ~TinyGLTF() {} - /// - /// Loads glTF ASCII asset from a file. - /// Set warning message to `warn` for example it fails to load asserts. - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadASCIIFromFile(Model *model, std::string *err, std::string *warn, - const std::string &filename, - unsigned int check_sections = REQUIRE_ALL); + /// + /// Loads glTF ASCII asset from a file. + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadASCIIFromFile(Model *model, std::string *err, std::string *warn, + const std::string &filename, + unsigned int check_sections = REQUIRE_ALL); - /// - /// Loads glTF ASCII asset from string(memory). - /// `length` = strlen(str); - /// Set warning message to `warn` for example it fails to load asserts. - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadASCIIFromString(Model *model, std::string *err, std::string *warn, - const char *str, const unsigned int length, - const std::string &base_dir, - unsigned int check_sections = REQUIRE_ALL); + /// + /// Loads glTF ASCII asset from string(memory). + /// `length` = strlen(str); + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadASCIIFromString(Model *model, std::string *err, std::string *warn, + 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. - /// Set warning message to `warn` for example it fails to load asserts. - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadBinaryFromFile(Model *model, std::string *err, std::string *warn, - const std::string &filename, - unsigned int check_sections = REQUIRE_ALL); + /// + /// Loads glTF binary asset from a file. + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadBinaryFromFile(Model *model, std::string *err, std::string *warn, + const std::string &filename, + unsigned int check_sections = REQUIRE_ALL); - /// - /// Loads glTF binary asset from memory. - /// `length` = strlen(str); - /// Set warning message to `warn` for example it fails to load asserts. - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadBinaryFromMemory(Model *model, std::string *err, std::string *warn, - const unsigned char *bytes, - const unsigned int length, - const std::string &base_dir = "", - unsigned int check_sections = REQUIRE_ALL); + /// + /// Loads glTF binary asset from memory. + /// `length` = strlen(str); + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadBinaryFromMemory(Model *model, std::string *err, std::string *warn, + const unsigned char *bytes, + const unsigned int length, + const std::string &base_dir = "", + unsigned int check_sections = REQUIRE_ALL); - /// - /// Write glTF to file. - /// - bool WriteGltfSceneToFile(Model *model, const std::string &filename, - bool embedImages, - bool embedBuffers /*, bool writeBinary*/); + /// + /// Write glTF to file. + /// + bool WriteGltfSceneToFile(Model *model, const std::string &filename, + bool embedImages, bool embedBuffers, + bool prettyPrint, bool writeBinary); - /// - /// Set callback to use for loading image data - /// - void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data); + /// + /// Set callback to use for loading image data + /// + void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data); - /// - /// Set callback to use for writing image data - /// - void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data); + /// + /// Set callback to use for writing image data + /// + void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data); - /// - /// Set callbacks to use for filesystem (fs) access and their user data - /// - void SetFsCallbacks(FsCallbacks callbacks); + /// + /// Set callbacks to use for filesystem (fs) access and their user data + /// + void SetFsCallbacks(FsCallbacks callbacks); - private: - /// - /// Loads glTF asset from string(memory). - /// `length` = strlen(str); - /// Set warning message to `warn` for example it fails to load asserts - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadFromString(Model *model, std::string *err, std::string *warn, - const char *str, const unsigned int length, - const std::string &base_dir, unsigned int check_sections); + private: + /// + /// Loads glTF asset from string(memory). + /// `length` = strlen(str); + /// Set warning message to `warn` for example it fails to load asserts + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadFromString(Model *model, std::string *err, std::string *warn, + 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_; + const unsigned char *bin_data_; + size_t bin_size_; + bool is_binary_; - FsCallbacks fs = { -#ifndef TINYGLTF_NO_FS - &tinygltf::FileExists, &tinygltf::ExpandFilePath, - &tinygltf::ReadWholeFile, &tinygltf::WriteWholeFile, + FsCallbacks fs = { + #ifndef TINYGLTF_NO_FS + &tinygltf::FileExists, &tinygltf::ExpandFilePath, + &tinygltf::ReadWholeFile, &tinygltf::WriteWholeFile, - nullptr // Fs callback user data -#else - nullptr, nullptr, nullptr, nullptr, + nullptr // Fs callback user data + #else + nullptr, nullptr, nullptr, nullptr, - nullptr // Fs callback user data -#endif - }; + nullptr // Fs callback user data + #endif + }; - LoadImageDataFunction LoadImageData = + LoadImageDataFunction LoadImageData = #ifndef TINYGLTF_NO_STB_IMAGE - &tinygltf::LoadImageData; + &tinygltf::LoadImageData; #else - nullptr; + nullptr; #endif - void *load_image_user_data_ = reinterpret_cast(&fs); + void *load_image_user_data_ = reinterpret_cast(&fs); - WriteImageDataFunction WriteImageData = + WriteImageDataFunction WriteImageData = #ifndef TINYGLTF_NO_STB_IMAGE_WRITE - &tinygltf::WriteImageData; + &tinygltf::WriteImageData; #else - nullptr; + nullptr; #endif - void *write_image_user_data_ = reinterpret_cast(&fs); -}; + void *write_image_user_data_ = reinterpret_cast(&fs); + }; #ifdef __clang__ #pragma clang diagnostic pop // -Wpadded @@ -939,7 +1059,7 @@ class TinyGLTF { #endif // TINY_GLTF_H_ -#ifdef TINYGLTF_IMPLEMENTATION +#if defined(TINYGLTF_IMPLEMENTATION) || defined(__INTELLISENSE__) #include //#include #ifndef TINYGLTF_NO_FS @@ -954,7 +1074,6 @@ class TinyGLTF { #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" @@ -966,6 +1085,9 @@ class TinyGLTF { #pragma clang diagnostic ignored "-Wimplicit-fallthrough" #pragma clang diagnostic ignored "-Wweak-vtables" #pragma clang diagnostic ignored "-Wcovered-switch-default" +#if __has_warning("-Wdouble-promotion") +#pragma clang diagnostic ignored "-Wdouble-promotion" +#endif #if __has_warning("-Wcomma") #pragma clang diagnostic ignored "-Wcomma" #endif @@ -987,24 +1109,73 @@ class TinyGLTF { #if __has_warning("-Wnewline-eof") #pragma clang diagnostic ignored "-Wnewline-eof" #endif +#if __has_warning("-Wunused-parameter") +#pragma clang diagnostic ignored "-Wunused-parameter" +#endif +#if __has_warning("-Wmismatched-tags") +#pragma clang diagnostic ignored "-Wmismatched-tags" +#endif #endif -#include "./json.hpp" +// Disable GCC warnigs +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" +#endif // __GNUC__ + +#ifndef TINYGLTF_NO_INCLUDE_JSON +#include "json.hpp" +#endif + +#ifdef TINYGLTF_ENABLE_DRACO +#include "draco/compression/decode.h" +#include "draco/core/decoder_buffer.h" +#endif #ifndef TINYGLTF_NO_STB_IMAGE -#include "./stb_image.h" +#ifndef TINYGLTF_NO_INCLUDE_STB_IMAGE +#include "stb_image.h" +#endif #endif #ifndef TINYGLTF_NO_STB_IMAGE_WRITE -#include "./stb_image_write.h" +#ifndef TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE +#include "stb_image_write.h" +#endif #endif #ifdef __clang__ #pragma clang diagnostic pop #endif +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + #ifdef _WIN32 -#include + +// issue 143. +// Define NOMINMAX to avoid min/max defines, +// but undef it after included windows.h +#ifndef NOMINMAX +#define TINYGLTF_INTERNAL_NOMINMAX +#define NOMINMAX +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#define TINYGLTF_INTERNAL_WIN32_LEAN_AND_MEAN +#endif +#include // include API for expanding a file path + +#ifdef TINYGLTF_INTERNAL_WIN32_LEAN_AND_MEAN +#undef WIN32_LEAN_AND_MEAN +#endif + +#if defined(TINYGLTF_INTERNAL_NOMINMAX) +#undef NOMINMAX +#endif + #elif !defined(__ANDROID__) #include #endif @@ -1030,101 +1201,316 @@ using nlohmann::json; namespace tinygltf { -static void swap4(unsigned int *val) { + // Equals function for Value, for recursivity + static bool Equals(const tinygltf::Value &one, const tinygltf::Value &other) { + if (one.Type() != other.Type()) return false; + + switch (one.Type()) { + case NULL_TYPE: + return true; + case BOOL_TYPE: + return one.Get() == other.Get(); + case NUMBER_TYPE: + return TINYGLTF_DOUBLE_EQUAL(one.Get(), other.Get()); + case INT_TYPE: + return one.Get() == other.Get(); + case OBJECT_TYPE: { + auto oneObj = one.Get(); + auto otherObj = other.Get(); + if (oneObj.size() != otherObj.size()) return false; + for (auto &it : oneObj) { + auto otherIt = otherObj.find(it.first); + if (otherIt == otherObj.end()) return false; + + if (!Equals(it.second, otherIt->second)) return false; + } + return true; + } + case ARRAY_TYPE: { + if (one.Size() != other.Size()) return false; + for (int i = 0; i < int(one.Size()); ++i) + if (Equals(one.Get(i), other.Get(i))) return false; + return true; + } + case STRING_TYPE: + return one.Get() == other.Get(); + case BINARY_TYPE: + return one.Get >() == + other.Get >(); + default: { + // unhandled type + return false; + } + } + } + + // Equals function for std::vector using TINYGLTF_DOUBLE_EPSILON + static bool Equals(const std::vector &one, + const std::vector &other) { + if (one.size() != other.size()) return false; + for (int i = 0; i < int(one.size()); ++i) { + if (!TINYGLTF_DOUBLE_EQUAL(one[size_t(i)], other[size_t(i)])) return false; + } + return true; + } + + bool Accessor::operator==(const Accessor &other) const { + return this->bufferView == other.bufferView && + this->byteOffset == other.byteOffset && + this->componentType == other.componentType && + this->count == other.count && this->extras == other.extras && + Equals(this->maxValues, other.maxValues) && + Equals(this->minValues, other.minValues) && this->name == other.name && + this->normalized == other.normalized && this->type == other.type; + } + bool Animation::operator==(const Animation &other) const { + return this->channels == other.channels && this->extras == other.extras && + this->name == other.name && this->samplers == other.samplers; + } + bool AnimationChannel::operator==(const AnimationChannel &other) const { + return this->extras == other.extras && + this->target_node == other.target_node && + this->target_path == other.target_path && + this->sampler == other.sampler; + } + bool AnimationSampler::operator==(const AnimationSampler &other) const { + return this->extras == other.extras && this->input == other.input && + this->interpolation == other.interpolation && + this->output == other.output; + } + bool Asset::operator==(const Asset &other) const { + return this->copyright == other.copyright && + this->extensions == other.extensions && this->extras == other.extras && + this->generator == other.generator && + this->minVersion == other.minVersion && this->version == other.version; + } + bool Buffer::operator==(const Buffer &other) const { + return this->data == other.data && this->extras == other.extras && + this->name == other.name && this->uri == other.uri; + } + bool BufferView::operator==(const BufferView &other) const { + return this->buffer == other.buffer && this->byteLength == other.byteLength && + this->byteOffset == other.byteOffset && + this->byteStride == other.byteStride && this->name == other.name && + this->target == other.target && this->extras == other.extras && + this->dracoDecoded == other.dracoDecoded; + } + bool Camera::operator==(const Camera &other) const { + return this->name == other.name && this->extensions == other.extensions && + this->extras == other.extras && + this->orthographic == other.orthographic && + this->perspective == other.perspective && this->type == other.type; + } + bool Image::operator==(const Image &other) const { + return this->bufferView == other.bufferView && + this->component == other.component && this->extras == other.extras && + this->height == other.height && this->image == other.image && + this->mimeType == other.mimeType && this->name == other.name && + this->uri == other.uri && this->width == other.width; + } + bool Light::operator==(const Light &other) const { + return Equals(this->color, other.color) && this->name == other.name && + this->type == other.type; + } + bool Material::operator==(const Material &other) const { + return this->additionalValues == other.additionalValues && + this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->values == other.values; + } + bool Mesh::operator==(const Mesh &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->primitives == other.primitives && + this->targets == other.targets && Equals(this->weights, other.weights); + } + bool Model::operator==(const Model &other) const { + return this->accessors == other.accessors && + this->animations == other.animations && this->asset == other.asset && + this->buffers == other.buffers && + this->bufferViews == other.bufferViews && + this->cameras == other.cameras && + this->defaultScene == other.defaultScene && + this->extensions == other.extensions && + this->extensionsRequired == other.extensionsRequired && + this->extensionsUsed == other.extensionsUsed && + this->extras == other.extras && this->images == other.images && + this->lights == other.lights && this->materials == other.materials && + this->meshes == other.meshes && this->nodes == other.nodes && + this->samplers == other.samplers && this->scenes == other.scenes && + this->skins == other.skins && this->textures == other.textures; + } + bool Node::operator==(const Node &other) const { + return this->camera == other.camera && this->children == other.children && + this->extensions == other.extensions && this->extras == other.extras && + Equals(this->matrix, other.matrix) && this->mesh == other.mesh && + this->name == other.name && Equals(this->rotation, other.rotation) && + Equals(this->scale, other.scale) && this->skin == other.skin && + Equals(this->translation, other.translation) && + Equals(this->weights, other.weights); + } + bool OrthographicCamera::operator==(const OrthographicCamera &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + TINYGLTF_DOUBLE_EQUAL(this->xmag, other.xmag) && + TINYGLTF_DOUBLE_EQUAL(this->ymag, other.ymag) && + TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) && + TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear); + } + bool Parameter::operator==(const Parameter &other) const { + if (this->bool_value != other.bool_value || + this->has_number_value != other.has_number_value) + return false; + + if (!TINYGLTF_DOUBLE_EQUAL(this->number_value, other.number_value)) + return false; + + if (this->json_double_value.size() != other.json_double_value.size()) + return false; + for (auto &it : this->json_double_value) { + auto otherIt = other.json_double_value.find(it.first); + if (otherIt == other.json_double_value.end()) return false; + + if (!TINYGLTF_DOUBLE_EQUAL(it.second, otherIt->second)) return false; + } + + if (!Equals(this->number_array, other.number_array)) return false; + + if (this->string_value != other.string_value) return false; + + return true; + } + bool PerspectiveCamera::operator==(const PerspectiveCamera &other) const { + return TINYGLTF_DOUBLE_EQUAL(this->aspectRatio, other.aspectRatio) && + this->extensions == other.extensions && this->extras == other.extras && + TINYGLTF_DOUBLE_EQUAL(this->yfov, other.yfov) && + TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) && + TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear); + } + bool Primitive::operator==(const Primitive &other) const { + return this->attributes == other.attributes && this->extras == other.extras && + this->indices == other.indices && this->material == other.material && + this->mode == other.mode && this->targets == other.targets; + } + bool Sampler::operator==(const Sampler &other) const { + return this->extras == other.extras && this->magFilter == other.magFilter && + this->minFilter == other.minFilter && this->name == other.name && + this->wrapR == other.wrapR && this->wrapS == other.wrapS && + this->wrapT == other.wrapT; + } + bool Scene::operator==(const Scene &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->nodes == other.nodes; + ; + } + bool Skin::operator==(const Skin &other) const { + return this->inverseBindMatrices == other.inverseBindMatrices && + this->joints == other.joints && this->name == other.name && + this->skeleton == other.skeleton; + } + bool Texture::operator==(const Texture &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->sampler == other.sampler && + this->source == other.source; + } + bool Value::operator==(const Value &other) const { + return Equals(*this, other); + } + + static void swap4(unsigned int *val) { #ifdef TINYGLTF_LITTLE_ENDIAN - (void)val; + (void)val; #else - unsigned int tmp = *val; - unsigned char *dst = reinterpret_cast(val); - unsigned char *src = reinterpret_cast(&tmp); + 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]; + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; #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 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, FsCallbacks *fs) { - if (fs == nullptr || fs->ExpandFilePath == nullptr || - fs->FileExists == nullptr) { - // Error, fs callback[s] missing - return std::string(); - } + static std::string FindFile(const std::vector &paths, + const std::string &filepath, FsCallbacks *fs) { + if (fs == nullptr || fs->ExpandFilePath == nullptr || + fs->FileExists == nullptr) { + // Error, fs callback[s] missing + return std::string(); + } - for (size_t i = 0; i < paths.size(); i++) { - std::string absPath = - fs->ExpandFilePath(JoinPath(paths[i], filepath), fs->user_data); - if (fs->FileExists(absPath, fs->user_data)) { - return absPath; - } - } + for (size_t i = 0; i < paths.size(); i++) { + std::string absPath = + fs->ExpandFilePath(JoinPath(paths[i], filepath), fs->user_data); + if (fs->FileExists(absPath, fs->user_data)) { + return absPath; + } + } - return std::string(); -} + return std::string(); + } -static 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 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 ""; -} + 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 ""; + } -// https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path -static std::string GetBaseFilename(const std::string &filepath) { - return filepath.substr(filepath.find_last_of("/\\") + 1); -} + // https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path + static std::string GetBaseFilename(const std::string &filepath) { + return filepath.substr(filepath.find_last_of("/\\") + 1); + } -std::string base64_encode(unsigned char const *, unsigned int len); -std::string base64_decode(std::string const &s); + std::string base64_encode(unsigned char const *, unsigned int len); + std::string base64_decode(std::string const &s); -/* - base64.cpp and base64.h + /* + base64.cpp and base64.h - Copyright (C) 2004-2008 RenĂ© Nyffenegger + 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. + 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: + 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. + 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. + 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. + 3. This notice may not be removed or altered from any source distribution. - RenĂ© Nyffenegger rene.nyffenegger@adp-gmbh.ch + RenĂ© Nyffenegger rene.nyffenegger@adp-gmbh.ch -*/ + */ #ifdef __clang__ #pragma clang diagnostic push @@ -1133,3318 +1519,3858 @@ std::string base64_decode(std::string const &s); #pragma clang diagnostic ignored "-Wsign-conversion" #pragma clang diagnostic ignored "-Wconversion" #endif -static const std::string base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; + static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; -static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); -} + static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); + } -std::string base64_encode(unsigned char const *bytes_to_encode, - unsigned int in_len) { - std::string ret; - int i = 0; - int j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; + std::string base64_encode(unsigned char const *bytes_to_encode, + unsigned int in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; - while (in_len--) { - char_array_3[i++] = *(bytes_to_encode++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = - ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = - ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; - for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]]; - i = 0; - } - } + for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]]; + i = 0; + } + } - if (i) { - for (j = i; j < 3; j++) char_array_3[j] = '\0'; + if (i) { + for (j = i; j < 3; j++) char_array_3[j] = '\0'; - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = - ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = - ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; + for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; - while ((i++ < 3)) ret += '='; - } + while ((i++ < 3)) ret += '='; + } - return ret; -} + return ret; + } -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; + 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])); + 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]; + 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; - } - } + 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; + 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])); + 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]; + 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]; - } + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } - return ret; -} + return ret; + } #ifdef __clang__ #pragma clang diagnostic pop #endif -static bool LoadExternalFile(std::vector *out, std::string *err, - std::string *warn, const std::string &filename, - const std::string &basedir, size_t reqBytes, - bool checkSize, FsCallbacks *fs) { - if (fs == nullptr || fs->FileExists == nullptr || - fs->ExpandFilePath == nullptr || fs->ReadWholeFile == nullptr) { - // This is a developer error, assert() ? - if (err) { - (*err) += "FS callback[s] not set\n"; - } - return false; - } + static bool LoadExternalFile(std::vector *out, std::string *err, + std::string *warn, const std::string &filename, + const std::string &basedir, bool required, + size_t reqBytes, bool checkSize, FsCallbacks *fs) { + if (fs == nullptr || fs->FileExists == nullptr || + fs->ExpandFilePath == nullptr || fs->ReadWholeFile == nullptr) { + // This is a developer error, assert() ? + if (err) { + (*err) += "FS callback[s] not set\n"; + } + return false; + } - out->clear(); + std::string *failMsgOut = required ? err : warn; - std::vector paths; - paths.push_back(basedir); - paths.push_back("."); + out->clear(); - std::string filepath = FindFile(paths, filename, fs); - if (filepath.empty() || filename.empty()) { - if (warn) { - (*warn) += "File not found : " + filename + "\n"; - } - return false; - } + std::vector paths; + paths.push_back(basedir); + paths.push_back("."); - std::vector buf; - std::string fileReadErr; - bool fileRead = - fs->ReadWholeFile(&buf, &fileReadErr, filepath, fs->user_data); - if (!fileRead) { - if (err) { - (*err) += "File read error : " + filepath + " : " + fileReadErr + "\n"; - } - return false; - } + std::string filepath = FindFile(paths, filename, fs); + if (filepath.empty() || filename.empty()) { + if (failMsgOut) { + (*failMsgOut) += "File not found : " + filename + "\n"; + } + return false; + } - size_t sz = buf.size(); - if (sz == 0) { - (*err) += "File is empty : " + filepath + "\n"; - return false; - } + std::vector buf; + std::string fileReadErr; + bool fileRead = + fs->ReadWholeFile(&buf, &fileReadErr, filepath, fs->user_data); + if (!fileRead) { + if (failMsgOut) { + (*failMsgOut) += + "File read error : " + filepath + " : " + fileReadErr + "\n"; + } + return false; + } - 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; - } - } + size_t sz = buf.size(); + if (sz == 0) { + if (failMsgOut) { + (*failMsgOut) += "File is empty : " + filepath + "\n"; + } + return false; + } - out->swap(buf); - return true; -} + 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 (failMsgOut) { + (*failMsgOut) += ss.str(); + } + return false; + } + } -void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) { - LoadImageData = func; - load_image_user_data_ = user_data; -} + out->swap(buf); + return true; + } + + void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) { + LoadImageData = func; + load_image_user_data_ = user_data; + } #ifndef TINYGLTF_NO_STB_IMAGE -bool LoadImageData(Image *image, std::string *err, std::string *warn, - int req_width, int req_height, const unsigned char *bytes, - int size, void *) { - (void)warn; + bool LoadImageData(Image *image, const int image_idx, std::string *err, + std::string *warn, int req_width, int req_height, + const unsigned char *bytes, int size, void *user_data) { + (void)user_data; + (void)warn; - int w, h, comp; - // if image cannot be decoded, ignore parsing and keep it by its path - // don't break in this case - // FIXME we should only enter this function if the image is embedded. If - // image->uri references - // an image file, it should be left as it is. Image loading should not be - // mandatory (to support other formats) - unsigned char *data = stbi_load_from_memory(bytes, size, &w, &h, &comp, 0); - if (!data) { - // NOTE: you can use `warn` instead of `err` - if (err) { - (*err) += "Unknown image format.\n"; - } - return false; - } + int w, h, comp, req_comp; - if (w < 1 || h < 1) { - free(data); - if (err) { - (*err) += "Invalid image data.\n"; - } - return false; - } + unsigned char *data = nullptr; - if (req_width > 0) { - if (req_width != w) { - free(data); - if (err) { - (*err) += "Image width mismatch.\n"; - } - return false; - } - } + // force 32-bit textures for common Vulkan compatibility. It appears that + // some GPU drivers do not support 24-bit images for Vulkan + req_comp = 4; + int bits = 8; + int pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; - if (req_height > 0) { - if (req_height != h) { - free(data); - if (err) { - (*err) += "Image height mismatch.\n"; - } - return false; - } - } + // It is possible that the image we want to load is a 16bit per channel image + // We are going to attempt to load it as 16bit per channel, and if it worked, + // set the image data accodingly. We are casting the returned pointer into + // unsigned char, because we are representing "bytes". But we are updating + // the Image metadata to signal that this image uses 2 bytes (16bits) per + // channel: + if (stbi_is_16_bit_from_memory(bytes, size)) { + data = (unsigned char *)stbi_load_16_from_memory(bytes, size, &w, &h, &comp, + req_comp); + if (data) { + bits = 16; + pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT; + } + } - 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()); + // at this point, if data is still NULL, it means that the image wasn't + // 16bit per channel, we are going to load it as a normal 8bit per channel + // mage as we used to do: + // if image cannot be decoded, ignore parsing and keep it by its path + // don't break in this case + // FIXME we should only enter this function if the image is embedded. If + // image->uri references + // an image file, it should be left as it is. Image loading should not be + // mandatory (to support other formats) + if (!data) data = stbi_load_from_memory(bytes, size, &w, &h, &comp, req_comp); + if (!data) { + // NOTE: you can use `warn` instead of `err` + if (err) { + (*err) += + "Unknown image format. STB cannot decode image data for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + "\".\n"; + } + return false; + } - free(data); + if (w < 1 || h < 1) { + stbi_image_free(data); + if (err) { + (*err) += "Invalid image data for image[" + std::to_string(image_idx) + + "] name = \"" + image->name + "\"\n"; + } + return false; + } - return true; -} + if (req_width > 0) { + if (req_width != w) { + stbi_image_free(data); + if (err) { + (*err) += "Image width mismatch for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + } + + if (req_height > 0) { + if (req_height != h) { + stbi_image_free(data); + if (err) { + (*err) += "Image height mismatch. for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + } + + image->width = w; + image->height = h; + image->component = req_comp; + image->bits = bits; + image->pixel_type = pixel_type; + image->image.resize(static_cast(w * h * req_comp) * (bits / 8)); + std::copy(data, data + w * h * req_comp * (bits / 8), image->image.begin()); + stbi_image_free(data); + + return true; + } #endif -void TinyGLTF::SetImageWriter(WriteImageDataFunction func, void *user_data) { - WriteImageData = func; - write_image_user_data_ = user_data; -} + void TinyGLTF::SetImageWriter(WriteImageDataFunction func, void *user_data) { + WriteImageData = func; + write_image_user_data_ = user_data; + } #ifndef TINYGLTF_NO_STB_IMAGE_WRITE -static void WriteToMemory_stbi(void *context, void *data, int size) { - std::vector *buffer = - reinterpret_cast *>(context); + static void WriteToMemory_stbi(void *context, void *data, int size) { + std::vector *buffer = + reinterpret_cast *>(context); - unsigned char *pData = reinterpret_cast(data); + unsigned char *pData = reinterpret_cast(data); - buffer->insert(buffer->end(), pData, pData + size); -} + buffer->insert(buffer->end(), pData, pData + size); + } -bool WriteImageData(const std::string *basepath, const std::string *filename, - Image *image, bool embedImages, void *fsPtr) { - const std::string ext = GetFilePathExtension(*filename); + bool WriteImageData(const std::string *basepath, const std::string *filename, + Image *image, bool embedImages, void *fsPtr) { + const std::string ext = GetFilePathExtension(*filename); - // Write image to temporary buffer - std::string header; - std::vector data; + // Write image to temporary buffer + std::string header; + std::vector data; - if (ext == "png") { - stbi_write_png_to_func(WriteToMemory_stbi, &data, image->width, - image->height, image->component, &image->image[0], - 0); - header = "data:image/png;base64,"; - } else if (ext == "jpg") { - stbi_write_jpg_to_func(WriteToMemory_stbi, &data, image->width, - image->height, image->component, &image->image[0], - 100); - header = "data:image/jpeg;base64,"; - } else if (ext == "bmp") { - stbi_write_bmp_to_func(WriteToMemory_stbi, &data, image->width, - image->height, image->component, &image->image[0]); - header = "data:image/bmp;base64,"; - } else if (!embedImages) { - // Error: can't output requested format to file - return false; - } + if (ext == "png") { + if ((image->bits != 8) || + (image->pixel_type != TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE)) { + // Unsupported pixel format + return false; + } - if (embedImages) { - // Embed base64-encoded image into URI - if (data.size()) { - image->uri = - header + - base64_encode(&data[0], static_cast(data.size())); - } else { - // Throw error? - } - } else { - // Write image to disc - FsCallbacks *fs = reinterpret_cast(fsPtr); - if (fs != nullptr && fs->WriteWholeFile == nullptr) { - const std::string imagefilepath = JoinPath(*basepath, *filename); - std::string writeError; - if (!fs->WriteWholeFile(&writeError, imagefilepath, data, - fs->user_data)) { - // Could not write image file to disc; Throw error ? - } - } else { - // Throw error? - } - image->uri = *filename; - } + if (!stbi_write_png_to_func(WriteToMemory_stbi, &data, image->width, + image->height, image->component, + &image->image[0], 0)) { + return false; + } + header = "data:image/png;base64,"; + } + else if (ext == "jpg") { + if (!stbi_write_jpg_to_func(WriteToMemory_stbi, &data, image->width, + image->height, image->component, + &image->image[0], 100)) { + return false; + } + header = "data:image/jpeg;base64,"; + } + else if (ext == "bmp") { + if (!stbi_write_bmp_to_func(WriteToMemory_stbi, &data, image->width, + image->height, image->component, + &image->image[0])) { + return false; + } + header = "data:image/bmp;base64,"; + } + else if (!embedImages) { + // Error: can't output requested format to file + return false; + } - return true; -} + if (embedImages) { + // Embed base64-encoded image into URI + if (data.size()) { + image->uri = + header + + base64_encode(&data[0], static_cast(data.size())); + } + else { + // Throw error? + } + } + else { + // Write image to disc + FsCallbacks *fs = reinterpret_cast(fsPtr); + if ((fs != nullptr) && (fs->WriteWholeFile != nullptr)) { + const std::string imagefilepath = JoinPath(*basepath, *filename); + std::string writeError; + if (!fs->WriteWholeFile(&writeError, imagefilepath, data, + fs->user_data)) { + // Could not write image file to disc; Throw error ? + return false; + } + } + else { + // Throw error? + } + image->uri = *filename; + } + + return true; + } #endif -void TinyGLTF::SetFsCallbacks(FsCallbacks callbacks) { fs = callbacks; } + void TinyGLTF::SetFsCallbacks(FsCallbacks callbacks) { fs = callbacks; } #ifndef TINYGLTF_NO_FS -// Default implementations of filesystem functions + // Default implementations of filesystem functions -bool FileExists(const std::string &abs_filename, void *) { - bool ret; -#ifdef _WIN32 - FILE *fp; - errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb"); - if (err != 0) { - return false; - } + bool FileExists(const std::string &abs_filename, void *) { + bool ret; +#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS + if (asset_manager) { + AAsset *asset = AAssetManager_open(asset_manager, abs_filename.c_str(), + AASSET_MODE_STREAMING); + if (!asset) { + return false; + } + AAsset_close(asset); + ret = true; + } + else { + return false; + } #else - FILE *fp = fopen(abs_filename.c_str(), "rb"); -#endif - if (fp) { - ret = true; - fclose(fp); - } else { - ret = false; - } - - return ret; -} - -std::string ExpandFilePath(const std::string &filepath, void *) { #ifdef _WIN32 - DWORD len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0); - char *str = new char[len]; - ExpandEnvironmentStringsA(filepath.c_str(), str, len); + 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; + } +#endif - std::string s(str); + return ret; + } - delete[] str; + std::string ExpandFilePath(const std::string &filepath, void *) { +#ifdef _WIN32 + DWORD len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0); + char *str = new char[len]; + ExpandEnvironmentStringsA(filepath.c_str(), str, len); - return s; + std::string s(str); + + delete[] str; + + return s; #else #if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \ defined(__ANDROID__) || defined(__EMSCRIPTEN__) - // no expansion - std::string s = filepath; + // no expansion + std::string s = filepath; #else - std::string s; - wordexp_t p; + std::string s; + wordexp_t p; - if (filepath.empty()) { - return ""; - } + if (filepath.empty()) { + return ""; + } - // char** w; - int ret = wordexp(filepath.c_str(), &p, 0); - if (ret) { - // err - s = filepath; - return s; - } + // 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; - } + // Use first element only. + if (p.we_wordv) { + s = std::string(p.we_wordv[0]); + wordfree(&p); + } + else { + s = filepath; + } #endif - return s; + return s; #endif -} + } -bool ReadWholeFile(std::vector *out, std::string *err, - const std::string &filepath, void *) { - std::ifstream f(filepath.c_str(), std::ifstream::binary); - if (!f) { - if (err) { - (*err) += "File open error : " + filepath + "\n"; - } - return false; - } + bool ReadWholeFile(std::vector *out, std::string *err, + const std::string &filepath, void *) { +#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS + if (asset_manager) { + AAsset *asset = AAssetManager_open(asset_manager, filepath.c_str(), + AASSET_MODE_STREAMING); + if (!asset) { + if (err) { + (*err) += "File open error : " + filepath + "\n"; + } + return false; + } + size_t size = AAsset_getLength(asset); + if (size <= 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + } + out->resize(size); + AAsset_read(asset, reinterpret_cast(&out->at(0)), size); + AAsset_close(asset); + return true; + } + else { + if (err) { + (*err) += "No asset manager specified : " + filepath + "\n"; + } + return false; + } +#else + 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()); - f.seekg(0, f.beg); + f.seekg(0, f.end); + size_t sz = static_cast(f.tellg()); + f.seekg(0, f.beg); - if (int(sz) < 0) { - if (err) { - (*err) += "Invalid file size : " + filepath + - " (does the path point to a directory?)"; - } - return false; - } else if (sz == 0) { - if (err) { - (*err) += "File is empty : " + filepath + "\n"; - } - return false; - } + if (int(sz) < 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + return false; + } + else if (sz == 0) { + if (err) { + (*err) += "File is empty : " + filepath + "\n"; + } + return false; + } - out->resize(sz); - f.read(reinterpret_cast(&out->at(0)), - static_cast(sz)); - f.close(); + out->resize(sz); + f.read(reinterpret_cast(&out->at(0)), + static_cast(sz)); + f.close(); - return true; -} + return true; +#endif + } -bool WriteWholeFile(std::string *err, const std::string &filepath, - const std::vector &contents, void *) { - std::ofstream f(filepath.c_str(), std::ofstream::binary); - if (!f) { - if (err) { - (*err) += "File open error for writing : " + filepath + "\n"; - } - return false; - } + bool WriteWholeFile(std::string *err, const std::string &filepath, + const std::vector &contents, void *) { + std::ofstream f(filepath.c_str(), std::ofstream::binary); + if (!f) { + if (err) { + (*err) += "File open error for writing : " + filepath + "\n"; + } + return false; + } - f.write(reinterpret_cast(&contents.at(0)), - static_cast(contents.size())); - if (!f) { - if (err) { - (*err) += "File write error: " + filepath + "\n"; - } - return false; - } + f.write(reinterpret_cast(&contents.at(0)), + static_cast(contents.size())); + if (!f) { + if (err) { + (*err) += "File write error: " + filepath + "\n"; + } + return false; + } - f.close(); - return true; -} + f.close(); + return true; + } #endif // TINYGLTF_NO_FS -static std::string MimeToExt(const std::string &mimeType) { - if (mimeType == "image/jpeg") { - return "jpg"; - } else if (mimeType == "image/png") { - return "png"; - } else if (mimeType == "image/bmp") { - return "bmp"; - } else if (mimeType == "image/gif") { - return "gif"; - } - - return ""; -} - -static void UpdateImageObject(Image &image, std::string &baseDir, int index, - bool embedImages, - WriteImageDataFunction *WriteImageData = nullptr, - void *user_data = nullptr) { - std::string filename; - std::string ext; - - // If image have uri. Use it it as a filename - if (image.uri.size()) { - filename = GetBaseFilename(image.uri); - ext = GetFilePathExtension(filename); - - } else if (image.name.size()) { - ext = MimeToExt(image.mimeType); - // Otherwise use name as filename - filename = image.name + "." + ext; - } else { - ext = MimeToExt(image.mimeType); - // Fallback to index of image as filename - filename = std::to_string(index) + "." + ext; - } - - // If callback is set, modify image data object - if (*WriteImageData != nullptr) { - std::string uri; - (*WriteImageData)(&baseDir, &filename, &image, embedImages, user_data); - } -} - -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/jpeg;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:image/png;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:image/bmp;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:image/gif;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:text/plain;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:application/gltf-buffer;base64,"; - if (in.find(header) == 0) { - return true; - } - - return false; -} - -static bool DecodeDataURI(std::vector *out, - std::string &mime_type, 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) { - mime_type = "image/jpeg"; - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } - - if (data.empty()) { - header = "data:image/png;base64,"; - if (in.find(header) == 0) { - mime_type = "image/png"; - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } - - if (data.empty()) { - header = "data:image/bmp;base64,"; - if (in.find(header) == 0) { - mime_type = "image/bmp"; - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } - - if (data.empty()) { - header = "data:image/gif;base64,"; - if (in.find(header) == 0) { - mime_type = "image/gif"; - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } - - if (data.empty()) { - header = "data:text/plain;base64,"; - if (in.find(header) == 0) { - mime_type = "text/plain"; - data = base64_decode(in.substr(header.size())); - } - } - - if (data.empty()) { - header = "data:application/gltf-buffer;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 bool ParseJsonAsValue(Value *ret, const json &o) { - Value val{}; - switch (o.type()) { - case json::value_t::object: { - Value::Object value_object; - for (auto it = o.begin(); it != o.end(); it++) { - Value entry; - ParseJsonAsValue(&entry, it.value()); - if (entry.Type() != NULL_TYPE) value_object[it.key()] = entry; - } - if (value_object.size() > 0) val = Value(value_object); - } break; - case json::value_t::array: { - Value::Array value_array; - for (auto it = o.begin(); it != o.end(); it++) { - Value entry; - ParseJsonAsValue(&entry, it.value()); - if (entry.Type() != NULL_TYPE) value_array.push_back(entry); - } - if (value_array.size() > 0) val = Value(value_array); - } break; - case json::value_t::string: - val = Value(o.get()); - break; - case json::value_t::boolean: - val = Value(o.get()); - break; - case json::value_t::number_integer: - case json::value_t::number_unsigned: - val = Value(static_cast(o.get())); - break; - case json::value_t::number_float: - val = Value(o.get()); - break; - case json::value_t::null: - case json::value_t::discarded: - // default: - break; - } - if (ret) *ret = val; - - return val.Type() != NULL_TYPE; -} - -static bool ParseExtrasProperty(Value *ret, const json &o) { - json::const_iterator it = o.find("extras"); - if (it == o.end()) { - return false; - } - - return ParseJsonAsValue(ret, it.value()); -} - -static bool ParseBooleanProperty(bool *ret, std::string *err, const json &o, - const std::string &property, - const bool required, - const std::string &parent_node = "") { - json::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - - if (!it.value().is_boolean()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a bool type.\n"; - } - } - return false; - } - - if (ret) { - (*ret) = it.value().get(); - } - - return true; -} - -static bool ParseNumberProperty(double *ret, std::string *err, const json &o, - const std::string &property, - const bool required, - const std::string &parent_node = "") { - json::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - - if (!it.value().is_number()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a number type.\n"; - } - } - return false; - } - - if (ret) { - (*ret) = it.value().get(); - } - - return true; -} - -static bool ParseNumberArrayProperty(std::vector *ret, std::string *err, - const json &o, const std::string &property, - bool required, - const std::string &parent_node = "") { - json::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - - if (!it.value().is_array()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not an array"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - - ret->clear(); - for (json::const_iterator i = it.value().begin(); i != it.value().end(); - i++) { - if (!i.value().is_number()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a number.\n"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - ret->push_back(i.value()); - } - - return true; -} - -static bool ParseStringProperty( - std::string *ret, std::string *err, const json &o, - const std::string &property, bool required, - const std::string &parent_node = std::string()) { - json::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.value().is_string()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a string type.\n"; - } - } - return false; - } - - if (ret) { - (*ret) = it.value(); - } - - return true; -} - -static bool ParseStringIntProperty(std::map *ret, - std::string *err, const json &o, - const std::string &property, bool required, - const std::string &parent = "") { - json::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - if (!parent.empty()) { - (*err) += - "'" + property + "' property is missing in " + parent + ".\n"; - } else { - (*err) += "'" + property + "' property is missing.\n"; - } - } - } - return false; - } - - // Make sure we are dealing with an object / dictionary. - if (!it.value().is_object()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not an object.\n"; - } - } - return false; - } - - ret->clear(); - const json &dict = it.value(); - - json::const_iterator dictIt(dict.begin()); - json::const_iterator dictItEnd(dict.end()); - - for (; dictIt != dictItEnd; ++dictIt) { - if (!dictIt.value().is_number()) { - if (required) { - if (err) { - (*err) += "'" + property + "' value is not an int.\n"; - } - } - return false; - } - - // Insert into the list. - (*ret)[dictIt.key()] = static_cast(dictIt.value()); - } - return true; -} - -static bool ParseJSONProperty(std::map *ret, - std::string *err, const json &o, - const std::string &property, bool required) { - json::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing. \n'"; - } - } - return false; - } - - if (!it.value().is_object()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a JSON object.\n"; - } - } - return false; - } - - ret->clear(); - const json &obj = it.value(); - json::const_iterator it2(obj.begin()); - json::const_iterator itEnd(obj.end()); - for (; it2 != itEnd; it2++) { - if (it2.value().is_number()) - ret->insert(std::pair(it2.key(), it2.value())); - } - - return true; -} - -static bool ParseParameterProperty(Parameter *param, std::string *err, - const json &o, const std::string &prop, - bool required) { - // 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(¶m->number_value, err, o, prop, false)) { - return param->has_number_value = true; - } else if (ParseJSONProperty(¶m->json_double_value, err, o, prop, - false)) { - return true; - } else if (ParseBooleanProperty(¶m->bool_value, err, o, prop, false)) { - return true; - } else { - if (required) { - if (err) { - (*err) += "parameter must be a string or number / number array.\n"; - } - } - return false; - } -} - -static bool ParseExtensionsProperty(ExtensionMap *ret, std::string *err, - const json &o) { - (void)err; - - json::const_iterator it = o.find("extensions"); - if (it == o.end()) { - return false; - } - if (!it.value().is_object()) { - return false; - } - ExtensionMap extensions; - json::const_iterator extIt = it.value().begin(); - for (; extIt != it.value().end(); extIt++) { - if (!extIt.value().is_object()) continue; - ParseJsonAsValue(&extensions[extIt.key()], extIt.value()); - } - if (ret) { - (*ret) = extensions; - } - return true; -} - -static bool ParseAsset(Asset *asset, std::string *err, const json &o) { - ParseStringProperty(&asset->version, err, o, "version", true, "Asset"); - ParseStringProperty(&asset->generator, err, o, "generator", false, "Asset"); - ParseStringProperty(&asset->minVersion, err, o, "minVersion", false, "Asset"); - - ParseExtensionsProperty(&asset->extensions, err, o); - - // Unity exporter version is added as extra here - ParseExtrasProperty(&(asset->extras), o); - - return true; -} - -static bool ParseImage(Image *image, std::string *err, std::string *warn, - const json &o, const std::string &basedir, - FsCallbacks *fs, - LoadImageDataFunction *LoadImageData = nullptr, - void *load_image_user_data = nullptr) { - // A glTF image must either reference a bufferView or an image uri - - // schema says oneOf [`bufferView`, `uri`] - // TODO(syoyo): Check the type of each parameters. - bool hasBufferView = (o.find("bufferView") != o.end()); - bool hasURI = (o.find("uri") != o.end()); - - if (hasBufferView && hasURI) { - // Should not both defined. - if (err) { - (*err) += - "Only one of `bufferView` or `uri` should be defined, but both are " - "defined for Image.\n"; - } - return false; - } - - if (!hasBufferView && !hasURI) { - if (err) { - (*err) += "Neither required `bufferView` nor `uri` defined for Image.\n"; - } - return false; - } - - ParseStringProperty(&image->name, err, o, "name", false); - - if (hasBufferView) { - double bufferView = -1; - if (!ParseNumberProperty(&bufferView, err, o, "bufferView", true)) { - if (err) { - (*err) += "Failed to parse `bufferView` for Image.\n"; - } - return false; - } - - std::string mime_type; - ParseStringProperty(&mime_type, err, o, "mimeType", false); - - double width = 0.0; - ParseNumberProperty(&width, err, o, "width", false); - - double height = 0.0; - ParseNumberProperty(&height, err, o, "height", false); - - // Just only save some information here. Loading actual image data from - // bufferView is done after this `ParseImage` function. - image->bufferView = static_cast(bufferView); - image->mimeType = mime_type; - image->width = static_cast(width); - image->height = static_cast(height); - - return true; - } - - // Parse URI & Load image data. - - std::string uri; - std::string tmp_err; - if (!ParseStringProperty(&uri, &tmp_err, o, "uri", true)) { - if (err) { - (*err) += "Failed to parse `uri` for Image.\n"; - } - return false; - } - - std::vector img; - - if (IsDataURI(uri)) { - if (!DecodeDataURI(&img, image->mimeType, uri, 0, false)) { - if (err) { - (*err) += "Failed to decode 'uri' for image parameter.\n"; - } - return false; - } - } else { - // Assume external file - // Keep texture path (for textures that cannot be decoded) - image->uri = uri; + static std::string MimeToExt(const std::string &mimeType) { + if (mimeType == "image/jpeg") { + return "jpg"; + } + else if (mimeType == "image/png") { + return "png"; + } + else if (mimeType == "image/bmp") { + return "bmp"; + } + else if (mimeType == "image/gif") { + return "gif"; + } + + return ""; + } + + static void UpdateImageObject(Image &image, std::string &baseDir, int index, + bool embedImages, + WriteImageDataFunction *WriteImageData = nullptr, + void *user_data = nullptr) { + std::string filename; + std::string ext; + + // If image have uri. Use it it as a filename + if (image.uri.size()) { + filename = GetBaseFilename(image.uri); + ext = GetFilePathExtension(filename); + + } + else if (image.name.size()) { + ext = MimeToExt(image.mimeType); + // Otherwise use name as filename + filename = image.name + "." + ext; + } + else { + ext = MimeToExt(image.mimeType); + // Fallback to index of image as filename + filename = std::to_string(index) + "." + ext; + } + + // If callback is set, modify image data object + if (*WriteImageData != nullptr) { + std::string uri; + (*WriteImageData)(&baseDir, &filename, &image, embedImages, user_data); + } + } + + bool IsDataURI(const std::string &in) { + std::string header = "data:application/octet-stream;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:image/jpeg;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:image/png;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:image/bmp;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:image/gif;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:text/plain;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:application/gltf-buffer;base64,"; + if (in.find(header) == 0) { + return true; + } + + return false; + } + + bool DecodeDataURI(std::vector *out, std::string &mime_type, + 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) { + mime_type = "image/jpeg"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } + + if (data.empty()) { + header = "data:image/png;base64,"; + if (in.find(header) == 0) { + mime_type = "image/png"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } + + if (data.empty()) { + header = "data:image/bmp;base64,"; + if (in.find(header) == 0) { + mime_type = "image/bmp"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } + + if (data.empty()) { + header = "data:image/gif;base64,"; + if (in.find(header) == 0) { + mime_type = "image/gif"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } + + if (data.empty()) { + header = "data:text/plain;base64,"; + if (in.find(header) == 0) { + mime_type = "text/plain"; + data = base64_decode(in.substr(header.size())); + } + } + + if (data.empty()) { + header = "data:application/gltf-buffer;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 bool ParseJsonAsValue(Value *ret, const json &o) { + Value val{}; + switch (o.type()) { + case json::value_t::object: { + Value::Object value_object; + for (auto it = o.begin(); it != o.end(); it++) { + Value entry; + ParseJsonAsValue(&entry, it.value()); + if (entry.Type() != NULL_TYPE) value_object[it.key()] = entry; + } + if (value_object.size() > 0) val = Value(value_object); + } break; + case json::value_t::array: { + Value::Array value_array; + for (auto it = o.begin(); it != o.end(); it++) { + Value entry; + ParseJsonAsValue(&entry, it.value()); + if (entry.Type() != NULL_TYPE) value_array.push_back(entry); + } + if (value_array.size() > 0) val = Value(value_array); + } break; + case json::value_t::string: + val = Value(o.get()); + break; + case json::value_t::boolean: + val = Value(o.get()); + break; + case json::value_t::number_integer: + case json::value_t::number_unsigned: + val = Value(static_cast(o.get())); + break; + case json::value_t::number_float: + val = Value(o.get()); + break; + case json::value_t::null: + case json::value_t::discarded: + // default: + break; + } + if (ret) *ret = val; + + return val.Type() != NULL_TYPE; + } + + static bool ParseExtrasProperty(Value *ret, const json &o) { + json::const_iterator it = o.find("extras"); + if (it == o.end()) { + return false; + } + + return ParseJsonAsValue(ret, it.value()); + } + + static bool ParseBooleanProperty(bool *ret, std::string *err, const json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + if (!it.value().is_boolean()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a bool type.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = it.value().get(); + } + + return true; + } + + static bool ParseNumberProperty(double *ret, std::string *err, const json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + if (!it.value().is_number()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a number type.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = it.value().get(); + } + + return true; + } + + static bool ParseNumberArrayProperty(std::vector *ret, std::string *err, + const json &o, const std::string &property, + bool required, + const std::string &parent_node = "") { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + if (!it.value().is_array()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an array"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + ret->clear(); + for (json::const_iterator i = it.value().begin(); i != it.value().end(); + i++) { + if (!i.value().is_number()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a number.\n"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + ret->push_back(i.value()); + } + + return true; + } + + static bool ParseStringProperty( + std::string *ret, std::string *err, const json &o, + const std::string &property, bool required, + const std::string &parent_node = std::string()) { + json::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.value().is_string()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a string type.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = it.value().get(); + } + + return true; + } + + static bool ParseStringIntProperty(std::map *ret, + std::string *err, const json &o, + const std::string &property, bool required, + const std::string &parent = "") { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + if (!parent.empty()) { + (*err) += + "'" + property + "' property is missing in " + parent + ".\n"; + } + else { + (*err) += "'" + property + "' property is missing.\n"; + } + } + } + return false; + } + + // Make sure we are dealing with an object / dictionary. + if (!it.value().is_object()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an object.\n"; + } + } + return false; + } + + ret->clear(); + const json &dict = it.value(); + + json::const_iterator dictIt(dict.begin()); + json::const_iterator dictItEnd(dict.end()); + + for (; dictIt != dictItEnd; ++dictIt) { + if (!dictIt.value().is_number()) { + if (required) { + if (err) { + (*err) += "'" + property + "' value is not an int.\n"; + } + } + return false; + } + + // Insert into the list. + (*ret)[dictIt.key()] = static_cast(dictIt.value()); + } + return true; + } + + static bool ParseJSONProperty(std::map *ret, + std::string *err, const json &o, + const std::string &property, bool required) { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing. \n'"; + } + } + return false; + } + + if (!it.value().is_object()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a JSON object.\n"; + } + } + return false; + } + + ret->clear(); + const json &obj = it.value(); + json::const_iterator it2(obj.begin()); + json::const_iterator itEnd(obj.end()); + for (; it2 != itEnd; it2++) { + if (it2.value().is_number()) + ret->insert(std::pair(it2.key(), it2.value())); + } + + return true; + } + + static bool ParseParameterProperty(Parameter *param, std::string *err, + const json &o, const std::string &prop, + bool required) { + // 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(¶m->number_value, err, o, prop, false)) { + return param->has_number_value = true; + } + else if (ParseJSONProperty(¶m->json_double_value, err, o, prop, + false)) { + return true; + } + else if (ParseBooleanProperty(¶m->bool_value, err, o, prop, false)) { + return true; + } + else { + if (required) { + if (err) { + (*err) += "parameter must be a string or number / number array.\n"; + } + } + return false; + } + } + + static bool ParseExtensionsProperty(ExtensionMap *ret, std::string *err, + const json &o) { + (void)err; + + json::const_iterator it = o.find("extensions"); + if (it == o.end()) { + return false; + } + if (!it.value().is_object()) { + return false; + } + ExtensionMap extensions; + json::const_iterator extIt = it.value().begin(); + for (; extIt != it.value().end(); extIt++) { + if (!extIt.value().is_object()) continue; + if (!ParseJsonAsValue(&extensions[extIt.key()], extIt.value())) { + if (!extIt.key().empty()) { + // create empty object so that an extension object is still of type + // object + extensions[extIt.key()] = Value{ Value::Object{} }; + } + } + } + if (ret) { + (*ret) = extensions; + } + return true; + } + + static bool ParseAsset(Asset *asset, std::string *err, const json &o) { + ParseStringProperty(&asset->version, err, o, "version", true, "Asset"); + ParseStringProperty(&asset->generator, err, o, "generator", false, "Asset"); + ParseStringProperty(&asset->minVersion, err, o, "minVersion", false, "Asset"); + + ParseExtensionsProperty(&asset->extensions, err, o); + + // Unity exporter version is added as extra here + ParseExtrasProperty(&(asset->extras), o); + + return true; + } + + static bool ParseImage(Image *image, const int image_idx, std::string *err, + std::string *warn, const json &o, + const std::string &basedir, FsCallbacks *fs, + LoadImageDataFunction *LoadImageData = nullptr, + void *load_image_user_data = nullptr) { + // A glTF image must either reference a bufferView or an image uri + + // schema says oneOf [`bufferView`, `uri`] + // TODO(syoyo): Check the type of each parameters. + bool hasBufferView = (o.find("bufferView") != o.end()); + bool hasURI = (o.find("uri") != o.end()); + + ParseStringProperty(&image->name, err, o, "name", false); + + if (hasBufferView && hasURI) { + // Should not both defined. + if (err) { + (*err) += + "Only one of `bufferView` or `uri` should be defined, but both are " + "defined for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + "\"\n"; + } + return false; + } + + if (!hasBufferView && !hasURI) { + if (err) { + (*err) += "Neither required `bufferView` nor `uri` defined for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + + ParseExtensionsProperty(&image->extensions, err, o); + ParseExtrasProperty(&image->extras, o); + + if (hasBufferView) { + double bufferView = -1; + if (!ParseNumberProperty(&bufferView, err, o, "bufferView", true)) { + if (err) { + (*err) += "Failed to parse `bufferView` for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + + std::string mime_type; + ParseStringProperty(&mime_type, err, o, "mimeType", false); + + double width = 0.0; + ParseNumberProperty(&width, err, o, "width", false); + + double height = 0.0; + ParseNumberProperty(&height, err, o, "height", false); + + // Just only save some information here. Loading actual image data from + // bufferView is done after this `ParseImage` function. + image->bufferView = static_cast(bufferView); + image->mimeType = mime_type; + image->width = static_cast(width); + image->height = static_cast(height); + + return true; + } + + // Parse URI & Load image data. + + std::string uri; + std::string tmp_err; + if (!ParseStringProperty(&uri, &tmp_err, o, "uri", true)) { + if (err) { + (*err) += "Failed to parse `uri` for image[" + std::to_string(image_idx) + + "] name = \"" + image->name + "\".\n"; + } + return false; + } + + std::vector img; + + if (IsDataURI(uri)) { + if (!DecodeDataURI(&img, image->mimeType, uri, 0, false)) { + if (err) { + (*err) += "Failed to decode 'uri' for image[" + + std::to_string(image_idx) + "] name = [" + image->name + + "]\n"; + } + return false; + } + } + else { + // Assume external file + // Keep texture path (for textures that cannot be decoded) + image->uri = uri; #ifdef TINYGLTF_NO_EXTERNAL_IMAGE - return true; + return true; #endif - if (!LoadExternalFile(&img, err, warn, uri, basedir, 0, false, fs)) { - if (warn) { - (*warn) += "Failed to load external 'uri' for image parameter\n"; - } - // If the image cannot be loaded, keep uri as image->uri. - return true; - } - - if (img.empty()) { - if (warn) { - (*warn) += "Image is empty.\n"; - } - return false; - } - } - - if (*LoadImageData == nullptr) { - if (err) { - (*err) += "No LoadImageData callback specified.\n"; - } - return false; - } - return (*LoadImageData)(image, err, warn, 0, 0, &img.at(0), - static_cast(img.size()), load_image_user_data); -} - -static bool ParseTexture(Texture *texture, std::string *err, const json &o, - const std::string &basedir) { - (void)basedir; - double sampler = -1.0; - double source = -1.0; - ParseNumberProperty(&sampler, err, o, "sampler", false); - - ParseNumberProperty(&source, err, o, "source", false); - - texture->sampler = static_cast(sampler); - texture->source = static_cast(source); - - ParseExtensionsProperty(&texture->extensions, err, o); - ParseExtrasProperty(&texture->extras, o); - - ParseStringProperty(&texture->name, err, o, "name", false); - - return true; -} - -static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, - FsCallbacks *fs, const std::string &basedir, - bool is_binary = false, - const unsigned char *bin_data = nullptr, - size_t bin_size = 0) { - double byteLength; - if (!ParseNumberProperty(&byteLength, err, o, "byteLength", true, "Buffer")) { - return false; - } - - // In glTF 2.0, uri is not mandatory anymore - buffer->uri.clear(); - ParseStringProperty(&buffer->uri, err, o, "uri", false, "Buffer"); - - // having an empty uri for a non embedded image should not be valid - if (!is_binary && buffer->uri.empty()) { - if (err) { - (*err) += "'uri' is missing from non binary glTF file buffer.\n"; - } - } - - json::const_iterator type = o.find("type"); - if (type != o.end()) { - if (type.value().is_string()) { - const std::string &ty = type.value(); - if (ty.compare("arraybuffer") == 0) { - // buffer.type = "arraybuffer"; - } - } - } - - size_t bytes = static_cast(byteLength); - if (is_binary) { - // Still binary glTF accepts external dataURI. - if (!buffer->uri.empty()) { - // First try embedded data URI. - if (IsDataURI(buffer->uri)) { - std::string mime_type; - if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, bytes, - true)) { - if (err) { - (*err) += - "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; - } - return false; - } - } else { - // External .bin file. - LoadExternalFile(&buffer->data, err, /* warn */ nullptr, buffer->uri, - basedir, bytes, true, fs); - } - } else { - // load data from (embedded) binary data - - if ((bin_size == 0) || (bin_data == nullptr)) { - if (err) { - (*err) += "Invalid binary data in `Buffer'.\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; - } - - // Read buffer data - buffer->data.resize(static_cast(byteLength)); - memcpy(&(buffer->data.at(0)), bin_data, static_cast(byteLength)); - } - - } else { - if (IsDataURI(buffer->uri)) { - std::string mime_type; - if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, bytes, true)) { - if (err) { - (*err) += "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; - } - return false; - } - } else { - // Assume external .bin file. - if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, buffer->uri, - basedir, bytes, true, fs)) { - return false; - } - } - } - - ParseStringProperty(&buffer->name, err, o, "name", false); - - return true; -} - -static bool ParseBufferView(BufferView *bufferView, std::string *err, - const json &o) { - double buffer = -1.0; - if (!ParseNumberProperty(&buffer, err, o, "buffer", true, "BufferView")) { - return false; - } - - double byteOffset = 0.0; - ParseNumberProperty(&byteOffset, err, o, "byteOffset", false); - - double byteLength = 1.0; - if (!ParseNumberProperty(&byteLength, err, o, "byteLength", true, - "BufferView")) { - return false; - } - - size_t byteStride = 0; - double byteStrideValue = 0.0; - if (!ParseNumberProperty(&byteStrideValue, err, o, "byteStride", false)) { - // Spec says: When byteStride of referenced bufferView is not defined, it - // means that accessor elements are tightly packed, i.e., effective stride - // equals the size of the element. - // We cannot determine the actual byteStride until Accessor are parsed, thus - // set 0(= tightly packed) here(as done in OpenGL's VertexAttribPoiner) - byteStride = 0; - } else { - byteStride = static_cast(byteStrideValue); - } - - if ((byteStride > 252) || ((byteStride % 4) != 0)) { - if (err) { - std::stringstream ss; - ss << "Invalid `byteStride' value. `byteStride' must be the multiple of " - "4 : " - << byteStride << std::endl; - - (*err) += ss.str(); - } - return 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 = static_cast(buffer); - bufferView->byteOffset = static_cast(byteOffset); - bufferView->byteLength = static_cast(byteLength); - bufferView->byteStride = static_cast(byteStride); - - return true; -} - -static bool ParseAccessor(Accessor *accessor, std::string *err, const json &o) { - double bufferView = -1.0; - if (!ParseNumberProperty(&bufferView, err, o, "bufferView", true, - "Accessor")) { - return false; - } - - double byteOffset = 0.0; - ParseNumberProperty(&byteOffset, err, o, "byteOffset", false, "Accessor"); - - bool normalized = false; - ParseBooleanProperty(&normalized, err, o, "normalized", false, "Accessor"); - - double componentType = 0.0; - if (!ParseNumberProperty(&componentType, err, o, "componentType", true, - "Accessor")) { - return false; - } - - double count = 0.0; - if (!ParseNumberProperty(&count, err, o, "count", true, "Accessor")) { - return false; - } - - std::string type; - if (!ParseStringProperty(&type, err, o, "type", true, "Accessor")) { - 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; - } - - ParseStringProperty(&accessor->name, err, o, "name", false); - - accessor->minValues.clear(); - accessor->maxValues.clear(); - ParseNumberArrayProperty(&accessor->minValues, err, o, "min", false, - "Accessor"); - - ParseNumberArrayProperty(&accessor->maxValues, err, o, "max", false, - "Accessor"); - - accessor->count = static_cast(count); - accessor->bufferView = static_cast(bufferView); - accessor->byteOffset = static_cast(byteOffset); - accessor->normalized = normalized; - { - 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 json &o) { - double material = -1.0; - ParseNumberProperty(&material, err, o, "material", false); - primitive->material = static_cast(material); - - double mode = static_cast(TINYGLTF_MODE_TRIANGLES); - ParseNumberProperty(&mode, err, o, "mode", false); - - int primMode = static_cast(mode); - primitive->mode = primMode; // Why only triangled were supported ? - - double indices = -1.0; - ParseNumberProperty(&indices, err, o, "indices", false); - primitive->indices = static_cast(indices); - if (!ParseStringIntProperty(&primitive->attributes, err, o, "attributes", - true, "Primitive")) { - return false; - } - - // Look for morph targets - json::const_iterator targetsObject = o.find("targets"); - if ((targetsObject != o.end()) && targetsObject.value().is_array()) { - for (json::const_iterator i = targetsObject.value().begin(); - i != targetsObject.value().end(); i++) { - std::map targetAttribues; - - const json &dict = i.value(); - json::const_iterator dictIt(dict.begin()); - json::const_iterator dictItEnd(dict.end()); - - for (; dictIt != dictItEnd; ++dictIt) { - targetAttribues[dictIt.key()] = static_cast(dictIt.value()); - } - primitive->targets.push_back(targetAttribues); - } - } - - ParseExtrasProperty(&(primitive->extras), o); - - return true; -} - -static bool ParseMesh(Mesh *mesh, std::string *err, const json &o) { - ParseStringProperty(&mesh->name, err, o, "name", false); - - mesh->primitives.clear(); - json::const_iterator primObject = o.find("primitives"); - if ((primObject != o.end()) && primObject.value().is_array()) { - for (json::const_iterator i = primObject.value().begin(); - i != primObject.value().end(); i++) { - Primitive primitive; - if (ParsePrimitive(&primitive, err, i.value())) { - // Only add the primitive if the parsing succeeds. - mesh->primitives.push_back(primitive); - } - } - } - - // Look for morph targets - json::const_iterator targetsObject = o.find("targets"); - if ((targetsObject != o.end()) && targetsObject.value().is_array()) { - for (json::const_iterator i = targetsObject.value().begin(); - i != targetsObject.value().end(); i++) { - std::map targetAttribues; - - const json &dict = i.value(); - json::const_iterator dictIt(dict.begin()); - json::const_iterator dictItEnd(dict.end()); - - for (; dictIt != dictItEnd; ++dictIt) { - targetAttribues[dictIt.key()] = static_cast(dictIt.value()); - } - mesh->targets.push_back(targetAttribues); - } - } - - // Should probably check if has targets and if dimensions fit - ParseNumberArrayProperty(&mesh->weights, err, o, "weights", false); - - ParseExtensionsProperty(&mesh->extensions, err, o); - ParseExtrasProperty(&(mesh->extras), o); - - return true; -} - -static bool ParseLight(Light *light, std::string *err, const json &o) { - ParseStringProperty(&light->name, err, o, "name", false); - ParseNumberArrayProperty(&light->color, err, o, "color", false); - ParseStringProperty(&light->type, err, o, "type", false); - return true; -} - -static bool ParseNode(Node *node, std::string *err, const json &o) { - ParseStringProperty(&node->name, err, o, "name", false); - - double skin = -1.0; - ParseNumberProperty(&skin, err, o, "skin", false); - node->skin = static_cast(skin); - - // Matrix and T/R/S are exclusive - if (!ParseNumberArrayProperty(&node->matrix, err, o, "matrix", false)) { - ParseNumberArrayProperty(&node->rotation, err, o, "rotation", false); - ParseNumberArrayProperty(&node->scale, err, o, "scale", false); - ParseNumberArrayProperty(&node->translation, err, o, "translation", false); - } - - double camera = -1.0; - ParseNumberProperty(&camera, err, o, "camera", false); - node->camera = static_cast(camera); - - double mesh = -1.0; - ParseNumberProperty(&mesh, err, o, "mesh", false); - node->mesh = int(mesh); - - node->children.clear(); - json::const_iterator childrenObject = o.find("children"); - if ((childrenObject != o.end()) && childrenObject.value().is_array()) { - for (json::const_iterator i = childrenObject.value().begin(); - i != childrenObject.value().end(); i++) { - if (!i.value().is_number()) { - if (err) { - (*err) += "Invalid `children` array.\n"; - } - return false; - } - const int &childrenNode = static_cast(i.value()); - node->children.push_back(childrenNode); - } - } - - ParseExtensionsProperty(&node->extensions, err, o); - ParseExtrasProperty(&(node->extras), o); - - return true; -} - -static bool ParseMaterial(Material *material, std::string *err, const json &o) { - material->values.clear(); - material->extensions.clear(); - material->additionalValues.clear(); - - json::const_iterator it(o.begin()); - json::const_iterator itEnd(o.end()); - - for (; it != itEnd; it++) { - if (it.key() == "pbrMetallicRoughness") { - if (it.value().is_object()) { - const json &values_object = it.value(); - - json::const_iterator itVal(values_object.begin()); - json::const_iterator itValEnd(values_object.end()); - - for (; itVal != itValEnd; itVal++) { - Parameter param; - if (ParseParameterProperty(¶m, err, values_object, itVal.key(), - false)) { - material->values[itVal.key()] = param; - } - } - } - } else if (it.key() == "extensions" || it.key() == "extras") { - // done later, skip, otherwise poorly parsed contents will be saved in the - // parametermap and serialized again later - } else { - Parameter param; - if (ParseParameterProperty(¶m, err, o, it.key(), false)) { - material->additionalValues[it.key()] = param; - } - } - } - - ParseExtensionsProperty(&material->extensions, err, o); - ParseExtrasProperty(&(material->extras), o); - - return true; -} - -static bool ParseAnimationChannel(AnimationChannel *channel, std::string *err, - const json &o) { - double samplerIndex = -1.0; - double targetIndex = -1.0; - if (!ParseNumberProperty(&samplerIndex, err, o, "sampler", true, - "AnimationChannel")) { - if (err) { - (*err) += "`sampler` field is missing in animation channels\n"; - } - return false; - } - - json::const_iterator targetIt = o.find("target"); - if ((targetIt != o.end()) && targetIt.value().is_object()) { - const json &target_object = targetIt.value(); - - if (!ParseNumberProperty(&targetIndex, err, target_object, "node", true)) { - if (err) { - (*err) += "`node` 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; - } - } - - channel->sampler = static_cast(samplerIndex); - channel->target_node = static_cast(targetIndex); - - ParseExtrasProperty(&(channel->extras), o); - - return true; -} - -static bool ParseAnimation(Animation *animation, std::string *err, - const json &o) { - { - json::const_iterator channelsIt = o.find("channels"); - if ((channelsIt != o.end()) && channelsIt.value().is_array()) { - for (json::const_iterator i = channelsIt.value().begin(); - i != channelsIt.value().end(); i++) { - AnimationChannel channel; - if (ParseAnimationChannel(&channel, err, i.value())) { - // Only add the channel if the parsing succeeds. - animation->channels.push_back(channel); - } - } - } - } - - { - json::const_iterator samplerIt = o.find("samplers"); - if ((samplerIt != o.end()) && samplerIt.value().is_array()) { - const json &sampler_array = samplerIt.value(); - - json::const_iterator it = sampler_array.begin(); - json::const_iterator itEnd = sampler_array.end(); - - for (; it != itEnd; it++) { - const json &s = it->get(); - - AnimationSampler sampler; - double inputIndex = -1.0; - double outputIndex = -1.0; - if (!ParseNumberProperty(&inputIndex, 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 (!ParseNumberProperty(&outputIndex, err, s, "output", true)) { - if (err) { - (*err) += "`output` field is missing in animation.sampler\n"; - } - return false; - } - sampler.input = static_cast(inputIndex); - sampler.output = static_cast(outputIndex); - ParseExtrasProperty(&(sampler.extras), s); - animation->samplers.push_back(sampler); - } - } - } - - ParseStringProperty(&animation->name, err, o, "name", false); - - ParseExtrasProperty(&(animation->extras), o); - - return true; -} - -static bool ParseSampler(Sampler *sampler, std::string *err, const json &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_REPEAT); - double wrapT = static_cast(TINYGLTF_TEXTURE_WRAP_REPEAT); - 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; -} - -static bool ParseSkin(Skin *skin, std::string *err, const json &o) { - ParseStringProperty(&skin->name, err, o, "name", false, "Skin"); - - std::vector joints; - if (!ParseNumberArrayProperty(&joints, err, o, "joints", false, "Skin")) { - return false; - } - - double skeleton = -1.0; - ParseNumberProperty(&skeleton, err, o, "skeleton", false, "Skin"); - skin->skeleton = static_cast(skeleton); - - skin->joints.resize(joints.size()); - for (size_t i = 0; i < joints.size(); i++) { - skin->joints[i] = static_cast(joints[i]); - } - - double invBind = -1.0; - ParseNumberProperty(&invBind, err, o, "inverseBindMatrices", true, "Skin"); - skin->inverseBindMatrices = static_cast(invBind); - - return true; -} - -static bool ParsePerspectiveCamera(PerspectiveCamera *camera, std::string *err, - const json &o) { - double yfov = 0.0; - if (!ParseNumberProperty(&yfov, err, o, "yfov", true, "OrthographicCamera")) { - return false; - } - - double znear = 0.0; - if (!ParseNumberProperty(&znear, err, o, "znear", true, - "PerspectiveCamera")) { - return false; - } - - double aspectRatio = 0.0; // = invalid - ParseNumberProperty(&aspectRatio, err, o, "aspectRatio", false, - "PerspectiveCamera"); - - double zfar = 0.0; // = invalid - ParseNumberProperty(&zfar, err, o, "zfar", false, "PerspectiveCamera"); - - camera->aspectRatio = float(aspectRatio); - camera->zfar = float(zfar); - camera->yfov = float(yfov); - camera->znear = float(znear); - - ParseExtensionsProperty(&camera->extensions, err, o); - ParseExtrasProperty(&(camera->extras), o); - - // TODO(syoyo): Validate parameter values. - - return true; -} - -static bool ParseOrthographicCamera(OrthographicCamera *camera, - std::string *err, const json &o) { - double xmag = 0.0; - if (!ParseNumberProperty(&xmag, err, o, "xmag", true, "OrthographicCamera")) { - return false; - } - - double ymag = 0.0; - if (!ParseNumberProperty(&ymag, err, o, "ymag", true, "OrthographicCamera")) { - return false; - } - - double zfar = 0.0; - if (!ParseNumberProperty(&zfar, err, o, "zfar", true, "OrthographicCamera")) { - return false; - } - - double znear = 0.0; - if (!ParseNumberProperty(&znear, err, o, "znear", true, - "OrthographicCamera")) { - return false; - } - - ParseExtensionsProperty(&camera->extensions, err, o); - ParseExtrasProperty(&(camera->extras), o); - - camera->xmag = float(xmag); - camera->ymag = float(ymag); - camera->zfar = float(zfar); - camera->znear = float(znear); - - // TODO(syoyo): Validate parameter values. - - return true; -} - -static bool ParseCamera(Camera *camera, std::string *err, const json &o) { - if (!ParseStringProperty(&camera->type, err, o, "type", true, "Camera")) { - return false; - } - - if (camera->type.compare("orthographic") == 0) { - if (o.find("orthographic") == o.end()) { - if (err) { - std::stringstream ss; - ss << "Orhographic camera description not found." << std::endl; - (*err) += ss.str(); - } - return false; - } - - const json &v = o.find("orthographic").value(); - if (!v.is_object()) { - if (err) { - std::stringstream ss; - ss << "\"orthographic\" is not a JSON object." << std::endl; - (*err) += ss.str(); - } - return false; - } - - if (!ParseOrthographicCamera(&camera->orthographic, err, v.get())) { - return false; - } - } else if (camera->type.compare("perspective") == 0) { - if (o.find("perspective") == o.end()) { - if (err) { - std::stringstream ss; - ss << "Perspective camera description not found." << std::endl; - (*err) += ss.str(); - } - return false; - } - - const json &v = o.find("perspective").value(); - if (!v.is_object()) { - if (err) { - std::stringstream ss; - ss << "\"perspective\" is not a JSON object." << std::endl; - (*err) += ss.str(); - } - return false; - } - - if (!ParsePerspectiveCamera(&camera->perspective, err, v.get())) { - return false; - } - } else { - if (err) { - std::stringstream ss; - ss << "Invalid camera type: \"" << camera->type - << "\". Must be \"perspective\" or \"orthographic\"" << std::endl; - (*err) += ss.str(); - } - return false; - } - - ParseStringProperty(&camera->name, err, o, "name", false); - - ParseExtensionsProperty(&camera->extensions, err, o); - ParseExtrasProperty(&(camera->extras), o); - - return true; -} - -bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, - const char *str, unsigned int length, - const std::string &base_dir, - unsigned int check_sections) { - if (length < 4) { - if (err) { - (*err) = "JSON string too short.\n"; - } - return false; - } - - json v; + if (!LoadExternalFile(&img, err, warn, uri, basedir, false, 0, false, fs)) { + if (warn) { + (*warn) += "Failed to load external 'uri' for image[" + + std::to_string(image_idx) + "] name = [" + image->name + + "]\n"; + } + // If the image cannot be loaded, keep uri as image->uri. + return true; + } + + if (img.empty()) { + if (warn) { + (*warn) += "Image data is empty for image[" + + std::to_string(image_idx) + "] name = [" + image->name + + "] \n"; + } + return false; + } + } + + if (*LoadImageData == nullptr) { + if (err) { + (*err) += "No LoadImageData callback specified.\n"; + } + return false; + } + return (*LoadImageData)(image, image_idx, err, warn, 0, 0, &img.at(0), + static_cast(img.size()), load_image_user_data); + } + + static bool ParseTexture(Texture *texture, std::string *err, const json &o, + const std::string &basedir) { + (void)basedir; + double sampler = -1.0; + double source = -1.0; + ParseNumberProperty(&sampler, err, o, "sampler", false); + + ParseNumberProperty(&source, err, o, "source", false); + + texture->sampler = static_cast(sampler); + texture->source = static_cast(source); + + ParseExtensionsProperty(&texture->extensions, err, o); + ParseExtrasProperty(&texture->extras, o); + + ParseStringProperty(&texture->name, err, o, "name", false); + + return true; + } + + static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, + FsCallbacks *fs, const std::string &basedir, + bool is_binary = false, + const unsigned char *bin_data = nullptr, + size_t bin_size = 0) { + double byteLength; + if (!ParseNumberProperty(&byteLength, err, o, "byteLength", true, "Buffer")) { + return false; + } + + // In glTF 2.0, uri is not mandatory anymore + buffer->uri.clear(); + ParseStringProperty(&buffer->uri, err, o, "uri", false, "Buffer"); + + // having an empty uri for a non embedded image should not be valid + if (!is_binary && buffer->uri.empty()) { + if (err) { + (*err) += "'uri' is missing from non binary glTF file buffer.\n"; + } + } + + json::const_iterator type = o.find("type"); + if (type != o.end()) { + if (type.value().is_string()) { + const std::string &ty = type.value(); + if (ty.compare("arraybuffer") == 0) { + // buffer.type = "arraybuffer"; + } + } + } + + size_t bytes = static_cast(byteLength); + if (is_binary) { + // Still binary glTF accepts external dataURI. + if (!buffer->uri.empty()) { + // First try embedded data URI. + if (IsDataURI(buffer->uri)) { + std::string mime_type; + if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, bytes, + true)) { + if (err) { + (*err) += + "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; + } + return false; + } + } + else { + // External .bin file. + if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, + buffer->uri, basedir, true, bytes, true, fs)) { + return false; + } + } + } + else { + // load data from (embedded) binary data + + if ((bin_size == 0) || (bin_data == nullptr)) { + if (err) { + (*err) += "Invalid binary data in `Buffer'.\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; + } + + // Read buffer data + buffer->data.resize(static_cast(byteLength)); + memcpy(&(buffer->data.at(0)), bin_data, static_cast(byteLength)); + } + + } + else { + if (IsDataURI(buffer->uri)) { + std::string mime_type; + if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, bytes, true)) { + if (err) { + (*err) += "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; + } + return false; + } + } + else { + // Assume external .bin file. + if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, buffer->uri, + basedir, true, bytes, true, fs)) { + return false; + } + } + } + + ParseStringProperty(&buffer->name, err, o, "name", false); + + return true; + } + + static bool ParseBufferView(BufferView *bufferView, std::string *err, + const json &o) { + double buffer = -1.0; + if (!ParseNumberProperty(&buffer, err, o, "buffer", true, "BufferView")) { + return false; + } + + double byteOffset = 0.0; + ParseNumberProperty(&byteOffset, err, o, "byteOffset", false); + + double byteLength = 1.0; + if (!ParseNumberProperty(&byteLength, err, o, "byteLength", true, + "BufferView")) { + return false; + } + + size_t byteStride = 0; + double byteStrideValue = 0.0; + if (!ParseNumberProperty(&byteStrideValue, err, o, "byteStride", false)) { + // Spec says: When byteStride of referenced bufferView is not defined, it + // means that accessor elements are tightly packed, i.e., effective stride + // equals the size of the element. + // We cannot determine the actual byteStride until Accessor are parsed, thus + // set 0(= tightly packed) here(as done in OpenGL's VertexAttribPoiner) + byteStride = 0; + } + else { + byteStride = static_cast(byteStrideValue); + } + + if ((byteStride > 252) || ((byteStride % 4) != 0)) { + if (err) { + std::stringstream ss; + ss << "Invalid `byteStride' value. `byteStride' must be the multiple of " + "4 : " + << byteStride << std::endl; + + (*err) += ss.str(); + } + return 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 = static_cast(buffer); + bufferView->byteOffset = static_cast(byteOffset); + bufferView->byteLength = static_cast(byteLength); + bufferView->byteStride = static_cast(byteStride); + return true; + } + + static bool ParseSparseAccessor(Accessor *accessor, std::string *err, + const json &o) { + accessor->sparse.isSparse = true; + + double count = 0.0; + ParseNumberProperty(&count, err, o, "count", true); + + const auto indices_iterator = o.find("indices"); + const auto values_iterator = o.find("values"); + if (indices_iterator == o.end()) { + (*err) = "the sparse object of this accessor doesn't have indices"; + return false; + } + + if (values_iterator == o.end()) { + (*err) = "the sparse object ob ths accessor doesn't have values"; + return false; + } + + const json &indices_obj = *indices_iterator; + const json &values_obj = *values_iterator; + + double indices_buffer_view = 0.0, indices_byte_offset = 0.0, + component_type = 0.0; + ParseNumberProperty(&indices_buffer_view, err, indices_obj, "bufferView", + true); + ParseNumberProperty(&indices_byte_offset, err, indices_obj, "byteOffset", + true); + ParseNumberProperty(&component_type, err, indices_obj, "componentType", true); + + double values_buffer_view = 0.0, values_byte_offset = 0.0; + ParseNumberProperty(&values_buffer_view, err, values_obj, "bufferView", true); + ParseNumberProperty(&values_byte_offset, err, values_obj, "byteOffset", true); + + accessor->sparse.count = static_cast(count); + accessor->sparse.indices.bufferView = static_cast(indices_buffer_view); + accessor->sparse.indices.byteOffset = static_cast(indices_byte_offset); + accessor->sparse.indices.componentType = static_cast(component_type); + accessor->sparse.values.bufferView = static_cast(values_buffer_view); + accessor->sparse.values.byteOffset = static_cast(values_byte_offset); + + // todo check theses values + + return true; + } + + static bool ParseAccessor(Accessor *accessor, std::string *err, const json &o) { + double bufferView = -1.0; + ParseNumberProperty(&bufferView, err, o, "bufferView", false, "Accessor"); + + double byteOffset = 0.0; + ParseNumberProperty(&byteOffset, err, o, "byteOffset", false, "Accessor"); + + bool normalized = false; + ParseBooleanProperty(&normalized, err, o, "normalized", false, "Accessor"); + + double componentType = 0.0; + if (!ParseNumberProperty(&componentType, err, o, "componentType", true, + "Accessor")) { + return false; + } + + double count = 0.0; + if (!ParseNumberProperty(&count, err, o, "count", true, "Accessor")) { + return false; + } + + std::string type; + if (!ParseStringProperty(&type, err, o, "type", true, "Accessor")) { + 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; + } + + ParseStringProperty(&accessor->name, err, o, "name", false); + + accessor->minValues.clear(); + accessor->maxValues.clear(); + ParseNumberArrayProperty(&accessor->minValues, err, o, "min", false, + "Accessor"); + + ParseNumberArrayProperty(&accessor->maxValues, err, o, "max", false, + "Accessor"); + + accessor->count = static_cast(count); + accessor->bufferView = static_cast(bufferView); + accessor->byteOffset = static_cast(byteOffset); + accessor->normalized = normalized; + { + 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); + + // check if accessor has a "sparse" object: + const auto iterator = o.find("sparse"); + if (iterator != o.end()) { + // here this accessor has a "sparse" subobject + return ParseSparseAccessor(accessor, err, *iterator); + } + + return true; + } + +#ifdef TINYGLTF_ENABLE_DRACO + + static void DecodeIndexBuffer(draco::Mesh *mesh, size_t componentSize, + std::vector &outBuffer) { + if (componentSize == 4) { + assert(sizeof(mesh->face(draco::FaceIndex(0))[0]) == componentSize); + memcpy(outBuffer.data(), &mesh->face(draco::FaceIndex(0))[0], + outBuffer.size()); + } + else { + size_t faceStride = componentSize * 3; + for (draco::FaceIndex f(0); f < mesh->num_faces(); ++f) { + const draco::Mesh::Face &face = mesh->face(f); + if (componentSize == 2) { + uint16_t indices[3] = { (uint16_t)face[0].value(), + (uint16_t)face[1].value(), + (uint16_t)face[2].value() }; + memcpy(outBuffer.data() + f.value() * faceStride, &indices[0], + faceStride); + } + else { + uint8_t indices[3] = { (uint8_t)face[0].value(), + (uint8_t)face[1].value(), + (uint8_t)face[2].value() }; + memcpy(outBuffer.data() + f.value() * faceStride, &indices[0], + faceStride); + } + } + } + } + + template + static bool GetAttributeForAllPoints(draco::Mesh *mesh, + const draco::PointAttribute *pAttribute, + std::vector &outBuffer) { + size_t byteOffset = 0; + T values[4] = { 0, 0, 0, 0 }; + for (draco::PointIndex i(0); i < mesh->num_points(); ++i) { + const draco::AttributeValueIndex val_index = pAttribute->mapped_index(i); + if (!pAttribute->ConvertValue(val_index, pAttribute->num_components(), + values)) + return false; + + memcpy(outBuffer.data() + byteOffset, &values[0], + sizeof(T) * pAttribute->num_components()); + byteOffset += sizeof(T) * pAttribute->num_components(); + } + + return true; + } + + static bool GetAttributeForAllPoints(uint32_t componentType, draco::Mesh *mesh, + const draco::PointAttribute *pAttribute, + std::vector &outBuffer) { + bool decodeResult = false; + switch (componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_BYTE: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_SHORT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_INT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_FLOAT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_DOUBLE: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + default: + return false; + } + + return decodeResult; + } + + static bool ParseDracoExtension(Primitive *primitive, Model *model, + std::string *err, + const Value &dracoExtensionValue) { + auto bufferViewValue = dracoExtensionValue.Get("bufferView"); + if (!bufferViewValue.IsInt()) return false; + auto attributesValue = dracoExtensionValue.Get("attributes"); + if (!attributesValue.IsObject()) return false; + + auto attributesObject = attributesValue.Get(); + int bufferView = bufferViewValue.Get(); + + BufferView &view = model->bufferViews[bufferView]; + Buffer &buffer = model->buffers[view.buffer]; + // BufferView has already been decoded + if (view.dracoDecoded) return true; + view.dracoDecoded = true; + + const char *bufferViewData = + reinterpret_cast(buffer.data.data() + view.byteOffset); + size_t bufferViewSize = view.byteLength; + + // decode draco + draco::DecoderBuffer decoderBuffer; + decoderBuffer.Init(bufferViewData, bufferViewSize); + draco::Decoder decoder; + auto decodeResult = decoder.DecodeMeshFromBuffer(&decoderBuffer); + if (!decodeResult.ok()) { + return false; + } + const std::unique_ptr &mesh = decodeResult.value(); + + // create new bufferView for indices + if (primitive->indices >= 0) { + int32_t componentSize = GetComponentSizeInBytes( + model->accessors[primitive->indices].componentType); + Buffer decodedIndexBuffer; + decodedIndexBuffer.data.resize(mesh->num_faces() * 3 * componentSize); + + DecodeIndexBuffer(mesh.get(), componentSize, decodedIndexBuffer.data); + + model->buffers.emplace_back(std::move(decodedIndexBuffer)); + + BufferView decodedIndexBufferView; + decodedIndexBufferView.buffer = int(model->buffers.size() - 1); + decodedIndexBufferView.byteLength = + int(mesh->num_faces() * 3 * componentSize); + decodedIndexBufferView.byteOffset = 0; + decodedIndexBufferView.byteStride = 0; + decodedIndexBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER; + model->bufferViews.emplace_back(std::move(decodedIndexBufferView)); + + model->accessors[primitive->indices].bufferView = + int(model->bufferViews.size() - 1); + model->accessors[primitive->indices].count = int(mesh->num_faces() * 3); + } + + for (const auto &attribute : attributesObject) { + if (!attribute.second.IsInt()) return false; + auto primitiveAttribute = primitive->attributes.find(attribute.first); + if (primitiveAttribute == primitive->attributes.end()) return false; + + int dracoAttributeIndex = attribute.second.Get(); + const auto pAttribute = mesh->GetAttributeByUniqueId(dracoAttributeIndex); + const auto pBuffer = pAttribute->buffer(); + const auto componentType = + model->accessors[primitiveAttribute->second].componentType; + + // Create a new buffer for this decoded buffer + Buffer decodedBuffer; + size_t bufferSize = mesh->num_points() * pAttribute->num_components() * + GetComponentSizeInBytes(componentType); + decodedBuffer.data.resize(bufferSize); + + if (!GetAttributeForAllPoints(componentType, mesh.get(), pAttribute, + decodedBuffer.data)) + return false; + + model->buffers.emplace_back(std::move(decodedBuffer)); + + BufferView decodedBufferView; + decodedBufferView.buffer = int(model->buffers.size() - 1); + decodedBufferView.byteLength = bufferSize; + decodedBufferView.byteOffset = pAttribute->byte_offset(); + decodedBufferView.byteStride = pAttribute->byte_stride(); + decodedBufferView.target = primitive->indices >= 0 + ? TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER + : TINYGLTF_TARGET_ARRAY_BUFFER; + model->bufferViews.emplace_back(std::move(decodedBufferView)); + + model->accessors[primitiveAttribute->second].bufferView = + int(model->bufferViews.size() - 1); + model->accessors[primitiveAttribute->second].count = + int(mesh->num_points()); + } + + return true; + } +#endif + + static bool ParsePrimitive(Primitive *primitive, Model *model, std::string *err, + const json &o) { + double material = -1.0; + ParseNumberProperty(&material, err, o, "material", false); + primitive->material = static_cast(material); + + double mode = static_cast(TINYGLTF_MODE_TRIANGLES); + ParseNumberProperty(&mode, err, o, "mode", false); + + int primMode = static_cast(mode); + primitive->mode = primMode; // Why only triangled were supported ? + + double indices = -1.0; + ParseNumberProperty(&indices, err, o, "indices", false); + primitive->indices = static_cast(indices); + if (!ParseStringIntProperty(&primitive->attributes, err, o, "attributes", + true, "Primitive")) { + return false; + } + + // Look for morph targets + json::const_iterator targetsObject = o.find("targets"); + if ((targetsObject != o.end()) && targetsObject.value().is_array()) { + for (json::const_iterator i = targetsObject.value().begin(); + i != targetsObject.value().end(); i++) { + std::map targetAttribues; + + const json &dict = i.value(); + json::const_iterator dictIt(dict.begin()); + json::const_iterator dictItEnd(dict.end()); + + for (; dictIt != dictItEnd; ++dictIt) { + targetAttribues[dictIt.key()] = static_cast(dictIt.value()); + } + primitive->targets.push_back(targetAttribues); + } + } + + ParseExtrasProperty(&(primitive->extras), o); + + ParseExtensionsProperty(&primitive->extensions, err, o); + +#ifdef TINYGLTF_ENABLE_DRACO + auto dracoExtension = + primitive->extensions.find("KHR_draco_mesh_compression"); + if (dracoExtension != primitive->extensions.end()) { + ParseDracoExtension(primitive, model, err, dracoExtension->second); + } +#else + (void)model; +#endif + + return true; + } + + static bool ParseMesh(Mesh *mesh, Model *model, std::string *err, + const json &o) { + ParseStringProperty(&mesh->name, err, o, "name", false); + + mesh->primitives.clear(); + json::const_iterator primObject = o.find("primitives"); + if ((primObject != o.end()) && primObject.value().is_array()) { + for (json::const_iterator i = primObject.value().begin(); + i != primObject.value().end(); i++) { + Primitive primitive; + if (ParsePrimitive(&primitive, model, err, i.value())) { + // Only add the primitive if the parsing succeeds. + mesh->primitives.push_back(primitive); + } + } + } + + // Look for morph targets + json::const_iterator targetsObject = o.find("targets"); + if ((targetsObject != o.end()) && targetsObject.value().is_array()) { + for (json::const_iterator i = targetsObject.value().begin(); + i != targetsObject.value().end(); i++) { + std::map targetAttribues; + + const json &dict = i.value(); + json::const_iterator dictIt(dict.begin()); + json::const_iterator dictItEnd(dict.end()); + + for (; dictIt != dictItEnd; ++dictIt) { + targetAttribues[dictIt.key()] = static_cast(dictIt.value()); + } + mesh->targets.push_back(targetAttribues); + } + } + + // Should probably check if has targets and if dimensions fit + ParseNumberArrayProperty(&mesh->weights, err, o, "weights", false); + + ParseExtensionsProperty(&mesh->extensions, err, o); + ParseExtrasProperty(&(mesh->extras), o); + + return true; + } + + static bool ParseLight(Light *light, std::string *err, const json &o) { + ParseStringProperty(&light->name, err, o, "name", false); + ParseNumberArrayProperty(&light->color, err, o, "color", false); + ParseStringProperty(&light->type, err, o, "type", false); + return true; + } + + static bool ParseNode(Node *node, std::string *err, const json &o) { + ParseStringProperty(&node->name, err, o, "name", false); + + double skin = -1.0; + ParseNumberProperty(&skin, err, o, "skin", false); + node->skin = static_cast(skin); + + // Matrix and T/R/S are exclusive + if (!ParseNumberArrayProperty(&node->matrix, err, o, "matrix", false)) { + ParseNumberArrayProperty(&node->rotation, err, o, "rotation", false); + ParseNumberArrayProperty(&node->scale, err, o, "scale", false); + ParseNumberArrayProperty(&node->translation, err, o, "translation", false); + } + + double camera = -1.0; + ParseNumberProperty(&camera, err, o, "camera", false); + node->camera = static_cast(camera); + + double mesh = -1.0; + ParseNumberProperty(&mesh, err, o, "mesh", false); + node->mesh = int(mesh); + + node->children.clear(); + json::const_iterator childrenObject = o.find("children"); + if ((childrenObject != o.end()) && childrenObject.value().is_array()) { + for (json::const_iterator i = childrenObject.value().begin(); + i != childrenObject.value().end(); i++) { + if (!i.value().is_number()) { + if (err) { + (*err) += "Invalid `children` array.\n"; + } + return false; + } + const int &childrenNode = static_cast(i.value()); + node->children.push_back(childrenNode); + } + } + + ParseExtensionsProperty(&node->extensions, err, o); + ParseExtrasProperty(&(node->extras), o); + + return true; + } + + static bool ParseMaterial(Material *material, std::string *err, const json &o) { + material->values.clear(); + material->extensions.clear(); + material->additionalValues.clear(); + + json::const_iterator it(o.begin()); + json::const_iterator itEnd(o.end()); + + for (; it != itEnd; it++) { + if (it.key() == "pbrMetallicRoughness") { + if (it.value().is_object()) { + const json &values_object = it.value(); + + json::const_iterator itVal(values_object.begin()); + json::const_iterator itValEnd(values_object.end()); + + for (; itVal != itValEnd; itVal++) { + Parameter param; + if (ParseParameterProperty(¶m, err, values_object, itVal.key(), + false)) { + material->values[itVal.key()] = param; + } + } + } + } + else if (it.key() == "extensions" || it.key() == "extras") { + // done later, skip, otherwise poorly parsed contents will be saved in the + // parametermap and serialized again later + } + else { + Parameter param; + if (ParseParameterProperty(¶m, err, o, it.key(), false)) { + material->additionalValues[it.key()] = param; + } + } + } + + ParseExtensionsProperty(&material->extensions, err, o); + ParseExtrasProperty(&(material->extras), o); + + return true; + } + + static bool ParseAnimationChannel(AnimationChannel *channel, std::string *err, + const json &o) { + double samplerIndex = -1.0; + double targetIndex = -1.0; + if (!ParseNumberProperty(&samplerIndex, err, o, "sampler", true, + "AnimationChannel")) { + if (err) { + (*err) += "`sampler` field is missing in animation channels\n"; + } + return false; + } + + json::const_iterator targetIt = o.find("target"); + if ((targetIt != o.end()) && targetIt.value().is_object()) { + const json &target_object = targetIt.value(); + + if (!ParseNumberProperty(&targetIndex, err, target_object, "node", true)) { + if (err) { + (*err) += "`node` 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; + } + } + + channel->sampler = static_cast(samplerIndex); + channel->target_node = static_cast(targetIndex); + + ParseExtrasProperty(&(channel->extras), o); + + return true; + } + + static bool ParseAnimation(Animation *animation, std::string *err, + const json &o) { + { + json::const_iterator channelsIt = o.find("channels"); + if ((channelsIt != o.end()) && channelsIt.value().is_array()) { + for (json::const_iterator i = channelsIt.value().begin(); + i != channelsIt.value().end(); i++) { + AnimationChannel channel; + if (ParseAnimationChannel(&channel, err, i.value())) { + // Only add the channel if the parsing succeeds. + animation->channels.push_back(channel); + } + } + } + } + + { + json::const_iterator samplerIt = o.find("samplers"); + if ((samplerIt != o.end()) && samplerIt.value().is_array()) { + const json &sampler_array = samplerIt.value(); + + json::const_iterator it = sampler_array.begin(); + json::const_iterator itEnd = sampler_array.end(); + + for (; it != itEnd; it++) { + const json &s = it->get(); + + AnimationSampler sampler; + double inputIndex = -1.0; + double outputIndex = -1.0; + if (!ParseNumberProperty(&inputIndex, err, s, "input", true)) { + if (err) { + (*err) += "`input` field is missing in animation.sampler\n"; + } + return false; + } + ParseStringProperty(&sampler.interpolation, err, s, "interpolation", + false); + if (!ParseNumberProperty(&outputIndex, err, s, "output", true)) { + if (err) { + (*err) += "`output` field is missing in animation.sampler\n"; + } + return false; + } + sampler.input = static_cast(inputIndex); + sampler.output = static_cast(outputIndex); + ParseExtrasProperty(&(sampler.extras), s); + animation->samplers.push_back(sampler); + } + } + } + + ParseStringProperty(&animation->name, err, o, "name", false); + + ParseExtrasProperty(&(animation->extras), o); + + return true; + } + + static bool ParseSampler(Sampler *sampler, std::string *err, const json &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_REPEAT); + double wrapT = static_cast(TINYGLTF_TEXTURE_WRAP_REPEAT); + 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; + } + + static bool ParseSkin(Skin *skin, std::string *err, const json &o) { + ParseStringProperty(&skin->name, err, o, "name", false, "Skin"); + + std::vector joints; + if (!ParseNumberArrayProperty(&joints, err, o, "joints", false, "Skin")) { + return false; + } + + double skeleton = -1.0; + ParseNumberProperty(&skeleton, err, o, "skeleton", false, "Skin"); + skin->skeleton = static_cast(skeleton); + + skin->joints.resize(joints.size()); + for (size_t i = 0; i < joints.size(); i++) { + skin->joints[i] = static_cast(joints[i]); + } + + double invBind = -1.0; + ParseNumberProperty(&invBind, err, o, "inverseBindMatrices", true, "Skin"); + skin->inverseBindMatrices = static_cast(invBind); + + return true; + } + + static bool ParsePerspectiveCamera(PerspectiveCamera *camera, std::string *err, + const json &o) { + double yfov = 0.0; + if (!ParseNumberProperty(&yfov, err, o, "yfov", true, "OrthographicCamera")) { + return false; + } + + double znear = 0.0; + if (!ParseNumberProperty(&znear, err, o, "znear", true, + "PerspectiveCamera")) { + return false; + } + + double aspectRatio = 0.0; // = invalid + ParseNumberProperty(&aspectRatio, err, o, "aspectRatio", false, + "PerspectiveCamera"); + + double zfar = 0.0; // = invalid + ParseNumberProperty(&zfar, err, o, "zfar", false, "PerspectiveCamera"); + + camera->aspectRatio = aspectRatio; + camera->zfar = zfar; + camera->yfov = yfov; + camera->znear = znear; + + ParseExtensionsProperty(&camera->extensions, err, o); + ParseExtrasProperty(&(camera->extras), o); + + // TODO(syoyo): Validate parameter values. + + return true; + } + + static bool ParseOrthographicCamera(OrthographicCamera *camera, + std::string *err, const json &o) { + double xmag = 0.0; + if (!ParseNumberProperty(&xmag, err, o, "xmag", true, "OrthographicCamera")) { + return false; + } + + double ymag = 0.0; + if (!ParseNumberProperty(&ymag, err, o, "ymag", true, "OrthographicCamera")) { + return false; + } + + double zfar = 0.0; + if (!ParseNumberProperty(&zfar, err, o, "zfar", true, "OrthographicCamera")) { + return false; + } + + double znear = 0.0; + if (!ParseNumberProperty(&znear, err, o, "znear", true, + "OrthographicCamera")) { + return false; + } + + ParseExtensionsProperty(&camera->extensions, err, o); + ParseExtrasProperty(&(camera->extras), o); + + camera->xmag = xmag; + camera->ymag = ymag; + camera->zfar = zfar; + camera->znear = znear; + + // TODO(syoyo): Validate parameter values. + + return true; + } + + static bool ParseCamera(Camera *camera, std::string *err, const json &o) { + if (!ParseStringProperty(&camera->type, err, o, "type", true, "Camera")) { + return false; + } + + if (camera->type.compare("orthographic") == 0) { + if (o.find("orthographic") == o.end()) { + if (err) { + std::stringstream ss; + ss << "Orhographic camera description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const json &v = o.find("orthographic").value(); + if (!v.is_object()) { + if (err) { + std::stringstream ss; + ss << "\"orthographic\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParseOrthographicCamera(&camera->orthographic, err, v.get())) { + return false; + } + } + else if (camera->type.compare("perspective") == 0) { + if (o.find("perspective") == o.end()) { + if (err) { + std::stringstream ss; + ss << "Perspective camera description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const json &v = o.find("perspective").value(); + if (!v.is_object()) { + if (err) { + std::stringstream ss; + ss << "\"perspective\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParsePerspectiveCamera(&camera->perspective, err, v.get())) { + return false; + } + } + else { + if (err) { + std::stringstream ss; + ss << "Invalid camera type: \"" << camera->type + << "\". Must be \"perspective\" or \"orthographic\"" << std::endl; + (*err) += ss.str(); + } + return false; + } + + ParseStringProperty(&camera->name, err, o, "name", false); + + ParseExtensionsProperty(&camera->extensions, err, o); + ParseExtrasProperty(&(camera->extras), o); + + return true; + } + + bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, + const char *str, unsigned int length, + const std::string &base_dir, + unsigned int check_sections) { + if (length < 4) { + if (err) { + (*err) = "JSON string too short.\n"; + } + return false; + } + + json v; #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \ defined(_CPPUNWIND)) && \ not defined(TINYGLTF_NOEXCEPTION) - try { - v = json::parse(str, str + length); + try { + v = json::parse(str, str + length); - } catch (const std::exception &e) { - if (err) { - (*err) = e.what(); - } - return false; - } + } + catch (const std::exception &e) { + if (err) { + (*err) = e.what(); + } + return false; + } #else - { - v = json::parse(str, str + length, nullptr, /* exception */ false); + { + v = json::parse(str, str + length, nullptr, /* exception */ false); - if (!v.is_object()) { - // Assume parsing was failed. - if (err) { - (*err) = "Failed to parse JSON object\n"; - } - return false; - } - } + if (!v.is_object()) { + // Assume parsing was failed. + if (err) { + (*err) = "Failed to parse JSON object\n"; + } + return false; + } + } #endif - if (!v.is_object()) { - // root is not an object. - if (err) { - (*err) = "Root element is not a JSON object\n"; - } - return false; - } - - // scene is not mandatory. - // FIXME Maybe a better way to handle it than removing the code - - { - json::const_iterator it = v.find("scenes"); - if ((it != v.end()) && it.value().is_array()) { - // OK - } else if (check_sections & REQUIRE_SCENES) { - if (err) { - (*err) += "\"scenes\" object not found in .gltf or not an array type\n"; - } - return false; - } - } - - { - json::const_iterator it = v.find("nodes"); - if ((it != v.end()) && it.value().is_array()) { - // OK - } else if (check_sections & REQUIRE_NODES) { - if (err) { - (*err) += "\"nodes\" object not found in .gltf\n"; - } - return false; - } - } - - { - json::const_iterator it = v.find("accessors"); - if ((it != v.end()) && it.value().is_array()) { - // OK - } else if (check_sections & REQUIRE_ACCESSORS) { - if (err) { - (*err) += "\"accessors\" object not found in .gltf\n"; - } - return false; - } - } - - { - json::const_iterator it = v.find("buffers"); - if ((it != v.end()) && it.value().is_array()) { - // OK - } else if (check_sections & REQUIRE_BUFFERS) { - if (err) { - (*err) += "\"buffers\" object not found in .gltf\n"; - } - return false; - } - } - - { - json::const_iterator it = v.find("bufferViews"); - if ((it != v.end()) && it.value().is_array()) { - // OK - } else if (check_sections & REQUIRE_BUFFER_VIEWS) { - if (err) { - (*err) += "\"bufferViews\" object not found in .gltf\n"; - } - return false; - } - } - - model->buffers.clear(); - model->bufferViews.clear(); - model->accessors.clear(); - model->meshes.clear(); - model->cameras.clear(); - model->nodes.clear(); - model->extensionsUsed.clear(); - model->extensionsRequired.clear(); - model->extensions.clear(); - model->defaultScene = -1; - - // 1. Parse Asset - { - json::const_iterator it = v.find("asset"); - if ((it != v.end()) && it.value().is_object()) { - const json &root = it.value(); - - ParseAsset(&model->asset, err, root); - } - } - - // 2. Parse extensionUsed - { - json::const_iterator it = v.find("extensionsUsed"); - if ((it != v.end()) && it.value().is_array()) { - const json &root = it.value(); - for (unsigned int i = 0; i < root.size(); ++i) { - model->extensionsUsed.push_back(root[i].get()); - } - } - } - - { - json::const_iterator it = v.find("extensionsRequired"); - if ((it != v.end()) && it.value().is_array()) { - const json &root = it.value(); - for (unsigned int i = 0; i < root.size(); ++i) { - model->extensionsRequired.push_back(root[i].get()); - } - } - } - - // 3. Parse Buffer - { - json::const_iterator rootIt = v.find("buffers"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`buffers' does not contain an JSON object."; - } - return false; - } - Buffer buffer; - if (!ParseBuffer(&buffer, err, it->get(), &fs, base_dir, - is_binary_, bin_data_, bin_size_)) { - return false; - } - - model->buffers.push_back(buffer); - } - } - } - - // 4. Parse BufferView - { - json::const_iterator rootIt = v.find("bufferViews"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`bufferViews' does not contain an JSON object."; - } - return false; - } - BufferView bufferView; - if (!ParseBufferView(&bufferView, err, it->get())) { - return false; - } - - model->bufferViews.push_back(bufferView); - } - } - } - - // 5. Parse Accessor - { - json::const_iterator rootIt = v.find("accessors"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`accessors' does not contain an JSON object."; - } - return false; - } - Accessor accessor; - if (!ParseAccessor(&accessor, err, it->get())) { - return false; - } - - model->accessors.push_back(accessor); - } - } - } - - // 6. Parse Mesh - { - json::const_iterator rootIt = v.find("meshes"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`meshes' does not contain an JSON object."; - } - return false; - } - Mesh mesh; - if (!ParseMesh(&mesh, err, it->get())) { - return false; - } - - model->meshes.push_back(mesh); - } - } - } - - // 7. Parse Node - { - json::const_iterator rootIt = v.find("nodes"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`nodes' does not contain an JSON object."; - } - return false; - } - Node node; - if (!ParseNode(&node, err, it->get())) { - return false; - } - - model->nodes.push_back(node); - } - } - } - - // 8. Parse scenes. - { - json::const_iterator rootIt = v.find("scenes"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!(it.value().is_object())) { - if (err) { - (*err) += "`scenes' does not contain an JSON object."; - } - return false; - } - const json &o = it->get(); - std::vector nodes; - if (!ParseNumberArrayProperty(&nodes, err, o, "nodes", false)) { - return false; - } - - Scene scene; - ParseStringProperty(&scene.name, err, o, "name", false); - std::vector nodesIds; - for (size_t i = 0; i < nodes.size(); i++) { - nodesIds.push_back(static_cast(nodes[i])); - } - scene.nodes = nodesIds; - - ParseExtensionsProperty(&scene.extensions, err, o); - ParseExtrasProperty(&scene.extras, o); - - model->scenes.push_back(scene); - } - } - } - - // 9. Parse default scenes. - { - json::const_iterator rootIt = v.find("scene"); - if ((rootIt != v.end()) && rootIt.value().is_number()) { - const int defaultScene = rootIt.value(); - - model->defaultScene = static_cast(defaultScene); - } - } - - // 10. Parse Material - { - json::const_iterator rootIt = v.find("materials"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`materials' does not contain an JSON object."; - } - return false; - } - json jsonMaterial = it->get(); - - Material material; - ParseStringProperty(&material.name, err, jsonMaterial, "name", false); - - if (!ParseMaterial(&material, err, jsonMaterial)) { - return false; - } - - model->materials.push_back(material); - } - } - } - - // 11. Parse Image - { - json::const_iterator rootIt = v.find("images"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`images' does not contain an JSON object."; - } - return false; - } - Image image; - if (!ParseImage(&image, err, warn, it.value(), base_dir, &fs, - &this->LoadImageData, load_image_user_data_)) { - return false; - } - - if (image.bufferView != -1) { - // Load image from the buffer view. - if (size_t(image.bufferView) >= model->bufferViews.size()) { - if (err) { - std::stringstream ss; - ss << "bufferView \"" << image.bufferView - << "\" not found in the scene." << std::endl; - (*err) += ss.str(); - } - return false; - } - - const BufferView &bufferView = - model->bufferViews[size_t(image.bufferView)]; - const Buffer &buffer = model->buffers[size_t(bufferView.buffer)]; - - if (*LoadImageData == nullptr) { - if (err) { - (*err) += "No LoadImageData callback specified.\n"; - } - return false; - } - bool ret = LoadImageData(&image, err, warn, image.width, image.height, - &buffer.data[bufferView.byteOffset], - static_cast(bufferView.byteLength), - load_image_user_data_); - if (!ret) { - return false; - } - } - - model->images.push_back(image); - } - } - } - - // 12. Parse Texture - { - json::const_iterator rootIt = v.find("textures"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`textures' does not contain an JSON object."; - } - return false; - } - Texture texture; - if (!ParseTexture(&texture, err, it->get(), base_dir)) { - return false; - } - - model->textures.push_back(texture); - } - } - } - - // 13. Parse Animation - { - json::const_iterator rootIt = v.find("animations"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`animations' does not contain an JSON object."; - } - return false; - } - Animation animation; - if (!ParseAnimation(&animation, err, it->get())) { - return false; - } - - model->animations.push_back(animation); - } - } - } - - // 14. Parse Skin - { - json::const_iterator rootIt = v.find("skins"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`skins' does not contain an JSON object."; - } - return false; - } - Skin skin; - if (!ParseSkin(&skin, err, it->get())) { - return false; - } - - model->skins.push_back(skin); - } - } - } - - // 15. Parse Sampler - { - json::const_iterator rootIt = v.find("samplers"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`samplers' does not contain an JSON object."; - } - return false; - } - Sampler sampler; - if (!ParseSampler(&sampler, err, it->get())) { - return false; - } - - model->samplers.push_back(sampler); - } - } - } - - // 16. Parse Camera - { - json::const_iterator rootIt = v.find("cameras"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`cameras' does not contain an JSON object."; - } - return false; - } - Camera camera; - if (!ParseCamera(&camera, err, it->get())) { - return false; - } - - model->cameras.push_back(camera); - } - } - } - - // 17. Parse Extensions - ParseExtensionsProperty(&model->extensions, err, v); - - // 18. Specific extension implementations - { - json::const_iterator rootIt = v.find("extensions"); - if ((rootIt != v.end()) && rootIt.value().is_object()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - // parse KHR_lights_cmn extension - if ((it.key().compare("KHR_lights_cmn") == 0) && - it.value().is_object()) { - const json &object = it.value(); - json::const_iterator itLight(object.find("lights")); - json::const_iterator itLightEnd(object.end()); - if (itLight == itLightEnd) { - continue; - } - - if (!itLight.value().is_array()) { - continue; - } - - const json &lights = itLight.value(); - json::const_iterator arrayIt(lights.begin()); - json::const_iterator arrayItEnd(lights.end()); - for (; arrayIt != arrayItEnd; ++arrayIt) { - Light light; - if (!ParseLight(&light, err, arrayIt.value())) { - return false; - } - model->lights.push_back(light); - } - } - } - } - } - - // 19. Parse Extras - ParseExtrasProperty(&model->extras, v); - - return true; -} - -bool TinyGLTF::LoadASCIIFromString(Model *model, std::string *err, - std::string *warn, const char *str, - unsigned int length, - const std::string &base_dir, - unsigned int check_sections) { - is_binary_ = false; - bin_data_ = nullptr; - bin_size_ = 0; - - return LoadFromString(model, err, warn, str, length, base_dir, - check_sections); -} - -bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err, - std::string *warn, const std::string &filename, - unsigned int check_sections) { - std::stringstream ss; - - if (fs.ReadWholeFile == nullptr) { - // Programmer error, assert() ? - ss << "Failed to read file: " << filename - << ": one or more FS callback not set" << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - std::vector data; - std::string fileerr; - bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); - if (!fileread) { - ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - size_t sz = data.size(); - if (sz == 0) { - if (err) { - (*err) = "Empty file."; - } - return false; - } - - std::string basedir = GetBaseDir(filename); - - bool ret = LoadASCIIFromString( - model, err, warn, reinterpret_cast(&data.at(0)), - static_cast(data.size()), basedir, check_sections); - - return ret; -} - -bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, - std::string *warn, - 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 model_length; // 4 bytes - unsigned int model_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(&model_length, bytes + 12, 4); - swap4(&model_length); - memcpy(&model_format, bytes + 16, 4); - swap4(&model_format); - - // In case the Bin buffer is not present, the size is exactly 20 + size of - // JSON contents, - // so use "greater than" operator. - if ((20 + model_length > size) || (model_length < 1) || - (model_format != 0x4E4F534A)) { // 0x4E4F534A = JSON format. - if (err) { - (*err) = "Invalid glTF binary."; - } - return false; - } - - // Extract JSON string. - std::string jsonString(reinterpret_cast(&bytes[20]), - model_length); - - is_binary_ = true; - bin_data_ = bytes + 20 + model_length + - 8; // 4 bytes (buffer_length) + 4 bytes(buffer_format) - bin_size_ = - length - (20 + model_length); // extract header + JSON scene data. - - bool ret = LoadFromString(model, err, warn, - reinterpret_cast(&bytes[20]), - model_length, base_dir, check_sections); - if (!ret) { - return ret; - } - - return true; -} - -bool TinyGLTF::LoadBinaryFromFile(Model *model, std::string *err, - std::string *warn, - const std::string &filename, - unsigned int check_sections) { - std::stringstream ss; - - if (fs.ReadWholeFile == nullptr) { - // Programmer error, assert() ? - ss << "Failed to read file: " << filename - << ": one or more FS callback not set" << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - std::vector data; - std::string fileerr; - bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); - if (!fileread) { - ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - std::string basedir = GetBaseDir(filename); - - bool ret = LoadBinaryFromMemory(model, err, warn, &data.at(0), - static_cast(data.size()), - basedir, check_sections); - - return ret; -} - -/////////////////////// -// GLTF Serialization -/////////////////////// - -// typedef std::pair json_object_pair; - -template -static void SerializeNumberProperty(const std::string &key, T number, - json &obj) { - // obj.insert( - // json_object_pair(key, json(static_cast(number)))); - // obj[key] = static_cast(number); - obj[key] = number; -} - -template -static void SerializeNumberArrayProperty(const std::string &key, - const std::vector &value, - json &obj) { - json o; - json vals; - - for (unsigned int i = 0; i < value.size(); ++i) { - vals.push_back(static_cast(value[i])); - } - if (!vals.is_null()) { - obj[key] = vals; - } -} - -static void SerializeStringProperty(const std::string &key, - const std::string &value, json &obj) { - obj[key] = value; -} - -static void SerializeStringArrayProperty(const std::string &key, - const std::vector &value, - json &obj) { - json o; - json vals; - - for (unsigned int i = 0; i < value.size(); ++i) { - vals.push_back(value[i]); - } - - obj[key] = vals; -} - -static bool ValueToJson(const Value &value, json *ret) { - json obj; - switch (value.Type()) { - case NUMBER_TYPE: - obj = json(value.Get()); - break; - case INT_TYPE: - obj = json(value.Get()); - break; - case BOOL_TYPE: - obj = json(value.Get()); - break; - case STRING_TYPE: - obj = json(value.Get()); - break; - case ARRAY_TYPE: { - for (unsigned int i = 0; i < value.ArrayLen(); ++i) { - Value elementValue = value.Get(int(i)); - json elementJson; - if (ValueToJson(value.Get(int(i)), &elementJson)) - obj.push_back(elementJson); - } - break; - } - case BINARY_TYPE: - // TODO - // obj = json(value.Get>()); - return false; - break; - case OBJECT_TYPE: { - Value::Object objMap = value.Get(); - for (auto &it : objMap) { - json elementJson; - if (ValueToJson(it.second, &elementJson)) obj[it.first] = elementJson; - } - break; - } - case NULL_TYPE: - default: - return false; - } - if (ret) *ret = obj; - return true; -} - -static void SerializeValue(const std::string &key, const Value &value, - json &obj) { - json ret; - if (ValueToJson(value, &ret)) obj[key] = ret; -} - -static void SerializeGltfBufferData(const std::vector &data, - json &o) { - std::string header = "data:application/octet-stream;base64,"; - std::string encodedData = - base64_encode(&data[0], static_cast(data.size())); - SerializeStringProperty("uri", header + encodedData, o); -} - -static void SerializeGltfBufferData(const std::vector &data, - const std::string &binFilename) { - std::ofstream output(binFilename.c_str(), std::ofstream::binary); - output.write(reinterpret_cast(&data[0]), - std::streamsize(data.size())); - output.close(); -} - -static void SerializeParameterMap(ParameterMap ¶m, json &o) { - for (ParameterMap::iterator paramIt = param.begin(); paramIt != param.end(); - ++paramIt) { - if (paramIt->second.number_array.size()) { - SerializeNumberArrayProperty(paramIt->first, - paramIt->second.number_array, o); - } else if (paramIt->second.json_double_value.size()) { - json json_double_value; - for (std::map::iterator it = - paramIt->second.json_double_value.begin(); - it != paramIt->second.json_double_value.end(); ++it) { - if (it->first == "index") { - json_double_value[it->first] = paramIt->second.TextureIndex(); - } else { - json_double_value[it->first] = it->second; - } - } - - o[paramIt->first] = json_double_value; - } else if (!paramIt->second.string_value.empty()) { - SerializeStringProperty(paramIt->first, paramIt->second.string_value, o); - } else if (paramIt->second.has_number_value) { - o[paramIt->first] = paramIt->second.number_value; - } else { - o[paramIt->first] = paramIt->second.bool_value; - } - } -} - -static void SerializeExtensionMap(ExtensionMap &extensions, json &o) { - if (!extensions.size()) return; - - json extMap; - for (ExtensionMap::iterator extIt = extensions.begin(); - extIt != extensions.end(); ++extIt) { - json extension_values; - SerializeValue(extIt->first, extIt->second, extMap); - } - o["extensions"] = extMap; -} - -static void SerializeGltfAccessor(Accessor &accessor, json &o) { - SerializeNumberProperty("bufferView", accessor.bufferView, o); - - if (accessor.byteOffset != 0.0) - SerializeNumberProperty("byteOffset", int(accessor.byteOffset), o); - - SerializeNumberProperty("componentType", accessor.componentType, o); - SerializeNumberProperty("count", accessor.count, o); - SerializeNumberArrayProperty("min", accessor.minValues, o); - SerializeNumberArrayProperty("max", accessor.maxValues, o); - std::string type; - switch (accessor.type) { - case TINYGLTF_TYPE_SCALAR: - type = "SCALAR"; - break; - case TINYGLTF_TYPE_VEC2: - type = "VEC2"; - break; - case TINYGLTF_TYPE_VEC3: - type = "VEC3"; - break; - case TINYGLTF_TYPE_VEC4: - type = "VEC4"; - break; - case TINYGLTF_TYPE_MAT2: - type = "MAT2"; - break; - case TINYGLTF_TYPE_MAT3: - type = "MAT3"; - break; - case TINYGLTF_TYPE_MAT4: - type = "MAT4"; - break; - } - - SerializeStringProperty("type", type, o); - - if (accessor.extras.Type() != NULL_TYPE) { - SerializeValue("extras", accessor.extras, o); - } -} - -static void SerializeGltfAnimationChannel(AnimationChannel &channel, json &o) { - SerializeNumberProperty("sampler", channel.sampler, o); - json target; - SerializeNumberProperty("node", channel.target_node, target); - SerializeStringProperty("path", channel.target_path, target); - - o["target"] = target; - - if (channel.extras.Type() != NULL_TYPE) { - SerializeValue("extras", channel.extras, o); - } -} - -static void SerializeGltfAnimationSampler(AnimationSampler &sampler, json &o) { - SerializeNumberProperty("input", sampler.input, o); - SerializeNumberProperty("output", sampler.output, o); - SerializeStringProperty("interpolation", sampler.interpolation, o); - - if (sampler.extras.Type() != NULL_TYPE) { - SerializeValue("extras", sampler.extras, o); - } -} - -static void SerializeGltfAnimation(Animation &animation, json &o) { - SerializeStringProperty("name", animation.name, o); - json channels; - for (unsigned int i = 0; i < animation.channels.size(); ++i) { - json channel; - AnimationChannel gltfChannel = animation.channels[i]; - SerializeGltfAnimationChannel(gltfChannel, channel); - channels.push_back(channel); - } - o["channels"] = channels; - - json samplers; - for (unsigned int i = 0; i < animation.samplers.size(); ++i) { - json sampler; - AnimationSampler gltfSampler = animation.samplers[i]; - SerializeGltfAnimationSampler(gltfSampler, sampler); - samplers.push_back(sampler); - } - - o["samplers"] = samplers; - - if (animation.extras.Type() != NULL_TYPE) { - SerializeValue("extras", animation.extras, o); - } -} - -static void SerializeGltfAsset(Asset &asset, json &o) { - if (!asset.generator.empty()) { - SerializeStringProperty("generator", asset.generator, o); - } - - if (!asset.version.empty()) { - SerializeStringProperty("version", asset.version, o); - } - - if (asset.extras.Keys().size()) { - SerializeValue("extras", asset.extras, o); - } - - SerializeExtensionMap(asset.extensions, o); -} - -static void SerializeGltfBuffer(Buffer &buffer, json &o) { - SerializeNumberProperty("byteLength", buffer.data.size(), o); - SerializeGltfBufferData(buffer.data, o); - - if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); - - if (buffer.extras.Type() != NULL_TYPE) { - SerializeValue("extras", buffer.extras, o); - } -} - -static void SerializeGltfBuffer(Buffer &buffer, json &o, - const std::string &binFilename, - const std::string &binBaseFilename) { - SerializeGltfBufferData(buffer.data, binFilename); - SerializeNumberProperty("byteLength", buffer.data.size(), o); - SerializeStringProperty("uri", binBaseFilename, o); - - if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); - - if (buffer.extras.Type() != NULL_TYPE) { - SerializeValue("extras", buffer.extras, o); - } -} - -static void SerializeGltfBufferView(BufferView &bufferView, json &o) { - SerializeNumberProperty("buffer", bufferView.buffer, o); - SerializeNumberProperty("byteLength", bufferView.byteLength, o); - - // byteStride is optional, minimum allowed is 4 - if (bufferView.byteStride >= 4) { - SerializeNumberProperty("byteStride", bufferView.byteStride, o); - } - // byteOffset is optional, default is 0 - if (bufferView.byteOffset > 0) { - SerializeNumberProperty("byteOffset", bufferView.byteOffset, o); - } - // Target is optional, check if it contains a valid value - if (bufferView.target == TINYGLTF_TARGET_ARRAY_BUFFER || - bufferView.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) { - SerializeNumberProperty("target", bufferView.target, o); - } - if (bufferView.name.size()) { - SerializeStringProperty("name", bufferView.name, o); - } - - if (bufferView.extras.Type() != NULL_TYPE) { - SerializeValue("extras", bufferView.extras, o); - } -} - -static void SerializeGltfImage(Image &image, json &o) { - SerializeStringProperty("uri", image.uri, o); - - if (image.name.size()) { - SerializeStringProperty("name", image.name, o); - } - - if (image.extras.Type() != NULL_TYPE) { - SerializeValue("extras", image.extras, o); - } -} - -static void SerializeGltfMaterial(Material &material, json &o) { - if (material.extras.Size()) SerializeValue("extras", material.extras, o); - SerializeExtensionMap(material.extensions, o); - - if (material.values.size()) { - json pbrMetallicRoughness; - SerializeParameterMap(material.values, pbrMetallicRoughness); - o["pbrMetallicRoughness"] = pbrMetallicRoughness; - } - - SerializeParameterMap(material.additionalValues, o); - - if (material.name.size()) { - SerializeStringProperty("name", material.name, o); - } - - if (material.extras.Type() != NULL_TYPE) { - SerializeValue("extras", material.extras, o); - } -} - -static void SerializeGltfMesh(Mesh &mesh, json &o) { - json primitives; - for (unsigned int i = 0; i < mesh.primitives.size(); ++i) { - json primitive; - json attributes; - Primitive gltfPrimitive = mesh.primitives[i]; - for (std::map::iterator attrIt = - gltfPrimitive.attributes.begin(); - attrIt != gltfPrimitive.attributes.end(); ++attrIt) { - SerializeNumberProperty(attrIt->first, attrIt->second, attributes); - } - - primitive["attributes"] = attributes; - - // Indicies is optional - if (gltfPrimitive.indices > -1) { - SerializeNumberProperty("indices", gltfPrimitive.indices, primitive); - } - // Material is optional - if (gltfPrimitive.material > -1) { - SerializeNumberProperty("material", gltfPrimitive.material, - primitive); - } - SerializeNumberProperty("mode", gltfPrimitive.mode, primitive); - - // Morph targets - if (gltfPrimitive.targets.size()) { - json targets; - for (unsigned int k = 0; k < gltfPrimitive.targets.size(); ++k) { - json targetAttributes; - std::map targetData = gltfPrimitive.targets[k]; - for (std::map::iterator attrIt = targetData.begin(); - attrIt != targetData.end(); ++attrIt) { - SerializeNumberProperty(attrIt->first, attrIt->second, - targetAttributes); - } - - targets.push_back(targetAttributes); - } - primitive["targets"] = targets; - } - - if (gltfPrimitive.extras.Type() != NULL_TYPE) { - SerializeValue("extras", gltfPrimitive.extras, primitive); - } - - primitives.push_back(primitive); - } - - o["primitives"] = primitives; - if (mesh.weights.size()) { - SerializeNumberArrayProperty("weights", mesh.weights, o); - } - - if (mesh.name.size()) { - SerializeStringProperty("name", mesh.name, o); - } - - if (mesh.extras.Type() != NULL_TYPE) { - SerializeValue("extras", mesh.extras, o); - } -} - -static void SerializeGltfLight(Light &light, json &o) { - SerializeStringProperty("name", light.name, o); - SerializeNumberArrayProperty("color", light.color, o); - SerializeStringProperty("type", light.type, o); -} - -static void SerializeGltfNode(Node &node, json &o) { - if (node.translation.size() > 0) { - SerializeNumberArrayProperty("translation", node.translation, o); - } - if (node.rotation.size() > 0) { - SerializeNumberArrayProperty("rotation", node.rotation, o); - } - if (node.scale.size() > 0) { - SerializeNumberArrayProperty("scale", node.scale, o); - } - if (node.matrix.size() > 0) { - SerializeNumberArrayProperty("matrix", node.matrix, o); - } - if (node.mesh != -1) { - SerializeNumberProperty("mesh", node.mesh, o); - } - - if (node.skin != -1) { - SerializeNumberProperty("skin", node.skin, o); - } - - if (node.camera != -1) { - SerializeNumberProperty("camera", node.camera, o); - } - - if (node.extras.Type() != NULL_TYPE) { - SerializeValue("extras", node.extras, o); - } - - SerializeExtensionMap(node.extensions, o); - SerializeStringProperty("name", node.name, o); - SerializeNumberArrayProperty("children", node.children, o); -} - -static void SerializeGltfSampler(Sampler &sampler, json &o) { - SerializeNumberProperty("magFilter", sampler.magFilter, o); - SerializeNumberProperty("minFilter", sampler.minFilter, o); - SerializeNumberProperty("wrapS", sampler.wrapS, o); - SerializeNumberProperty("wrapT", sampler.wrapT, o); - - if (sampler.extras.Type() != NULL_TYPE) { - SerializeValue("extras", sampler.extras, o); - } -} - -static void SerializeGltfOrthographicCamera(const OrthographicCamera &camera, - json &o) { - SerializeNumberProperty("zfar", camera.zfar, o); - SerializeNumberProperty("znear", camera.znear, o); - SerializeNumberProperty("xmag", camera.xmag, o); - SerializeNumberProperty("ymag", camera.ymag, o); - - if (camera.extras.Type() != NULL_TYPE) { - SerializeValue("extras", camera.extras, o); - } -} - -static void SerializeGltfPerspectiveCamera(const PerspectiveCamera &camera, - json &o) { - SerializeNumberProperty("zfar", camera.zfar, o); - SerializeNumberProperty("znear", camera.znear, o); - if (camera.aspectRatio > 0) { - SerializeNumberProperty("aspectRatio", camera.aspectRatio, o); - } - - if (camera.yfov > 0) { - SerializeNumberProperty("yfov", camera.yfov, o); - } - - if (camera.extras.Type() != NULL_TYPE) { - SerializeValue("extras", camera.extras, o); - } -} - -static void SerializeGltfCamera(const Camera &camera, json &o) { - SerializeStringProperty("type", camera.type, o); - if (!camera.name.empty()) { - SerializeStringProperty("name", camera.type, o); - } - - if (camera.type.compare("orthographic") == 0) { - json orthographic; - SerializeGltfOrthographicCamera(camera.orthographic, orthographic); - o["orthographic"] = orthographic; - } else if (camera.type.compare("perspective") == 0) { - json perspective; - SerializeGltfPerspectiveCamera(camera.perspective, perspective); - o["perspective"] = perspective; - } else { - // ??? - } -} - -static void SerializeGltfScene(Scene &scene, json &o) { - SerializeNumberArrayProperty("nodes", scene.nodes, o); - - if (scene.name.size()) { - SerializeStringProperty("name", scene.name, o); - } - if (scene.extras.Type() != NULL_TYPE) { - SerializeValue("extras", scene.extras, o); - } - SerializeExtensionMap(scene.extensions, o); -} - -static void SerializeGltfSkin(Skin &skin, json &o) { - if (skin.inverseBindMatrices != -1) - SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o); - - SerializeNumberArrayProperty("joints", skin.joints, o); - SerializeNumberProperty("skeleton", skin.skeleton, o); - if (skin.name.size()) { - SerializeStringProperty("name", skin.name, o); - } -} - -static void SerializeGltfTexture(Texture &texture, json &o) { - if (texture.sampler > -1) { - SerializeNumberProperty("sampler", texture.sampler, o); - } - SerializeNumberProperty("source", texture.source, o); - - if (texture.extras.Type() != NULL_TYPE) { - SerializeValue("extras", texture.extras, o); - } - SerializeExtensionMap(texture.extensions, o); -} - -static void WriteGltfFile(const std::string &output, - const std::string &content) { - std::ofstream gltfFile(output.c_str()); - gltfFile << content << std::endl; -} - -bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename, - bool embedImages = false, - bool embedBuffers = false - /*, bool writeBinary*/) { - json output; - - // ACCESSORS - json accessors; - for (unsigned int i = 0; i < model->accessors.size(); ++i) { - json accessor; - SerializeGltfAccessor(model->accessors[i], accessor); - accessors.push_back(accessor); - } - output["accessors"] = accessors; - - // ANIMATIONS - if (model->animations.size()) { - json animations; - for (unsigned int i = 0; i < model->animations.size(); ++i) { - if (model->animations[i].channels.size()) { - json animation; - SerializeGltfAnimation(model->animations[i], animation); - animations.push_back(animation); - } - } - output["animations"] = animations; - } - - // ASSET - json asset; - SerializeGltfAsset(model->asset, asset); - output["asset"] = asset; - - std::string binFilename = GetBaseFilename(filename); - std::string ext = ".bin"; - std::string::size_type pos = binFilename.rfind('.', binFilename.length()); - - if (pos != std::string::npos) { - binFilename = binFilename.substr(0, pos) + ext; - } else { - binFilename = binFilename + ".bin"; - } - std::string baseDir = GetBaseDir(filename); - if (baseDir.empty()) { - baseDir = "./"; - } - - std::string binSaveFilePath = JoinPath(baseDir, binFilename); - - // BUFFERS (We expect only one buffer here) - json buffers; - for (unsigned int i = 0; i < model->buffers.size(); ++i) { - json buffer; - if (embedBuffers) { - SerializeGltfBuffer(model->buffers[i], buffer); - } else { - SerializeGltfBuffer(model->buffers[i], buffer, binSaveFilePath, - binFilename); - } - buffers.push_back(buffer); - } - output["buffers"] = buffers; - - // BUFFERVIEWS - json bufferViews; - for (unsigned int i = 0; i < model->bufferViews.size(); ++i) { - json bufferView; - SerializeGltfBufferView(model->bufferViews[i], bufferView); - bufferViews.push_back(bufferView); - } - output["bufferViews"] = bufferViews; - - // Extensions used - if (model->extensionsUsed.size()) { - SerializeStringArrayProperty("extensionsUsed", model->extensionsUsed, - output); - } - - // Extensions required - if (model->extensionsRequired.size()) { - SerializeStringArrayProperty("extensionsRequired", - model->extensionsRequired, output); - } - - // IMAGES - if (model->images.size()) { - json images; - for (unsigned int i = 0; i < model->images.size(); ++i) { - json image; - - UpdateImageObject(model->images[i], baseDir, int(i), embedImages, - &this->WriteImageData, &this->write_image_user_data_); - SerializeGltfImage(model->images[i], image); - images.push_back(image); - } - output["images"] = images; - } - - // MATERIALS - if (model->materials.size()) { - json materials; - for (unsigned int i = 0; i < model->materials.size(); ++i) { - json material; - SerializeGltfMaterial(model->materials[i], material); - materials.push_back(material); - } - output["materials"] = materials; - } - - // MESHES - if (model->meshes.size()) { - json meshes; - for (unsigned int i = 0; i < model->meshes.size(); ++i) { - json mesh; - SerializeGltfMesh(model->meshes[i], mesh); - meshes.push_back(mesh); - } - output["meshes"] = meshes; - } - - // NODES - if (model->nodes.size()) { - json nodes; - for (unsigned int i = 0; i < model->nodes.size(); ++i) { - json node; - SerializeGltfNode(model->nodes[i], node); - nodes.push_back(node); - } - output["nodes"] = nodes; - } - - // SCENE - if (model->defaultScene > -1) { - SerializeNumberProperty("scene", model->defaultScene, output); - } - - // SCENES - if (model->scenes.size()) { - json scenes; - for (unsigned int i = 0; i < model->scenes.size(); ++i) { - json currentScene; - SerializeGltfScene(model->scenes[i], currentScene); - scenes.push_back(currentScene); - } - output["scenes"] = scenes; - } - - // SKINS - if (model->skins.size()) { - json skins; - for (unsigned int i = 0; i < model->skins.size(); ++i) { - json skin; - SerializeGltfSkin(model->skins[i], skin); - skins.push_back(skin); - } - output["skins"] = skins; - } - - // TEXTURES - if (model->textures.size()) { - json textures; - for (unsigned int i = 0; i < model->textures.size(); ++i) { - json texture; - SerializeGltfTexture(model->textures[i], texture); - textures.push_back(texture); - } - output["textures"] = textures; - } - - // SAMPLERS - if (model->samplers.size()) { - json samplers; - for (unsigned int i = 0; i < model->samplers.size(); ++i) { - json sampler; - SerializeGltfSampler(model->samplers[i], sampler); - samplers.push_back(sampler); - } - output["samplers"] = samplers; - } - - // CAMERAS - if (model->cameras.size()) { - json cameras; - for (unsigned int i = 0; i < model->cameras.size(); ++i) { - json camera; - SerializeGltfCamera(model->cameras[i], camera); - cameras.push_back(camera); - } - output["cameras"] = cameras; - } - - // EXTENSIONS - SerializeExtensionMap(model->extensions, output); - - // LIGHTS as KHR_lights_cmn - if (model->lights.size()) { - json lights; - for (unsigned int i = 0; i < model->lights.size(); ++i) { - json light; - SerializeGltfLight(model->lights[i], light); - lights.push_back(light); - } - json khr_lights_cmn; - khr_lights_cmn["lights"] = lights; - json ext_j; - - if (output.find("extensions") != output.end()) { - ext_j = output["extensions"]; - } - - ext_j["KHR_lights_cmn"] = khr_lights_cmn; - - output["extensions"] = ext_j; - } - - // EXTRAS - if (model->extras.Type() != NULL_TYPE) { - SerializeValue("extras", model->extras, output); - } - - WriteGltfFile(filename, output.dump()); - return true; -} + if (!v.is_object()) { + // root is not an object. + if (err) { + (*err) = "Root element is not a JSON object\n"; + } + return false; + } + + // scene is not mandatory. + // FIXME Maybe a better way to handle it than removing the code + + { + json::const_iterator it = v.find("scenes"); + if ((it != v.end()) && it.value().is_array()) { + // OK + } + else if (check_sections & REQUIRE_SCENES) { + if (err) { + (*err) += "\"scenes\" object not found in .gltf or not an array type\n"; + } + return false; + } + } + + { + json::const_iterator it = v.find("nodes"); + if ((it != v.end()) && it.value().is_array()) { + // OK + } + else if (check_sections & REQUIRE_NODES) { + if (err) { + (*err) += "\"nodes\" object not found in .gltf\n"; + } + return false; + } + } + + { + json::const_iterator it = v.find("accessors"); + if ((it != v.end()) && it.value().is_array()) { + // OK + } + else if (check_sections & REQUIRE_ACCESSORS) { + if (err) { + (*err) += "\"accessors\" object not found in .gltf\n"; + } + return false; + } + } + + { + json::const_iterator it = v.find("buffers"); + if ((it != v.end()) && it.value().is_array()) { + // OK + } + else if (check_sections & REQUIRE_BUFFERS) { + if (err) { + (*err) += "\"buffers\" object not found in .gltf\n"; + } + return false; + } + } + + { + json::const_iterator it = v.find("bufferViews"); + if ((it != v.end()) && it.value().is_array()) { + // OK + } + else if (check_sections & REQUIRE_BUFFER_VIEWS) { + if (err) { + (*err) += "\"bufferViews\" object not found in .gltf\n"; + } + return false; + } + } + + model->buffers.clear(); + model->bufferViews.clear(); + model->accessors.clear(); + model->meshes.clear(); + model->cameras.clear(); + model->nodes.clear(); + model->extensionsUsed.clear(); + model->extensionsRequired.clear(); + model->extensions.clear(); + model->defaultScene = -1; + + // 1. Parse Asset + { + json::const_iterator it = v.find("asset"); + if ((it != v.end()) && it.value().is_object()) { + const json &root = it.value(); + + ParseAsset(&model->asset, err, root); + } + } + + // 2. Parse extensionUsed + { + json::const_iterator it = v.find("extensionsUsed"); + if ((it != v.end()) && it.value().is_array()) { + const json &root = it.value(); + for (unsigned int i = 0; i < root.size(); ++i) { + model->extensionsUsed.push_back(root[i].get()); + } + } + } + + { + json::const_iterator it = v.find("extensionsRequired"); + if ((it != v.end()) && it.value().is_array()) { + const json &root = it.value(); + for (unsigned int i = 0; i < root.size(); ++i) { + model->extensionsRequired.push_back(root[i].get()); + } + } + } + + // 3. Parse Buffer + { + json::const_iterator rootIt = v.find("buffers"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`buffers' does not contain an JSON object."; + } + return false; + } + Buffer buffer; + if (!ParseBuffer(&buffer, err, it->get(), &fs, base_dir, + is_binary_, bin_data_, bin_size_)) { + return false; + } + + model->buffers.push_back(buffer); + } + } + } + + // 4. Parse BufferView + { + json::const_iterator rootIt = v.find("bufferViews"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`bufferViews' does not contain an JSON object."; + } + return false; + } + BufferView bufferView; + if (!ParseBufferView(&bufferView, err, it->get())) { + return false; + } + + model->bufferViews.push_back(bufferView); + } + } + } + + // 5. Parse Accessor + { + json::const_iterator rootIt = v.find("accessors"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`accessors' does not contain an JSON object."; + } + return false; + } + Accessor accessor; + if (!ParseAccessor(&accessor, err, it->get())) { + return false; + } + + model->accessors.push_back(accessor); + } + } + } + + // 6. Parse Mesh + { + json::const_iterator rootIt = v.find("meshes"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`meshes' does not contain an JSON object."; + } + return false; + } + Mesh mesh; + if (!ParseMesh(&mesh, model, err, it->get())) { + return false; + } + + model->meshes.push_back(mesh); + } + } + } + + // Assign missing bufferView target types + // - Look for missing Mesh indices + // - Look for missing bufferView targets + for (auto &mesh : model->meshes) { + for (auto &primitive : mesh.primitives) { + if (primitive.indices > + -1) // has indices from parsing step, must be Element Array Buffer + { + model->bufferViews[model->accessors[primitive.indices].bufferView] + .target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER; + // we could optionally check if acessors' bufferView type is Scalar, as + // it should be + } + } + } + // find any missing targets, must be an array buffer type if not fulfilled + // from previous check + for (auto &bufferView : model->bufferViews) { + if (bufferView.target == 0) // missing target type + { + bufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER; + } + } + + // 7. Parse Node + { + json::const_iterator rootIt = v.find("nodes"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`nodes' does not contain an JSON object."; + } + return false; + } + Node node; + if (!ParseNode(&node, err, it->get())) { + return false; + } + + model->nodes.push_back(node); + } + } + } + + // 8. Parse scenes. + { + json::const_iterator rootIt = v.find("scenes"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!(it.value().is_object())) { + if (err) { + (*err) += "`scenes' does not contain an JSON object."; + } + return false; + } + const json &o = it->get(); + std::vector nodes; + if (!ParseNumberArrayProperty(&nodes, err, o, "nodes", false)) { + return false; + } + + Scene scene; + ParseStringProperty(&scene.name, err, o, "name", false); + std::vector nodesIds; + for (size_t i = 0; i < nodes.size(); i++) { + nodesIds.push_back(static_cast(nodes[i])); + } + scene.nodes = nodesIds; + + ParseExtensionsProperty(&scene.extensions, err, o); + ParseExtrasProperty(&scene.extras, o); + + model->scenes.push_back(scene); + } + } + } + + // 9. Parse default scenes. + { + json::const_iterator rootIt = v.find("scene"); + if ((rootIt != v.end()) && rootIt.value().is_number()) { + const int defaultScene = rootIt.value(); + + model->defaultScene = static_cast(defaultScene); + } + } + + // 10. Parse Material + { + json::const_iterator rootIt = v.find("materials"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`materials' does not contain an JSON object."; + } + return false; + } + json jsonMaterial = it->get(); + + Material material; + ParseStringProperty(&material.name, err, jsonMaterial, "name", false); + + if (!ParseMaterial(&material, err, jsonMaterial)) { + return false; + } + + model->materials.push_back(material); + } + } + } + + // 11. Parse Image + { + json::const_iterator rootIt = v.find("images"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + int idx = 0; + for (; it != itEnd; it++, idx++) { + if (!it.value().is_object()) { + if (err) { + (*err) += + "image[" + std::to_string(idx) + "] is not a JSON object."; + } + return false; + } + Image image; + if (!ParseImage(&image, idx, err, warn, it.value(), base_dir, &fs, + &this->LoadImageData, load_image_user_data_)) { + return false; + } + + if (image.bufferView != -1) { + // Load image from the buffer view. + if (size_t(image.bufferView) >= model->bufferViews.size()) { + if (err) { + std::stringstream ss; + ss << "image[" << idx << "] bufferView \"" << image.bufferView + << "\" not found in the scene." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const BufferView &bufferView = + model->bufferViews[size_t(image.bufferView)]; + const Buffer &buffer = model->buffers[size_t(bufferView.buffer)]; + + if (*LoadImageData == nullptr) { + if (err) { + (*err) += "No LoadImageData callback specified.\n"; + } + return false; + } + bool ret = LoadImageData( + &image, idx, err, warn, image.width, image.height, + &buffer.data[bufferView.byteOffset], + static_cast(bufferView.byteLength), load_image_user_data_); + if (!ret) { + return false; + } + } + + model->images.push_back(image); + } + } + } + + // 12. Parse Texture + { + json::const_iterator rootIt = v.find("textures"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`textures' does not contain an JSON object."; + } + return false; + } + Texture texture; + if (!ParseTexture(&texture, err, it->get(), base_dir)) { + return false; + } + + model->textures.push_back(texture); + } + } + } + + // 13. Parse Animation + { + json::const_iterator rootIt = v.find("animations"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`animations' does not contain an JSON object."; + } + return false; + } + Animation animation; + if (!ParseAnimation(&animation, err, it->get())) { + return false; + } + + model->animations.push_back(animation); + } + } + } + + // 14. Parse Skin + { + json::const_iterator rootIt = v.find("skins"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`skins' does not contain an JSON object."; + } + return false; + } + Skin skin; + if (!ParseSkin(&skin, err, it->get())) { + return false; + } + + model->skins.push_back(skin); + } + } + } + + // 15. Parse Sampler + { + json::const_iterator rootIt = v.find("samplers"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`samplers' does not contain an JSON object."; + } + return false; + } + Sampler sampler; + if (!ParseSampler(&sampler, err, it->get())) { + return false; + } + + model->samplers.push_back(sampler); + } + } + } + + // 16. Parse Camera + { + json::const_iterator rootIt = v.find("cameras"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`cameras' does not contain an JSON object."; + } + return false; + } + Camera camera; + if (!ParseCamera(&camera, err, it->get())) { + return false; + } + + model->cameras.push_back(camera); + } + } + } + + // 17. Parse Extensions + ParseExtensionsProperty(&model->extensions, err, v); + + // 18. Specific extension implementations + { + json::const_iterator rootIt = v.find("extensions"); + if ((rootIt != v.end()) && rootIt.value().is_object()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + // parse KHR_lights_cmn extension + if ((it.key().compare("KHR_lights_cmn") == 0) && + it.value().is_object()) { + const json &object = it.value(); + json::const_iterator itLight(object.find("lights")); + json::const_iterator itLightEnd(object.end()); + if (itLight == itLightEnd) { + continue; + } + + if (!itLight.value().is_array()) { + continue; + } + + const json &lights = itLight.value(); + json::const_iterator arrayIt(lights.begin()); + json::const_iterator arrayItEnd(lights.end()); + for (; arrayIt != arrayItEnd; ++arrayIt) { + Light light; + if (!ParseLight(&light, err, arrayIt.value())) { + return false; + } + model->lights.push_back(light); + } + } + } + } + } + + // 19. Parse Extras + ParseExtrasProperty(&model->extras, v); + + return true; + } + + bool TinyGLTF::LoadASCIIFromString(Model *model, std::string *err, + std::string *warn, const char *str, + unsigned int length, + const std::string &base_dir, + unsigned int check_sections) { + is_binary_ = false; + bin_data_ = nullptr; + bin_size_ = 0; + + return LoadFromString(model, err, warn, str, length, base_dir, + check_sections); + } + + bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err, + std::string *warn, const std::string &filename, + unsigned int check_sections) { + std::stringstream ss; + + if (fs.ReadWholeFile == nullptr) { + // Programmer error, assert() ? + ss << "Failed to read file: " << filename + << ": one or more FS callback not set" << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::vector data; + std::string fileerr; + bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); + if (!fileread) { + ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + size_t sz = data.size(); + if (sz == 0) { + if (err) { + (*err) = "Empty file."; + } + return false; + } + + std::string basedir = GetBaseDir(filename); + + bool ret = LoadASCIIFromString( + model, err, warn, reinterpret_cast(&data.at(0)), + static_cast(data.size()), basedir, check_sections); + + return ret; + } + + bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, + std::string *warn, + 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 model_length; // 4 bytes + unsigned int model_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(&model_length, bytes + 12, 4); + swap4(&model_length); + memcpy(&model_format, bytes + 16, 4); + swap4(&model_format); + + // In case the Bin buffer is not present, the size is exactly 20 + size of + // JSON contents, + // so use "greater than" operator. + if ((20 + model_length > size) || (model_length < 1) || + (model_format != 0x4E4F534A)) { // 0x4E4F534A = JSON format. + if (err) { + (*err) = "Invalid glTF binary."; + } + return false; + } + + // Extract JSON string. + std::string jsonString(reinterpret_cast(&bytes[20]), + model_length); + + is_binary_ = true; + bin_data_ = bytes + 20 + model_length + + 8; // 4 bytes (buffer_length) + 4 bytes(buffer_format) + bin_size_ = + length - (20 + model_length); // extract header + JSON scene data. + + bool ret = LoadFromString(model, err, warn, + reinterpret_cast(&bytes[20]), + model_length, base_dir, check_sections); + if (!ret) { + return ret; + } + + return true; + } + + bool TinyGLTF::LoadBinaryFromFile(Model *model, std::string *err, + std::string *warn, + const std::string &filename, + unsigned int check_sections) { + std::stringstream ss; + + if (fs.ReadWholeFile == nullptr) { + // Programmer error, assert() ? + ss << "Failed to read file: " << filename + << ": one or more FS callback not set" << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::vector data; + std::string fileerr; + bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); + if (!fileread) { + ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::string basedir = GetBaseDir(filename); + + bool ret = LoadBinaryFromMemory(model, err, warn, &data.at(0), + static_cast(data.size()), + basedir, check_sections); + + return ret; + } + + /////////////////////// + // GLTF Serialization + /////////////////////// + + // typedef std::pair json_object_pair; + + template + static void SerializeNumberProperty(const std::string &key, T number, + json &obj) { + // obj.insert( + // json_object_pair(key, json(static_cast(number)))); + // obj[key] = static_cast(number); + obj[key] = number; + } + + template + static void SerializeNumberArrayProperty(const std::string &key, + const std::vector &value, + json &obj) { + json o; + json vals; + + for (unsigned int i = 0; i < value.size(); ++i) { + vals.push_back(static_cast(value[i])); + } + if (!vals.is_null()) { + obj[key] = vals; + } + } + + static void SerializeStringProperty(const std::string &key, + const std::string &value, json &obj) { + obj[key] = value; + } + + static void SerializeStringArrayProperty(const std::string &key, + const std::vector &value, + json &obj) { + json o; + json vals; + + for (unsigned int i = 0; i < value.size(); ++i) { + vals.push_back(value[i]); + } + + obj[key] = vals; + } + + static bool ValueToJson(const Value &value, json *ret) { + json obj; + switch (value.Type()) { + case NUMBER_TYPE: + obj = json(value.Get()); + break; + case INT_TYPE: + obj = json(value.Get()); + break; + case BOOL_TYPE: + obj = json(value.Get()); + break; + case STRING_TYPE: + obj = json(value.Get()); + break; + case ARRAY_TYPE: { + for (unsigned int i = 0; i < value.ArrayLen(); ++i) { + Value elementValue = value.Get(int(i)); + json elementJson; + if (ValueToJson(value.Get(int(i)), &elementJson)) + obj.push_back(elementJson); + } + break; + } + case BINARY_TYPE: + // TODO + // obj = json(value.Get>()); + return false; + break; + case OBJECT_TYPE: { + Value::Object objMap = value.Get(); + for (auto &it : objMap) { + json elementJson; + if (ValueToJson(it.second, &elementJson)) obj[it.first] = elementJson; + } + break; + } + case NULL_TYPE: + default: + return false; + } + if (ret) *ret = obj; + return true; + } + + static void SerializeValue(const std::string &key, const Value &value, + json &obj) { + json ret; + if (ValueToJson(value, &ret)) obj[key] = ret; + } + + static void SerializeGltfBufferData(const std::vector &data, + json &o) { + std::string header = "data:application/octet-stream;base64,"; + std::string encodedData = + base64_encode(&data[0], static_cast(data.size())); + SerializeStringProperty("uri", header + encodedData, o); + } + + static bool SerializeGltfBufferData(const std::vector &data, + const std::string &binFilename) { + std::ofstream output(binFilename.c_str(), std::ofstream::binary); + if (!output.is_open()) return false; + output.write(reinterpret_cast(&data[0]), + std::streamsize(data.size())); + output.close(); + return true; + } + + static void SerializeParameterMap(ParameterMap ¶m, json &o) { + for (ParameterMap::iterator paramIt = param.begin(); paramIt != param.end(); + ++paramIt) { + if (paramIt->second.number_array.size()) { + SerializeNumberArrayProperty(paramIt->first, + paramIt->second.number_array, o); + } + else if (paramIt->second.json_double_value.size()) { + json json_double_value; + for (std::map::iterator it = + paramIt->second.json_double_value.begin(); + it != paramIt->second.json_double_value.end(); ++it) { + if (it->first == "index") { + json_double_value[it->first] = paramIt->second.TextureIndex(); + } + else { + json_double_value[it->first] = it->second; + } + } + + o[paramIt->first] = json_double_value; + } + else if (!paramIt->second.string_value.empty()) { + SerializeStringProperty(paramIt->first, paramIt->second.string_value, o); + } + else if (paramIt->second.has_number_value) { + o[paramIt->first] = paramIt->second.number_value; + } + else { + o[paramIt->first] = paramIt->second.bool_value; + } + } + } + + static void SerializeExtensionMap(ExtensionMap &extensions, json &o) { + if (!extensions.size()) return; + + json extMap; + for (ExtensionMap::iterator extIt = extensions.begin(); + extIt != extensions.end(); ++extIt) { + json extension_values; + + // Allow an empty object for extension(#97) + json ret; + if (ValueToJson(extIt->second, &ret)) { + extMap[extIt->first] = ret; + } + if (ret.is_null()) { + if (!(extIt->first.empty())) { // name should not be empty, but for sure + // create empty object so that an extension name is still included in + // json. + extMap[extIt->first] = json({}); + } + } + } + o["extensions"] = extMap; + } + + static void SerializeGltfAccessor(Accessor &accessor, json &o) { + SerializeNumberProperty("bufferView", accessor.bufferView, o); + + if (accessor.byteOffset != 0.0) + SerializeNumberProperty("byteOffset", int(accessor.byteOffset), o); + + SerializeNumberProperty("componentType", accessor.componentType, o); + SerializeNumberProperty("count", accessor.count, o); + SerializeNumberArrayProperty("min", accessor.minValues, o); + SerializeNumberArrayProperty("max", accessor.maxValues, o); + std::string type; + switch (accessor.type) { + case TINYGLTF_TYPE_SCALAR: + type = "SCALAR"; + break; + case TINYGLTF_TYPE_VEC2: + type = "VEC2"; + break; + case TINYGLTF_TYPE_VEC3: + type = "VEC3"; + break; + case TINYGLTF_TYPE_VEC4: + type = "VEC4"; + break; + case TINYGLTF_TYPE_MAT2: + type = "MAT2"; + break; + case TINYGLTF_TYPE_MAT3: + type = "MAT3"; + break; + case TINYGLTF_TYPE_MAT4: + type = "MAT4"; + break; + } + + SerializeStringProperty("type", type, o); + if (!accessor.name.empty()) SerializeStringProperty("name", accessor.name, o); + + if (accessor.extras.Type() != NULL_TYPE) { + SerializeValue("extras", accessor.extras, o); + } + } + + static void SerializeGltfAnimationChannel(AnimationChannel &channel, json &o) { + SerializeNumberProperty("sampler", channel.sampler, o); + json target; + SerializeNumberProperty("node", channel.target_node, target); + SerializeStringProperty("path", channel.target_path, target); + + o["target"] = target; + + if (channel.extras.Type() != NULL_TYPE) { + SerializeValue("extras", channel.extras, o); + } + } + + static void SerializeGltfAnimationSampler(AnimationSampler &sampler, json &o) { + SerializeNumberProperty("input", sampler.input, o); + SerializeNumberProperty("output", sampler.output, o); + SerializeStringProperty("interpolation", sampler.interpolation, o); + + if (sampler.extras.Type() != NULL_TYPE) { + SerializeValue("extras", sampler.extras, o); + } + } + + static void SerializeGltfAnimation(Animation &animation, json &o) { + if (!animation.name.empty()) + SerializeStringProperty("name", animation.name, o); + json channels; + for (unsigned int i = 0; i < animation.channels.size(); ++i) { + json channel; + AnimationChannel gltfChannel = animation.channels[i]; + SerializeGltfAnimationChannel(gltfChannel, channel); + channels.push_back(channel); + } + o["channels"] = channels; + + json samplers; + for (unsigned int i = 0; i < animation.samplers.size(); ++i) { + json sampler; + AnimationSampler gltfSampler = animation.samplers[i]; + SerializeGltfAnimationSampler(gltfSampler, sampler); + samplers.push_back(sampler); + } + + o["samplers"] = samplers; + + if (animation.extras.Type() != NULL_TYPE) { + SerializeValue("extras", animation.extras, o); + } + } + + static void SerializeGltfAsset(Asset &asset, json &o) { + if (!asset.generator.empty()) { + SerializeStringProperty("generator", asset.generator, o); + } + + if (!asset.version.empty()) { + SerializeStringProperty("version", asset.version, o); + } + + if (asset.extras.Keys().size()) { + SerializeValue("extras", asset.extras, o); + } + + SerializeExtensionMap(asset.extensions, o); + } + + static void SerializeGltfBuffer(Buffer &buffer, json &o) { + SerializeNumberProperty("byteLength", buffer.data.size(), o); + SerializeGltfBufferData(buffer.data, o); + + if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); + + if (buffer.extras.Type() != NULL_TYPE) { + SerializeValue("extras", buffer.extras, o); + } + } + + static bool SerializeGltfBuffer(Buffer &buffer, json &o, + const std::string &binFilename, + const std::string &binBaseFilename) { + if (!SerializeGltfBufferData(buffer.data, binFilename)) return false; + SerializeNumberProperty("byteLength", buffer.data.size(), o); + SerializeStringProperty("uri", binBaseFilename, o); + + if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); + + if (buffer.extras.Type() != NULL_TYPE) { + SerializeValue("extras", buffer.extras, o); + } + return true; + } + + static void SerializeGltfBufferView(BufferView &bufferView, json &o) { + SerializeNumberProperty("buffer", bufferView.buffer, o); + SerializeNumberProperty("byteLength", bufferView.byteLength, o); + + // byteStride is optional, minimum allowed is 4 + if (bufferView.byteStride >= 4) { + SerializeNumberProperty("byteStride", bufferView.byteStride, o); + } + // byteOffset is optional, default is 0 + if (bufferView.byteOffset > 0) { + SerializeNumberProperty("byteOffset", bufferView.byteOffset, o); + } + // Target is optional, check if it contains a valid value + if (bufferView.target == TINYGLTF_TARGET_ARRAY_BUFFER || + bufferView.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) { + SerializeNumberProperty("target", bufferView.target, o); + } + if (bufferView.name.size()) { + SerializeStringProperty("name", bufferView.name, o); + } + + if (bufferView.extras.Type() != NULL_TYPE) { + SerializeValue("extras", bufferView.extras, o); + } + } + + static void SerializeGltfImage(Image &image, json &o) { + SerializeStringProperty("uri", image.uri, o); + + if (image.name.size()) { + SerializeStringProperty("name", image.name, o); + } + + if (image.extras.Type() != NULL_TYPE) { + SerializeValue("extras", image.extras, o); + } + + SerializeExtensionMap(image.extensions, o); + } + + static void SerializeGltfMaterial(Material &material, json &o) { + if (material.extras.Size()) SerializeValue("extras", material.extras, o); + SerializeExtensionMap(material.extensions, o); + + if (material.values.size()) { + json pbrMetallicRoughness; + SerializeParameterMap(material.values, pbrMetallicRoughness); + o["pbrMetallicRoughness"] = pbrMetallicRoughness; + } + + SerializeParameterMap(material.additionalValues, o); + + if (material.name.size()) { + SerializeStringProperty("name", material.name, o); + } + + if (material.extras.Type() != NULL_TYPE) { + SerializeValue("extras", material.extras, o); + } + } + + static void SerializeGltfMesh(Mesh &mesh, json &o) { + json primitives; + for (unsigned int i = 0; i < mesh.primitives.size(); ++i) { + json primitive; + json attributes; + Primitive gltfPrimitive = mesh.primitives[i]; + for (std::map::iterator attrIt = + gltfPrimitive.attributes.begin(); + attrIt != gltfPrimitive.attributes.end(); ++attrIt) { + SerializeNumberProperty(attrIt->first, attrIt->second, attributes); + } + + primitive["attributes"] = attributes; + + // Indicies is optional + if (gltfPrimitive.indices > -1) { + SerializeNumberProperty("indices", gltfPrimitive.indices, primitive); + } + // Material is optional + if (gltfPrimitive.material > -1) { + SerializeNumberProperty("material", gltfPrimitive.material, + primitive); + } + SerializeNumberProperty("mode", gltfPrimitive.mode, primitive); + + // Morph targets + if (gltfPrimitive.targets.size()) { + json targets; + for (unsigned int k = 0; k < gltfPrimitive.targets.size(); ++k) { + json targetAttributes; + std::map targetData = gltfPrimitive.targets[k]; + for (std::map::iterator attrIt = targetData.begin(); + attrIt != targetData.end(); ++attrIt) { + SerializeNumberProperty(attrIt->first, attrIt->second, + targetAttributes); + } + + targets.push_back(targetAttributes); + } + primitive["targets"] = targets; + } + + if (gltfPrimitive.extras.Type() != NULL_TYPE) { + SerializeValue("extras", gltfPrimitive.extras, primitive); + } + + primitives.push_back(primitive); + } + + o["primitives"] = primitives; + if (mesh.weights.size()) { + SerializeNumberArrayProperty("weights", mesh.weights, o); + } + + if (mesh.name.size()) { + SerializeStringProperty("name", mesh.name, o); + } + + if (mesh.extras.Type() != NULL_TYPE) { + SerializeValue("extras", mesh.extras, o); + } + } + + static void SerializeGltfLight(Light &light, json &o) { + if (!light.name.empty()) SerializeStringProperty("name", light.name, o); + SerializeNumberArrayProperty("color", light.color, o); + SerializeStringProperty("type", light.type, o); + } + + static void SerializeGltfNode(Node &node, json &o) { + if (node.translation.size() > 0) { + SerializeNumberArrayProperty("translation", node.translation, o); + } + if (node.rotation.size() > 0) { + SerializeNumberArrayProperty("rotation", node.rotation, o); + } + if (node.scale.size() > 0) { + SerializeNumberArrayProperty("scale", node.scale, o); + } + if (node.matrix.size() > 0) { + SerializeNumberArrayProperty("matrix", node.matrix, o); + } + if (node.mesh != -1) { + SerializeNumberProperty("mesh", node.mesh, o); + } + + if (node.skin != -1) { + SerializeNumberProperty("skin", node.skin, o); + } + + if (node.camera != -1) { + SerializeNumberProperty("camera", node.camera, o); + } + + if (node.extras.Type() != NULL_TYPE) { + SerializeValue("extras", node.extras, o); + } + + SerializeExtensionMap(node.extensions, o); + if (!node.name.empty()) SerializeStringProperty("name", node.name, o); + SerializeNumberArrayProperty("children", node.children, o); + } + + static void SerializeGltfSampler(Sampler &sampler, json &o) { + SerializeNumberProperty("magFilter", sampler.magFilter, o); + SerializeNumberProperty("minFilter", sampler.minFilter, o); + SerializeNumberProperty("wrapR", sampler.wrapR, o); + SerializeNumberProperty("wrapS", sampler.wrapS, o); + SerializeNumberProperty("wrapT", sampler.wrapT, o); + + if (sampler.extras.Type() != NULL_TYPE) { + SerializeValue("extras", sampler.extras, o); + } + } + + static void SerializeGltfOrthographicCamera(const OrthographicCamera &camera, + json &o) { + SerializeNumberProperty("zfar", camera.zfar, o); + SerializeNumberProperty("znear", camera.znear, o); + SerializeNumberProperty("xmag", camera.xmag, o); + SerializeNumberProperty("ymag", camera.ymag, o); + + if (camera.extras.Type() != NULL_TYPE) { + SerializeValue("extras", camera.extras, o); + } + } + + static void SerializeGltfPerspectiveCamera(const PerspectiveCamera &camera, + json &o) { + SerializeNumberProperty("zfar", camera.zfar, o); + SerializeNumberProperty("znear", camera.znear, o); + if (camera.aspectRatio > 0) { + SerializeNumberProperty("aspectRatio", camera.aspectRatio, o); + } + + if (camera.yfov > 0) { + SerializeNumberProperty("yfov", camera.yfov, o); + } + + if (camera.extras.Type() != NULL_TYPE) { + SerializeValue("extras", camera.extras, o); + } + } + + static void SerializeGltfCamera(const Camera &camera, json &o) { + SerializeStringProperty("type", camera.type, o); + if (!camera.name.empty()) { + SerializeStringProperty("name", camera.name, o); + } + + if (camera.type.compare("orthographic") == 0) { + json orthographic; + SerializeGltfOrthographicCamera(camera.orthographic, orthographic); + o["orthographic"] = orthographic; + } + else if (camera.type.compare("perspective") == 0) { + json perspective; + SerializeGltfPerspectiveCamera(camera.perspective, perspective); + o["perspective"] = perspective; + } + else { + // ??? + } + } + + static void SerializeGltfScene(Scene &scene, json &o) { + SerializeNumberArrayProperty("nodes", scene.nodes, o); + + if (scene.name.size()) { + SerializeStringProperty("name", scene.name, o); + } + if (scene.extras.Type() != NULL_TYPE) { + SerializeValue("extras", scene.extras, o); + } + SerializeExtensionMap(scene.extensions, o); + } + + static void SerializeGltfSkin(Skin &skin, json &o) { + if (skin.inverseBindMatrices != -1) + SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o); + + SerializeNumberArrayProperty("joints", skin.joints, o); + SerializeNumberProperty("skeleton", skin.skeleton, o); + if (skin.name.size()) { + SerializeStringProperty("name", skin.name, o); + } + } + + static void SerializeGltfTexture(Texture &texture, json &o) { + if (texture.sampler > -1) { + SerializeNumberProperty("sampler", texture.sampler, o); + } + if (texture.source > -1) { + SerializeNumberProperty("source", texture.source, o); + } + if (texture.extras.Type() != NULL_TYPE) { + SerializeValue("extras", texture.extras, o); + } + SerializeExtensionMap(texture.extensions, o); + } + + static bool WriteGltfFile(const std::string &output, + const std::string &content) { + std::ofstream gltfFile(output.c_str()); + if (!gltfFile.is_open()) return false; + gltfFile << content << std::endl; + return true; + } + + static void WriteBinaryGltfFile(const std::string &output, + const std::string &content) { + std::ofstream gltfFile(output.c_str(), std::ios::binary); + + const std::string header = "glTF"; + const int version = 2; + const int padding_size = content.size() % 4; + + // 12 bytes for header, JSON content length, 8 bytes for JSON chunk info, + // padding + const int length = 12 + 8 + int(content.size()) + padding_size; + + gltfFile.write(header.c_str(), header.size()); + gltfFile.write(reinterpret_cast(&version), sizeof(version)); + gltfFile.write(reinterpret_cast(&length), sizeof(length)); + + // JSON chunk info, then JSON data + const int model_length = int(content.size()) + padding_size; + const int model_format = 0x4E4F534A; + gltfFile.write(reinterpret_cast(&model_length), + sizeof(model_length)); + gltfFile.write(reinterpret_cast(&model_format), + sizeof(model_format)); + gltfFile.write(content.c_str(), content.size()); + + // Chunk must be multiplies of 4, so pad with spaces + if (padding_size > 0) { + const std::string padding = std::string(padding_size, ' '); + gltfFile.write(padding.c_str(), padding.size()); + } + } + + bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename, + bool embedImages = false, + bool embedBuffers = false, + bool prettyPrint = true, + bool writeBinary = false) { + json output; + + // ACCESSORS + json accessors; + for (unsigned int i = 0; i < model->accessors.size(); ++i) { + json accessor; + SerializeGltfAccessor(model->accessors[i], accessor); + accessors.push_back(accessor); + } + output["accessors"] = accessors; + + // ANIMATIONS + if (model->animations.size()) { + json animations; + for (unsigned int i = 0; i < model->animations.size(); ++i) { + if (model->animations[i].channels.size()) { + json animation; + SerializeGltfAnimation(model->animations[i], animation); + animations.push_back(animation); + } + } + output["animations"] = animations; + } + + // ASSET + json asset; + SerializeGltfAsset(model->asset, asset); + output["asset"] = asset; + + std::string defaultBinFilename = GetBaseFilename(filename); + std::string defaultBinFileExt = ".bin"; + std::string::size_type pos = + defaultBinFilename.rfind('.', defaultBinFilename.length()); + + if (pos != std::string::npos) { + defaultBinFilename = defaultBinFilename.substr(0, pos); + } + std::string baseDir = GetBaseDir(filename); + if (baseDir.empty()) { + baseDir = "./"; + } + + // BUFFERS + std::vector usedUris; + json buffers; + for (unsigned int i = 0; i < model->buffers.size(); ++i) { + json buffer; + if (embedBuffers) { + SerializeGltfBuffer(model->buffers[i], buffer); + } + else { + std::string binSavePath; + std::string binUri; + if (!model->buffers[i].uri.empty() && !IsDataURI(model->buffers[i].uri)) { + binUri = model->buffers[i].uri; + } + else { + binUri = defaultBinFilename + defaultBinFileExt; + bool inUse = true; + int numUsed = 0; + while (inUse) { + inUse = false; + for (const std::string &usedName : usedUris) { + if (binUri.compare(usedName) != 0) continue; + inUse = true; + binUri = defaultBinFilename + std::to_string(numUsed++) + + defaultBinFileExt; + break; + } + } + } + usedUris.push_back(binUri); + binSavePath = JoinPath(baseDir, binUri); + if (!SerializeGltfBuffer(model->buffers[i], buffer, binSavePath, + binUri)) { + return false; + } + } + buffers.push_back(buffer); + } + output["buffers"] = buffers; + + // BUFFERVIEWS + json bufferViews; + for (unsigned int i = 0; i < model->bufferViews.size(); ++i) { + json bufferView; + SerializeGltfBufferView(model->bufferViews[i], bufferView); + bufferViews.push_back(bufferView); + } + output["bufferViews"] = bufferViews; + + // Extensions used + if (model->extensionsUsed.size()) { + SerializeStringArrayProperty("extensionsUsed", model->extensionsUsed, + output); + } + + // Extensions required + if (model->extensionsRequired.size()) { + SerializeStringArrayProperty("extensionsRequired", + model->extensionsRequired, output); + } + + // IMAGES + if (model->images.size()) { + json images; + for (unsigned int i = 0; i < model->images.size(); ++i) { + json image; + + UpdateImageObject(model->images[i], baseDir, int(i), embedImages, + &this->WriteImageData, this->write_image_user_data_); + SerializeGltfImage(model->images[i], image); + images.push_back(image); + } + output["images"] = images; + } + + // MATERIALS + if (model->materials.size()) { + json materials; + for (unsigned int i = 0; i < model->materials.size(); ++i) { + json material; + SerializeGltfMaterial(model->materials[i], material); + materials.push_back(material); + } + output["materials"] = materials; + } + + // MESHES + if (model->meshes.size()) { + json meshes; + for (unsigned int i = 0; i < model->meshes.size(); ++i) { + json mesh; + SerializeGltfMesh(model->meshes[i], mesh); + meshes.push_back(mesh); + } + output["meshes"] = meshes; + } + + // NODES + if (model->nodes.size()) { + json nodes; + for (unsigned int i = 0; i < model->nodes.size(); ++i) { + json node; + SerializeGltfNode(model->nodes[i], node); + nodes.push_back(node); + } + output["nodes"] = nodes; + } + + // SCENE + if (model->defaultScene > -1) { + SerializeNumberProperty("scene", model->defaultScene, output); + } + + // SCENES + if (model->scenes.size()) { + json scenes; + for (unsigned int i = 0; i < model->scenes.size(); ++i) { + json currentScene; + SerializeGltfScene(model->scenes[i], currentScene); + scenes.push_back(currentScene); + } + output["scenes"] = scenes; + } + + // SKINS + if (model->skins.size()) { + json skins; + for (unsigned int i = 0; i < model->skins.size(); ++i) { + json skin; + SerializeGltfSkin(model->skins[i], skin); + skins.push_back(skin); + } + output["skins"] = skins; + } + + // TEXTURES + if (model->textures.size()) { + json textures; + for (unsigned int i = 0; i < model->textures.size(); ++i) { + json texture; + SerializeGltfTexture(model->textures[i], texture); + textures.push_back(texture); + } + output["textures"] = textures; + } + + // SAMPLERS + if (model->samplers.size()) { + json samplers; + for (unsigned int i = 0; i < model->samplers.size(); ++i) { + json sampler; + SerializeGltfSampler(model->samplers[i], sampler); + samplers.push_back(sampler); + } + output["samplers"] = samplers; + } + + // CAMERAS + if (model->cameras.size()) { + json cameras; + for (unsigned int i = 0; i < model->cameras.size(); ++i) { + json camera; + SerializeGltfCamera(model->cameras[i], camera); + cameras.push_back(camera); + } + output["cameras"] = cameras; + } + + // EXTENSIONS + SerializeExtensionMap(model->extensions, output); + + // LIGHTS as KHR_lights_cmn + if (model->lights.size()) { + json lights; + for (unsigned int i = 0; i < model->lights.size(); ++i) { + json light; + SerializeGltfLight(model->lights[i], light); + lights.push_back(light); + } + json khr_lights_cmn; + khr_lights_cmn["lights"] = lights; + json ext_j; + + if (output.find("extensions") != output.end()) { + ext_j = output["extensions"]; + } + + ext_j["KHR_lights_cmn"] = khr_lights_cmn; + + output["extensions"] = ext_j; + } + + // EXTRAS + if (model->extras.Type() != NULL_TYPE) { + SerializeValue("extras", model->extras, output); + } + + if (writeBinary) { + WriteBinaryGltfFile(filename, output.dump()); + } + else { + WriteGltfFile(filename, output.dump(prettyPrint ? 2 : -1)); + } + + return true; + } } // namespace tinygltf @@ -4452,4 +5378,4 @@ bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename, #pragma clang diagnostic pop #endif -#endif // TINYGLTF_IMPLEMENTATION +#endif // TINYGLTF_IMPLEMENTATION \ No newline at end of file diff --git a/WickedEngine/ArchiveVersionHistory.txt b/WickedEngine/ArchiveVersionHistory.txt index f243c88d0..abf5d0044 100644 --- a/WickedEngine/ArchiveVersionHistory.txt +++ b/WickedEngine/ArchiveVersionHistory.txt @@ -1,5 +1,6 @@ This file contains changelog of wiArchive versions +28: extended MaterialComponent with occlusionMap and uvsets, and displacement map factor; MeshComponent extended with second uv set 27: removed ArmatureComponent::rootBoneID 26: removed ArmatureComponent::remapMatrix, added ArmatureComponent::rootBoneID 25: emissiveColor separated from baseColor diff --git a/WickedEngine/ShaderInterop_BVH.h b/WickedEngine/ShaderInterop_BVH.h index 54aabab1c..d36021077 100644 --- a/WickedEngine/ShaderInterop_BVH.h +++ b/WickedEngine/ShaderInterop_BVH.h @@ -24,7 +24,7 @@ struct BVHMeshTriangle { float3 v0, v1, v2; // positions float3 n0, n1, n2; // normals - float2 t0, t1, t2; // texcoords + float4 u0, u1, u2; // uv sets float4 c0, c1, c2; // vertex colors uint materialIndex; }; diff --git a/WickedEngine/ShaderInterop_Renderer.h b/WickedEngine/ShaderInterop_Renderer.h index 7fb6e7a38..3165a19c2 100644 --- a/WickedEngine/ShaderInterop_Renderer.h +++ b/WickedEngine/ShaderInterop_Renderer.h @@ -239,7 +239,17 @@ CBUFFER(MaterialCB, CBSLOT_RENDERER_MATERIAL) float g_xMat_normalMapFlip; float g_xMat_parallaxOcclusionMapping; + float g_xMat_displacementMapping; uint g_xMat_useVertexColors; + uint g_xMat_uvset_baseColorMap; + uint g_xMat_uvset_surfaceMap; + + uint g_xMat_uvset_normalMap; + uint g_xMat_uvset_displacementMap; + uint g_xMat_uvset_emissiveMap; + uint g_xMat_uvset_occlusionMap; + + uint g_xMat_specularGlossinessWorkflow; uint3 g_xMat_padding; }; diff --git a/WickedEngine/ShaderInterop_TracedRendering.h b/WickedEngine/ShaderInterop_TracedRendering.h index 6a4d26a49..125db5286 100644 --- a/WickedEngine/ShaderInterop_TracedRendering.h +++ b/WickedEngine/ShaderInterop_TracedRendering.h @@ -41,12 +41,22 @@ struct TracedRenderingMaterial float normalMapFlip; float parallaxOcclusionMapping; + float displacementMapping; + uint useVertexColors; + uint uvset_baseColorMap; + uint uvset_surfaceMap; + + uint uvset_normalMap; + uint uvset_displacementMap; + uint uvset_emissiveMap; + uint uvset_occlusionMap; + + uint specularGlossinessWorkflow; + uint3 padding; + float4 baseColorAtlasMulAdd; float4 surfaceMapAtlasMulAdd; float4 emissiveMapAtlasMulAdd; - - uint g_xMat_useVertexColors; - uint3 g_xMat_padding; }; diff --git a/WickedEngine/bvh_classificationCS.hlsl b/WickedEngine/bvh_classificationCS.hlsl index d2b0a2722..48bd5ce63 100644 --- a/WickedEngine/bvh_classificationCS.hlsl +++ b/WickedEngine/bvh_classificationCS.hlsl @@ -13,8 +13,9 @@ STRUCTUREDBUFFER(materialBuffer, TracedRenderingMaterial, TEXSLOT_ONDEMAND0); TYPEDBUFFER(meshIndexBuffer, uint, TEXSLOT_ONDEMAND1); RAWBUFFER(meshVertexBuffer_POS, TEXSLOT_ONDEMAND2); -TYPEDBUFFER(meshVertexBuffer_TEX, float2, TEXSLOT_ONDEMAND3); -TYPEDBUFFER(meshVertexBuffer_COL, float4, TEXSLOT_ONDEMAND4); +TYPEDBUFFER(meshVertexBuffer_UV0, float2, TEXSLOT_ONDEMAND3); +TYPEDBUFFER(meshVertexBuffer_UV1, float2, TEXSLOT_ONDEMAND4); +TYPEDBUFFER(meshVertexBuffer_COL, float4, TEXSLOT_ONDEMAND5); RWSTRUCTUREDBUFFER(triangleBuffer, BVHMeshTriangle, 0); RWRAWBUFFER(clusterCounterBuffer, 1); @@ -129,9 +130,9 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) prim.n0 = mul((float3x3)WORLD, nor0); prim.n1 = mul((float3x3)WORLD, nor1); prim.n2 = mul((float3x3)WORLD, nor2); - prim.t0 = meshVertexBuffer_TEX[i0]; - prim.t1 = meshVertexBuffer_TEX[i1]; - prim.t2 = meshVertexBuffer_TEX[i2]; + prim.u0 = float4(meshVertexBuffer_UV0[i0], meshVertexBuffer_UV1[i0]); + prim.u1 = float4(meshVertexBuffer_UV0[i1], meshVertexBuffer_UV1[i1]); + prim.u2 = float4(meshVertexBuffer_UV0[i2], meshVertexBuffer_UV1[i2]); prim.materialIndex = xTraceBVHMaterialOffset + materialIndex; TracedRenderingMaterial mat = materialBuffer[prim.materialIndex]; @@ -142,7 +143,7 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) prim.c2 = color; [branch] - if (mat.g_xMat_useVertexColors) + if (mat.useVertexColors) { prim.c0 *= meshVertexBuffer_COL[i0]; prim.c1 *= meshVertexBuffer_COL[i1]; diff --git a/WickedEngine/captureImpostorPS_albedo.hlsl b/WickedEngine/captureImpostorPS_albedo.hlsl index 80a0d2b1d..d91668ad3 100644 --- a/WickedEngine/captureImpostorPS_albedo.hlsl +++ b/WickedEngine/captureImpostorPS_albedo.hlsl @@ -2,9 +2,8 @@ float4 main(PixelInputType input) : SV_Target0 { - float2 UV = input.tex * g_xMat_texMulAdd.xy + g_xMat_texMulAdd.zw; - - float4 color = xBaseColorMap.Sample(sampler_objectshader, UV); + const float2 UV_baseColorMap = g_xMat_uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float4 color = xBaseColorMap.Sample(sampler_objectshader, UV_baseColorMap); color.rgb = DEGAMMA(color.rgb); color *= input.color; ALPHATEST(color.a); diff --git a/WickedEngine/captureImpostorPS_normal.hlsl b/WickedEngine/captureImpostorPS_normal.hlsl index 525ff0997..412dc0125 100644 --- a/WickedEngine/captureImpostorPS_normal.hlsl +++ b/WickedEngine/captureImpostorPS_normal.hlsl @@ -2,15 +2,14 @@ float4 main(PixelInputType input) : SV_Target0 { - float2 UV = input.tex * g_xMat_texMulAdd.xy + g_xMat_texMulAdd.zw; - float3 N = normalize(input.nor); float3 P = input.pos3D; - float3x3 TBN = compute_tangent_frame(N, P, UV); + float3x3 TBN = compute_tangent_frame(N, P, input.uvsets.xy); float3 bumpColor; - NormalMapping(UV, P, N, TBN, bumpColor); + const float2 UV_normalMap = g_xMat_uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; + NormalMapping(UV_normalMap, P, N, TBN, bumpColor); return float4(N * 0.5f + 0.5f, 1); } diff --git a/WickedEngine/captureImpostorPS_surface.hlsl b/WickedEngine/captureImpostorPS_surface.hlsl index 94a291588..6bf8fccdb 100644 --- a/WickedEngine/captureImpostorPS_surface.hlsl +++ b/WickedEngine/captureImpostorPS_surface.hlsl @@ -2,9 +2,13 @@ float4 main(PixelInputType input) : SV_Target0 { - float2 UV = input.tex * g_xMat_texMulAdd.xy + g_xMat_texMulAdd.zw; + const float2 UV_surfaceMap = g_xMat_uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float4 surface_ao_roughness_metallic_reflectance = xSurfaceMap.Sample(sampler_objectshader, UV_surfaceMap); - float4 surface_ao_roughness_metallic_reflectance = xSurfaceMap.Sample(sampler_objectshader, UV); + if (g_xMat_specularGlossinessWorkflow) + { + ConvertToSpecularGlossiness(surface_ao_roughness_metallic_reflectance); + } float4 surface; surface.r = surface_ao_roughness_metallic_reflectance.r; diff --git a/WickedEngine/cubeShadowGS.hlsl b/WickedEngine/cubeShadowGS.hlsl index 2b5a8fb32..16c10a060 100644 --- a/WickedEngine/cubeShadowGS.hlsl +++ b/WickedEngine/cubeShadowGS.hlsl @@ -2,11 +2,11 @@ struct GS_CUBEMAP_IN { - float4 Pos : SV_POSITION; + float4 pos : SV_POSITION; }; struct PS_CUBEMAP_IN { - float4 Pos : SV_POSITION; + float4 pos : SV_POSITION; float3 pos3D : POSITION3D; uint RTIndex : SV_RenderTargetArrayIndex; }; @@ -23,8 +23,8 @@ void main( triangle GS_CUBEMAP_IN input[3], inout TriangleStream [unroll] for (int v = 0; v < 3; v++) { - output.Pos = mul(input[v].Pos, xCubeShadowVP[f]); - output.pos3D = input[v].Pos.xyz; + output.pos = mul(input[v].pos, xCubeShadowVP[f]); + output.pos3D = input[v].pos.xyz; CubeMapStream.Append(output); } CubeMapStream.RestartStrip(); diff --git a/WickedEngine/cubeShadowGS_alphatest.hlsl b/WickedEngine/cubeShadowGS_alphatest.hlsl index 23a3d6239..700aae6dd 100644 --- a/WickedEngine/cubeShadowGS_alphatest.hlsl +++ b/WickedEngine/cubeShadowGS_alphatest.hlsl @@ -2,14 +2,14 @@ struct GS_CUBEMAP_IN { - float4 Pos : SV_POSITION; - float2 Tex : TEXCOORD0; + float4 pos : SV_POSITION; + float2 uv : UV; }; struct PS_CUBEMAP_IN { - float4 Pos : SV_POSITION; + float4 pos : SV_POSITION; float3 pos3D : POSITION3D; - float2 Tex : TEXCOORD0; + float2 uv : UV; uint RTIndex : SV_RenderTargetArrayIndex; }; @@ -24,9 +24,9 @@ void main(triangle GS_CUBEMAP_IN input[3], inout TriangleStream C [unroll] for (int v = 0; v < 3; v++) { - output.Pos = mul(input[v].Pos, xCubeShadowVP[f]); - output.pos3D = input[v].Pos.xyz; - output.Tex = input[v].Tex; + output.pos = mul(input[v].pos, xCubeShadowVP[f]); + output.pos3D = input[v].pos.xyz; + output.uv = input[v].uv; CubeMapStream.Append(output); } CubeMapStream.RestartStrip(); diff --git a/WickedEngine/cubeShadowPS_alphatest.hlsl b/WickedEngine/cubeShadowPS_alphatest.hlsl index 14c785bc5..c843ac80c 100644 --- a/WickedEngine/cubeShadowPS_alphatest.hlsl +++ b/WickedEngine/cubeShadowPS_alphatest.hlsl @@ -4,12 +4,12 @@ struct VertextoPixel { float4 pos : SV_POSITION; float3 pos3D : POSITION3D; - float2 tex : TEXCOORD0; + float2 uv : UV; uint RTIndex : SV_RenderTargetArrayIndex; }; float main(VertextoPixel PSIn) : SV_DEPTH { - ALPHATEST(xBaseColorMap.Sample(sampler_linear_wrap,PSIn.tex).a); + ALPHATEST(xBaseColorMap.Sample(sampler_linear_wrap, PSIn.uv).a); return 1.0f - distance(PSIn.pos3D.xyz, g_xColor.xyz) * g_xColor.w; // g_xColor.w = 1.0 / range } \ No newline at end of file diff --git a/WickedEngine/cubeShadowVS_alphatest.hlsl b/WickedEngine/cubeShadowVS_alphatest.hlsl index 22a403e12..aaf221526 100644 --- a/WickedEngine/cubeShadowVS_alphatest.hlsl +++ b/WickedEngine/cubeShadowVS_alphatest.hlsl @@ -4,8 +4,8 @@ struct GS_CUBEMAP_IN { - float4 Pos : SV_POSITION; // World position - float2 Tex : TEXCOORD0; // Texture coord + float4 pos : SV_POSITION; // World position + float2 uv : UV; }; GS_CUBEMAP_IN main(Input_Object_POS_TEX input) @@ -15,8 +15,8 @@ GS_CUBEMAP_IN main(Input_Object_POS_TEX input) float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); VertexSurface surface = MakeVertexSurfaceFromInput(input); - Out.Pos = mul(surface.position, WORLD); - Out.Tex = surface.uv; + Out.pos = mul(surface.position, WORLD); + Out.uv = g_xMat_uvset_baseColorMap == 0 ? surface.uvsets.xy : surface.uvsets.zw; return Out; } \ No newline at end of file diff --git a/WickedEngine/envMapGS.hlsl b/WickedEngine/envMapGS.hlsl index 26a6c6141..1bdca0c19 100644 --- a/WickedEngine/envMapGS.hlsl +++ b/WickedEngine/envMapGS.hlsl @@ -13,7 +13,7 @@ void main(triangle VSOut_EnvmapRendering input[3], inout TriangleStream> 0) & 0x000000FF, (g_xFrame_TemporalAASampleRotation >> 8) & 0x000000FF); } inline bool IsStaticSky() { return g_xFrame_StaticSkyGamma > 0.0f; } +inline void ConvertToSpecularGlossiness(inout float4 surface_ao_roughness_metallic_reflectance) +{ + surface_ao_roughness_metallic_reflectance.r = 1; + surface_ao_roughness_metallic_reflectance.g = 1 - surface_ao_roughness_metallic_reflectance.a; + surface_ao_roughness_metallic_reflectance.b = max(surface_ao_roughness_metallic_reflectance.r, max(surface_ao_roughness_metallic_reflectance.g, surface_ao_roughness_metallic_reflectance.b)); + surface_ao_roughness_metallic_reflectance.a = 0.02f; +} struct ComputeShaderInput { diff --git a/WickedEngine/objectDS.hlsl b/WickedEngine/objectDS.hlsl index 8b8ad8e20..a23b43e2a 100644 --- a/WickedEngine/objectDS.hlsl +++ b/WickedEngine/objectDS.hlsl @@ -14,9 +14,13 @@ struct ConstantOutputType float4 color1 : COLOR1; float4 color2 : COLOR2; - float4 tex0 : TEXCOORD0; - float4 tex1 : TEXCOORD1; - float4 tex2 : TEXCOORD2; + float4 uvsets0 : UVSETS0; + float4 uvsets1 : UVSETS1; + float4 uvsets2 : UVSETS2; + + float4 atlas0 : ATLAS0; + float4 atlas1 : ATLAS1; + float4 atlas2 : ATLAS2; float4 nor0 : NORMAL0; float4 nor1 : NORMAL1; @@ -31,7 +35,8 @@ struct HullOutputType { float4 pos : POSITION; float4 color : COLOR; - float4 tex : TEXCOORD0; + float4 uvsets : UVSETS; + float4 atlas : ATLAS; float4 nor : NORMAL; float4 posPrev : POSITIONPREV; }; @@ -96,11 +101,13 @@ PixelInputType main(ConstantOutputType input, float3 uvwCoord : SV_DomainLocatio float4 vertexPosition; float4 vertexPositionPrev; float3 vertexNormal; - float4 vertexTex; + float4 vertexUvsets; + float2 vertexAtlas; float4 vertexColor; //New vertex returned from tessallator, average it - vertexTex = uvwCoord.z * patch[0].tex + uvwCoord.x * patch[1].tex + uvwCoord.y * patch[2].tex; + vertexUvsets = uvwCoord.z * patch[0].uvsets + uvwCoord.x * patch[1].uvsets + uvwCoord.y * patch[2].uvsets; + vertexAtlas = uvwCoord.z * patch[0].atlas.xy + uvwCoord.x * patch[1].atlas.xy + uvwCoord.y * patch[2].atlas.xy; vertexColor = uvwCoord.z * patch[0].color + uvwCoord.x * patch[1].color + uvwCoord.y * patch[2].color; @@ -117,9 +124,10 @@ PixelInputType main(ConstantOutputType input, float3 uvwCoord : SV_DomainLocatio vertexNormal = PhongNormal(fU, fV, fW, input); // Displacement - float3 displacement = 1 - xDisplacementMap.SampleLevel(sampler_linear_wrap, vertexTex.xy, 0).rrr; + const float2 UV_displacementMap = g_xMat_uvset_displacementMap == 0 ? vertexUvsets.xy : vertexUvsets.zw; + float3 displacement = 1 - xDisplacementMap.SampleLevel(sampler_linear_wrap, UV_displacementMap, 0).rrr; displacement *= vertexNormal.xyz; - displacement *= 0.1f; // todo: param + displacement *= -g_xMat_displacementMapping; vertexPosition.xyz += displacement; vertexPositionPrev.xyz += displacement; @@ -128,11 +136,11 @@ PixelInputType main(ConstantOutputType input, float3 uvwCoord : SV_DomainLocatio Out.pos = Out.pos2D = mul( vertexPosition, g_xCamera_VP ); Out.pos2DPrev = mul(vertexPositionPrev, g_xFrame_MainCamera_PrevVP); Out.pos3D = vertexPosition.xyz; - Out.tex = vertexTex.xy; + Out.uvsets = vertexUvsets; + Out.atl = vertexAtlas; Out.color = vertexColor; Out.nor = normalize(vertexNormal.xyz); Out.nor2D = mul(Out.nor.xyz, (float3x3)g_xCamera_View).xy; - Out.atl = vertexTex.zw; Out.ReflectionMapSamplingPos = mul(vertexPosition, g_xFrame_MainCamera_ReflVP); diff --git a/WickedEngine/objectGS_voxelizer.hlsl b/WickedEngine/objectGS_voxelizer.hlsl index cd7c23879..859d0df38 100644 --- a/WickedEngine/objectGS_voxelizer.hlsl +++ b/WickedEngine/objectGS_voxelizer.hlsl @@ -4,7 +4,7 @@ struct GSInput { float4 pos : SV_POSITION; float4 color : COLOR; - float2 tex : TEXCOORD; + float4 uvsets : UVSETS; float3 nor : NORMAL; }; @@ -12,7 +12,7 @@ struct GSOutput { float4 pos : SV_POSITION; float4 color : COLOR; - float2 tex : TEXCOORD; + float4 uvsets : UVSETS; float3 N : NORMAL; float3 P : POSITION3D; }; @@ -66,7 +66,7 @@ void main( // Append the rest of the parameters as is: output[j].color = input[j].color; - output[j].tex = input[j].tex; + output[j].uvsets = input[j].uvsets; output[j].N = input[j].nor; output[j].P = input[j].pos.xyz; diff --git a/WickedEngine/objectHF.hlsli b/WickedEngine/objectHF.hlsli index aab7ffe19..58c75dbc9 100644 --- a/WickedEngine/objectHF.hlsli +++ b/WickedEngine/objectHF.hlsli @@ -37,6 +37,7 @@ #define xSurfaceMap texture_2 // r: ao, g: roughness, b: metallic, a: reflectance #define xDisplacementMap texture_3 // r: heightmap #define xEmissiveMap texture_4 // rgba: emissive +#define xOcclusionMap texture_5 // r: ao // These are bound by RenderPath (based on Render Path): #define xReflection texture_6 // rgba: scene color from reflected camera angle @@ -51,14 +52,14 @@ struct PixelInputType_Simple float4 pos : SV_POSITION; float clip : SV_ClipDistance0; float4 color : COLOR; - float2 tex : TEXCOORD0; + float4 uvsets : UVSETS; }; struct PixelInputType { float4 pos : SV_POSITION; float clip : SV_ClipDistance0; float4 color : COLOR; - float2 tex : TEXCOORD0; + float4 uvsets : UVSETS; float2 atl : ATLAS; float3 nor : NORMAL; float4 pos2D : SCREENPOSITION; @@ -150,15 +151,16 @@ inline float3 PlanarReflection(in float2 reflectionUV, in float2 bumpColor) } #define NUM_PARALLAX_OCCLUSION_STEPS 32 -inline void ParallaxOcclusionMapping(inout float2 UV, in float3 V, in float3x3 TBN) +inline void ParallaxOcclusionMapping(inout float4 uvsets, in float3 V, in float3x3 TBN) { V = mul(TBN, V); float layerHeight = 1.0 / NUM_PARALLAX_OCCLUSION_STEPS; float curLayerHeight = 0; float2 dtex = g_xMat_parallaxOcclusionMapping * V.xy / NUM_PARALLAX_OCCLUSION_STEPS; - float2 currentTextureCoords = UV; - float2 derivX = ddx_coarse(UV); - float2 derivY = ddy_coarse(UV); + float2 originalTextureCoords = g_xMat_uvset_displacementMap == 0 ? uvsets.xy : uvsets.zw; + float2 currentTextureCoords = originalTextureCoords; + float2 derivX = ddx_coarse(currentTextureCoords); + float2 derivY = ddy_coarse(currentTextureCoords); float heightFromTexture = 1 - xDisplacementMap.SampleGrad(sampler_linear_wrap, currentTextureCoords, derivX, derivY).r; uint iter = 0; [loop] @@ -173,8 +175,9 @@ inline void ParallaxOcclusionMapping(inout float2 UV, in float3 V, in float3x3 T float nextH = heightFromTexture - curLayerHeight; float prevH = 1 - xDisplacementMap.SampleGrad(sampler_linear_wrap, prevTCoords, derivX, derivY).r - curLayerHeight + layerHeight; float weight = nextH / (nextH - prevH); - float2 finalTexCoords = prevTCoords * weight + currentTextureCoords * (1.0 - weight); - UV = finalTexCoords; + float2 finalTextureCoords = prevTCoords * weight + currentTextureCoords * (1.0 - weight); + float2 difference = finalTextureCoords - originalTextureCoords; + uvsets += difference.xyxy; } inline void Refraction(in float2 ScreenCoord, in float2 normal2D, in float3 bumpColor, inout Surface surface, inout float4 color, inout float3 diffuse) @@ -734,9 +737,6 @@ GBUFFEROutputType_Thin main(PIXELINPUT input) #endif // TRANSPARENT #endif // ENVMAPRENDERING - - float2 UV = input.tex * g_xMat_texMulAdd.xy + g_xMat_texMulAdd.zw; - Surface surface; #ifndef SIMPLE_INPUT @@ -746,14 +746,15 @@ GBUFFEROutputType_Thin main(PIXELINPUT input) surface.V /= dist; surface.N = normalize(input.nor); - float3x3 TBN = compute_tangent_frame(surface.N, surface.P, UV); + float3x3 TBN = compute_tangent_frame(surface.N, surface.P, input.uvsets.xy); #endif // SIMPLE_INPUT #ifdef POM - ParallaxOcclusionMapping(UV, surface.V, TBN); + ParallaxOcclusionMapping(input.uvsets, surface.V, TBN); #endif // POM - float4 color = xBaseColorMap.Sample(sampler_objectshader, UV); + const float2 UV_baseColorMap = g_xMat_uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float4 color = xBaseColorMap.Sample(sampler_objectshader, UV_baseColorMap); color.rgb = DEGAMMA(color.rgb); color *= input.color; ALPHATEST(color.a); @@ -783,15 +784,24 @@ GBUFFEROutputType_Thin main(PIXELINPUT input) #endif // SIMPLE_INPUT #ifdef NORMALMAP - NormalMapping(UV, surface.P, surface.N, TBN, bumpColor); + const float2 UV_normalMap = g_xMat_uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; + NormalMapping(UV_normalMap, surface.P, surface.N, TBN, bumpColor); #endif // NORMALMAP - float4 surface_ao_roughness_metallic_reflectance = xSurfaceMap.Sample(sampler_objectshader, UV); + const float2 UV_surfaceMap = g_xMat_uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float4 surface_ao_roughness_metallic_reflectance = xSurfaceMap.Sample(sampler_objectshader, UV_surfaceMap); + + if (g_xMat_specularGlossinessWorkflow) + { + ConvertToSpecularGlossiness(surface_ao_roughness_metallic_reflectance); + } + float4 emissiveColor; [branch] if (g_xMat_emissiveColor.a > 0) { - emissiveColor = xEmissiveMap.Sample(sampler_objectshader, UV); + const float2 UV_emissiveMap = g_xMat_uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw; + emissiveColor = xEmissiveMap.Sample(sampler_objectshader, UV_emissiveMap); emissiveColor.rgb = DEGAMMA(emissiveColor.rgb); emissiveColor *= g_xMat_emissiveColor; } @@ -800,12 +810,15 @@ GBUFFEROutputType_Thin main(PIXELINPUT input) emissiveColor = 0; } + const float2 UV_occlusionMap = g_xMat_uvset_occlusionMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float occlusion = xOcclusionMap.Sample(sampler_objectshader, UV_occlusionMap).r; + surface = CreateSurface( surface.P, surface.N, surface.V, color, - surface_ao_roughness_metallic_reflectance.r, + surface_ao_roughness_metallic_reflectance.r * occlusion, g_xMat_roughness * surface_ao_roughness_metallic_reflectance.g, g_xMat_metalness * surface_ao_roughness_metallic_reflectance.b, g_xMat_reflectance * surface_ao_roughness_metallic_reflectance.a, @@ -824,8 +837,9 @@ GBUFFEROutputType_Thin main(PIXELINPUT input) float2 bumpColor0 = 0; float2 bumpColor1 = 0; float2 bumpColor2 = 0; - bumpColor0 = 2.0f * xNormalMap.Sample(sampler_objectshader, UV - g_xMat_texMulAdd.ww).rg - 1.0f; - bumpColor1 = 2.0f * xNormalMap.Sample(sampler_objectshader, UV + g_xMat_texMulAdd.zw).rg - 1.0f; + const float2 UV_normalMap = g_xMat_uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; + bumpColor0 = 2.0f * xNormalMap.Sample(sampler_objectshader, UV_normalMap - g_xMat_texMulAdd.ww).rg - 1.0f; + bumpColor1 = 2.0f * xNormalMap.Sample(sampler_objectshader, UV_normalMap + g_xMat_texMulAdd.zw).rg - 1.0f; bumpColor2 = xWaterRipples.Sample(sampler_objectshader, ScreenCoord).rg; bumpColor = float3(bumpColor0 + bumpColor1 + bumpColor2, 1) * g_xMat_refractionIndex; surface.N = normalize(lerp(surface.N, mul(normalize(bumpColor), TBN), g_xMat_normalMapStrength)); diff --git a/WickedEngine/objectHS.hlsl b/WickedEngine/objectHS.hlsl index ad21dfbe1..9e3720002 100644 --- a/WickedEngine/objectHS.hlsl +++ b/WickedEngine/objectHS.hlsl @@ -6,7 +6,8 @@ struct HullInputType { float4 pos : POSITION; float4 color : COLOR; - float4 tex : TEXCOORD0; + float4 uvsets : UVSETS; + float4 atlas : ATLAS; float4 nor : NORMAL; float4 posPrev : POSITIONPREV; }; @@ -25,9 +26,13 @@ struct ConstantOutputType float4 color1 : COLOR1; float4 color2 : COLOR2; - float4 tex0 : TEXCOORD0; - float4 tex1 : TEXCOORD1; - float4 tex2 : TEXCOORD2; + float4 uvsets0 : UVSETS0; + float4 uvsets1 : UVSETS1; + float4 uvsets2 : UVSETS2; + + float4 atlas0 : ATLAS0; + float4 atlas1 : ATLAS1; + float4 atlas2 : ATLAS2; float4 nor0 : NORMAL0; float4 nor1 : NORMAL1; @@ -43,7 +48,8 @@ struct HullOutputType { float4 pos : POSITION; float4 color : COLOR; - float4 tex : TEXCOORD0; + float4 uvsets : UVSETS; + float4 atlas : ATLAS; float4 nor : NORMAL; float4 posPrev : POSITIONPREV; }; @@ -113,9 +119,13 @@ ConstantOutputType PatchConstantFunction(InputPatch I) Out.color1 = I[1].color; Out.color2 = I[2].color; - Out.tex0 = I[0].tex; - Out.tex1 = I[1].tex; - Out.tex2 = I[2].tex; + Out.uvsets0 = I[0].uvsets; + Out.uvsets1 = I[1].uvsets; + Out.uvsets2 = I[2].uvsets; + + Out.atlas0 = I[0].atlas; + Out.atlas1 = I[1].atlas; + Out.atlas2 = I[2].atlas; Out.nor0 = I[0].nor; Out.nor1 = I[1].nor; @@ -142,7 +152,8 @@ HullOutputType main(InputPatch patch, uint pointId : SV_Output Out.pos = patch[pointId].pos; Out.color = patch[pointId].color; - Out.tex = patch[pointId].tex; + Out.uvsets = patch[pointId].uvsets; + Out.atlas = patch[pointId].atlas; Out.nor = patch[pointId].nor; Out.posPrev = patch[pointId].posPrev; diff --git a/WickedEngine/objectInputLayoutHF.hlsli b/WickedEngine/objectInputLayoutHF.hlsli index e25b1cdd5..31614cbe2 100644 --- a/WickedEngine/objectInputLayoutHF.hlsli +++ b/WickedEngine/objectInputLayoutHF.hlsli @@ -28,13 +28,15 @@ struct Input_Object_POS struct Input_Object_POS_TEX { float4 pos : POSITION_NORMAL_SUBSETINDEX; - float2 tex : TEXCOORD0; + float2 uv0 : UVSET0; + float2 uv1 : UVSET1; Input_Instance inst; }; struct Input_Object_ALL { float4 pos : POSITION_NORMAL_SUBSETINDEX; - float2 tex : TEXCOORD; + float2 uv0 : UVSET0; + float2 uv1 : UVSET1; float2 atl : ATLAS; float4 col : COLOR; float4 pre : PREVPOS; @@ -65,7 +67,7 @@ inline float4x4 MakeWorldMatrixFromInstance(in Input_InstancePrev input) struct VertexSurface { float4 position; - float2 uv; + float4 uvsets; float2 atlas; float4 color; float3 normal; @@ -102,7 +104,7 @@ inline VertexSurface MakeVertexSurfaceFromInput(Input_Object_POS_TEX input) surface.normal.z = (float)((normal_wind_matID >> 16) & 0x000000FF) / 255.0f * 2.0f - 1.0f; surface.materialIndex = (normal_wind_matID >> 24) & 0x000000FF; - surface.uv = input.tex; + surface.uvsets = float4(input.uv0, input.uv1) * g_xMat_texMulAdd.xyxy + g_xMat_texMulAdd.zwzw; return surface; } @@ -125,7 +127,7 @@ inline VertexSurface MakeVertexSurfaceFromInput(Input_Object_ALL input) surface.normal.z = (float)((normal_wind_matID >> 16) & 0x000000FF) / 255.0f * 2.0f - 1.0f; surface.materialIndex = (normal_wind_matID >> 24) & 0x000000FF; - surface.uv = input.tex; + surface.uvsets = float4(input.uv0, input.uv1) * g_xMat_texMulAdd.xyxy + g_xMat_texMulAdd.zwzw; surface.atlas = input.atl * input.instAtlas.atlasMulAdd.xy + input.instAtlas.atlasMulAdd.zw; diff --git a/WickedEngine/objectPS_hologram.hlsl b/WickedEngine/objectPS_hologram.hlsl index ca949d417..14d66ce1b 100644 --- a/WickedEngine/objectPS_hologram.hlsl +++ b/WickedEngine/objectPS_hologram.hlsl @@ -2,9 +2,8 @@ float4 main(PixelInputType input) : SV_TARGET { - float2 UV = input.tex * g_xMat_texMulAdd.xy + g_xMat_texMulAdd.zw; - - float4 color = xBaseColorMap.Sample(sampler_objectshader, UV); + const float2 UV_baseColorMap = g_xMat_uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float4 color = xBaseColorMap.Sample(sampler_objectshader, UV_baseColorMap); color.rgb = DEGAMMA(color.rgb); color.rgb = max(color.r, max(color.g, color.b)); color *= input.color; diff --git a/WickedEngine/objectPS_voxelizer.hlsl b/WickedEngine/objectPS_voxelizer.hlsl index a3204f972..2149c5fe1 100644 --- a/WickedEngine/objectPS_voxelizer.hlsl +++ b/WickedEngine/objectPS_voxelizer.hlsl @@ -7,7 +7,7 @@ struct PSInput { float4 pos : SV_POSITION; float4 color : COLOR; - float2 tex : TEXCOORD; + float4 uvsets : UVSETS; float3 N : NORMAL; float3 P : POSITION3D; }; @@ -23,7 +23,8 @@ void main(PSInput input) [branch] if (is_saturated(uvw)) { - float4 baseColor = xBaseColorMap.Sample(sampler_linear_wrap, input.tex); + const float2 UV_baseColorMap = g_xMat_uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float4 baseColor = xBaseColorMap.Sample(sampler_linear_wrap, UV_baseColorMap); baseColor.rgb = DEGAMMA(baseColor.rgb); baseColor *= input.color; float4 color = baseColor; @@ -31,7 +32,8 @@ void main(PSInput input) [branch] if (g_xMat_emissiveColor.a > 0) { - emissiveColor = xEmissiveMap.Sample(sampler_linear_wrap, input.tex); + const float2 UV_emissiveMap = g_xMat_uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw; + emissiveColor = xEmissiveMap.Sample(sampler_linear_wrap, UV_emissiveMap); emissiveColor.rgb = DEGAMMA(emissiveColor.rgb); emissiveColor *= g_xMat_emissiveColor; } diff --git a/WickedEngine/objectVS_common.hlsl b/WickedEngine/objectVS_common.hlsl index 6a8c67ca8..cd0b1daa1 100644 --- a/WickedEngine/objectVS_common.hlsl +++ b/WickedEngine/objectVS_common.hlsl @@ -20,7 +20,7 @@ PixelInputType main(Input_Object_ALL input) Out.pos2DPrev = mul(surface.prevPos, g_xFrame_MainCamera_PrevVP); Out.pos3D = surface.position.xyz; Out.color = surface.color; - Out.tex = surface.uv; + Out.uvsets = surface.uvsets; Out.atl = surface.atlas; Out.nor = surface.normal; Out.nor2D = mul(Out.nor.xyz, (float3x3)g_xCamera_View).xy; diff --git a/WickedEngine/objectVS_common_tessellation.hlsl b/WickedEngine/objectVS_common_tessellation.hlsl index 22a05af24..f03182e85 100644 --- a/WickedEngine/objectVS_common_tessellation.hlsl +++ b/WickedEngine/objectVS_common_tessellation.hlsl @@ -5,7 +5,8 @@ struct HullInputType { float4 pos : POSITION; float4 color : COLOR; - float4 tex : TEXCOORD0; + float4 uvsets : UVSETS; + float4 atlas : ATLAS; float4 nor : NORMAL; float4 posPrev : POSITIONPREV; }; @@ -25,7 +26,8 @@ HullInputType main(Input_Object_ALL input) Out.pos = surface.position; Out.color = surface.color; - Out.tex = float4(surface.uv, surface.atlas); + Out.uvsets = surface.uvsets; + Out.atlas = surface.atlas.xyxy; Out.nor = float4(surface.normal, 1); Out.posPrev = surface.prevPos; diff --git a/WickedEngine/objectVS_simple.hlsl b/WickedEngine/objectVS_simple.hlsl index 86d6e3751..aed258164 100644 --- a/WickedEngine/objectVS_simple.hlsl +++ b/WickedEngine/objectVS_simple.hlsl @@ -13,7 +13,7 @@ PixelInputType_Simple main(Input_Object_POS_TEX input) Out.pos = mul(surface.position, g_xCamera_VP); Out.color = surface.color; - Out.tex = surface.uv; + Out.uvsets = surface.uvsets; return Out; } \ No newline at end of file diff --git a/WickedEngine/objectVS_simple_tessellation.hlsl b/WickedEngine/objectVS_simple_tessellation.hlsl index 22cb9f410..c246410d3 100644 --- a/WickedEngine/objectVS_simple_tessellation.hlsl +++ b/WickedEngine/objectVS_simple_tessellation.hlsl @@ -5,7 +5,8 @@ struct HullInputType { float4 pos : POSITION; float4 color : COLOR; - float4 tex : TEXCOORD0; + float4 uvsets : UVSETS; + float4 atlas : ATLAS; float4 nor : NORMAL; float4 posPrev : POSITIONPREV; }; @@ -23,7 +24,8 @@ HullInputType main(Input_Object_ALL input) Out.pos = surface.position; Out.color = surface.color; - Out.tex = surface.uv.xyxy; + Out.uvsets = surface.uvsets; + Out.atlas = surface.atlas.xyxy; Out.nor = float4(surface.normal, 1); // todo: leave these but I'm lazy to create appropriate hull/domain shaders now... diff --git a/WickedEngine/objectVS_voxelizer.hlsl b/WickedEngine/objectVS_voxelizer.hlsl index b2336719f..6f95a7e21 100644 --- a/WickedEngine/objectVS_voxelizer.hlsl +++ b/WickedEngine/objectVS_voxelizer.hlsl @@ -4,7 +4,7 @@ struct VSOut { float4 pos : SV_POSITION; float4 color : COLOR; - float2 tex : TEXCOORD; + float4 uvsets : UVSETS; float3 nor : NORMAL; }; @@ -17,7 +17,7 @@ VSOut main(Input_Object_POS_TEX input) Out.pos = mul(surface.position, WORLD); Out.color = surface.color; - Out.tex = surface.uv; + Out.uvsets = surface.uvsets; Out.nor = normalize(mul(surface.normal, (float3x3)WORLD)); return Out; diff --git a/WickedEngine/raySceneIntersectHF.hlsli b/WickedEngine/raySceneIntersectHF.hlsli index 1b040119f..c99b1ac38 100644 --- a/WickedEngine/raySceneIntersectHF.hlsli +++ b/WickedEngine/raySceneIntersectHF.hlsli @@ -290,28 +290,38 @@ inline float3 Shade(inout Ray ray, inout RayHit hit, inout float seed, in float2 float w = 1 - u - v; hit.N = normalize(tri.n0 * w + tri.n1 * u + tri.n2 * v); - hit.UV = tri.t0 * w + tri.t1 * u + tri.t2 * v; + hit.uvsets = tri.u0 * w + tri.u1 * u + tri.u2 * v; hit.color = tri.c0 * w + tri.c1 * u + tri.c2 * v; hit.materialIndex = tri.materialIndex; TracedRenderingMaterial mat = materialBuffer[hit.materialIndex]; - hit.UV = frac(hit.UV); // emulate wrap - float4 baseColorMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, hit.UV * mat.baseColorAtlasMulAdd.xy + mat.baseColorAtlasMulAdd.zw, 0); - float4 surfaceMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, hit.UV * mat.surfaceMapAtlasMulAdd.xy + mat.surfaceMapAtlasMulAdd.zw, 0); + hit.uvsets = frac(hit.uvsets); // emulate wrap + + const float2 UV_baseColorMap = mat.uvset_baseColorMap == 0 ? hit.uvsets.xy : hit.uvsets.zw; + float4 baseColorMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV_baseColorMap * mat.baseColorAtlasMulAdd.xy + mat.baseColorAtlasMulAdd.zw, 0); + + const float2 UV_surfaceMap = mat.uvset_surfaceMap == 0 ? hit.uvsets.xy : hit.uvsets.zw; + float4 surface_ao_roughness_metallic_reflectance = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV_surfaceMap * mat.surfaceMapAtlasMulAdd.xy + mat.surfaceMapAtlasMulAdd.zw, 0); + + if (mat.specularGlossinessWorkflow) + { + ConvertToSpecularGlossiness(surface_ao_roughness_metallic_reflectance); + } float4 baseColor = baseColorMap; baseColor.rgb = DEGAMMA(baseColor.rgb); baseColor *= hit.color; - float roughness = mat.roughness * surfaceMap.g; - float metalness = mat.metalness * surfaceMap.b; - float reflectance = mat.reflectance * surfaceMap.a; + float roughness = mat.roughness * surface_ao_roughness_metallic_reflectance.g; + float metalness = mat.metalness * surface_ao_roughness_metallic_reflectance.b; + float reflectance = mat.reflectance * surface_ao_roughness_metallic_reflectance.a; roughness = sqr(roughness); // convert linear roughness to cone aperture float4 emissiveColor; [branch] if (mat.emissiveColor.a > 0) { - emissiveColor = materialTextureAtlas.SampleLevel(sampler_linear_clamp, hit.UV * mat.emissiveMapAtlasMulAdd.xy + mat.emissiveMapAtlasMulAdd.zw, 0); + const float2 UV_emissiveMap = mat.uvset_emissiveMap == 0 ? hit.uvsets.xy : hit.uvsets.zw; + emissiveColor = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV_emissiveMap * mat.emissiveMapAtlasMulAdd.xy + mat.emissiveMapAtlasMulAdd.zw, 0); emissiveColor.rgb = DEGAMMA(emissiveColor.rgb); emissiveColor *= mat.emissiveColor; } diff --git a/WickedEngine/raytrace_lightsamplingCS.hlsl b/WickedEngine/raytrace_lightsamplingCS.hlsl index 6322850a5..4540a7992 100644 --- a/WickedEngine/raytrace_lightsamplingCS.hlsl +++ b/WickedEngine/raytrace_lightsamplingCS.hlsl @@ -43,23 +43,32 @@ void main( uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) float3 P = ray.origin; float3 N = normalize(tri.n0 * w + tri.n1 * u + tri.n2 * v); float3 V = normalize(g_xFrame_MainCamera_CamPos - P); - float2 UV = tri.t0 * w + tri.t1 * u + tri.t2 * v; + float4 uvsets = tri.u0 * w + tri.u1 * u + tri.u2 * v; uint materialIndex = tri.materialIndex; TracedRenderingMaterial mat = materialBuffer[materialIndex]; - UV = frac(UV); // emulate wrap - float4 baseColorMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV * mat.baseColorAtlasMulAdd.xy + mat.baseColorAtlasMulAdd.zw, 0); - float4 surfaceMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV * mat.surfaceMapAtlasMulAdd.xy + mat.surfaceMapAtlasMulAdd.zw, 0); + uvsets = frac(uvsets); // emulate wrap + + const float2 UV_baseColorMap = mat.uvset_baseColorMap == 0 ? uvsets.xy : uvsets.zw; + float4 baseColorMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV_baseColorMap * mat.baseColorAtlasMulAdd.xy + mat.baseColorAtlasMulAdd.zw, 0); + + const float2 UV_surfaceMap = mat.uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw; + float4 surface_ao_roughness_metallic_reflectance = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV_surfaceMap * mat.surfaceMapAtlasMulAdd.xy + mat.surfaceMapAtlasMulAdd.zw, 0); + + if (mat.specularGlossinessWorkflow) + { + ConvertToSpecularGlossiness(surface_ao_roughness_metallic_reflectance); + } float4 baseColor = baseColorMap; baseColor.rgb = DEGAMMA(baseColor.rgb); baseColor *= mat.baseColor; - float roughness = mat.roughness * surfaceMap.g; - float metalness = mat.metalness * surfaceMap.b; - float reflectance = mat.reflectance * surfaceMap.a; + float roughness = mat.roughness * surface_ao_roughness_metallic_reflectance.g; + float metalness = mat.metalness * surface_ao_roughness_metallic_reflectance.b; + float reflectance = mat.reflectance * surface_ao_roughness_metallic_reflectance.a; Surface surface = CreateSurface(P, N, V, baseColor, 1, roughness, metalness, reflectance); diff --git a/WickedEngine/shadowPS_alphatest.hlsl b/WickedEngine/shadowPS_alphatest.hlsl index 1fe044296..1430363cf 100644 --- a/WickedEngine/shadowPS_alphatest.hlsl +++ b/WickedEngine/shadowPS_alphatest.hlsl @@ -4,10 +4,10 @@ struct VertextoPixel { float4 pos : SV_POSITION; - float2 tex : TEXCOORD0; + float2 uv : UV; }; void main(VertextoPixel PSIn) { - ALPHATEST(xBaseColorMap.Sample(sampler_linear_wrap, PSIn.tex).a); + ALPHATEST(xBaseColorMap.Sample(sampler_linear_wrap, PSIn.uv).a); } \ No newline at end of file diff --git a/WickedEngine/shadowPS_transparent.hlsl b/WickedEngine/shadowPS_transparent.hlsl index 29dc9b036..8f33725a5 100644 --- a/WickedEngine/shadowPS_transparent.hlsl +++ b/WickedEngine/shadowPS_transparent.hlsl @@ -5,16 +5,15 @@ struct VertextoPixel { float4 pos : SV_POSITION; float4 color : COLOR; - float2 tex : TEXCOORD0; + float4 uvsets : UVSETS; }; float4 main(VertextoPixel input) : SV_TARGET { float2 pixel = input.pos.xy; - float2 UV = input.tex * g_xMat_texMulAdd.xy + g_xMat_texMulAdd.zw; - - float4 color = xBaseColorMap.Sample(sampler_objectshader, UV); + const float2 UV_baseColorMap = g_xMat_uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float4 color = xBaseColorMap.Sample(sampler_objectshader, UV_baseColorMap); color.rgb = DEGAMMA(color.rgb); color *= input.color; ALPHATEST(color.a); @@ -24,8 +23,9 @@ float4 main(VertextoPixel input) : SV_TARGET // Use the alpha channel for refraction caustics effect: float3 bumpColor; - - bumpColor = float3(2.0f * xNormalMap.Sample(sampler_objectshader, UV - g_xMat_texMulAdd.ww).rg - 1.0f, 1); + + const float2 UV_normalMap = g_xMat_uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; + bumpColor = float3(2.0f * xNormalMap.Sample(sampler_objectshader, UV_normalMap - g_xMat_texMulAdd.ww).rg - 1.0f, 1); bumpColor.rg *= g_xMat_refractionIndex; bumpColor.rg *= g_xMat_normalMapStrength; bumpColor = normalize(max(bumpColor, float3(0, 0, 0.0001f))); diff --git a/WickedEngine/shadowPS_water.hlsl b/WickedEngine/shadowPS_water.hlsl index b6aa00f1c..950d92a3a 100644 --- a/WickedEngine/shadowPS_water.hlsl +++ b/WickedEngine/shadowPS_water.hlsl @@ -5,16 +5,15 @@ struct VertextoPixel { float4 pos : SV_POSITION; float4 color : COLOR; - float2 tex : TEXCOORD0; + float4 uvsets : UVSETS; }; float4 main(VertextoPixel input) : SV_TARGET { float2 pixel = input.pos.xy; - float2 UV = input.tex * g_xMat_texMulAdd.xy + g_xMat_texMulAdd.zw; - - float4 color = xBaseColorMap.Sample(sampler_objectshader, UV); + const float2 UV_baseColorMap = g_xMat_uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float4 color = xBaseColorMap.Sample(sampler_objectshader, UV_baseColorMap); color.rgb = DEGAMMA(color.rgb); color *= input.color; ALPHATEST(color.a); @@ -31,8 +30,9 @@ float4 main(VertextoPixel input) : SV_TARGET float2 bumpColor0 = 0; float2 bumpColor1 = 0; float2 bumpColor2 = 0; - bumpColor0 = 2.0f * xNormalMap.Sample(sampler_objectshader, UV - g_xMat_texMulAdd.ww).rg - 1.0f; - bumpColor1 = 2.0f * xNormalMap.Sample(sampler_objectshader, UV + g_xMat_texMulAdd.zw).rg - 1.0f; + const float2 UV_normalMap = g_xMat_uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; + bumpColor0 = 2.0f * xNormalMap.Sample(sampler_objectshader, UV_normalMap - g_xMat_texMulAdd.ww).rg - 1.0f; + bumpColor1 = 2.0f * xNormalMap.Sample(sampler_objectshader, UV_normalMap + g_xMat_texMulAdd.zw).rg - 1.0f; bumpColor = float3(bumpColor0 + bumpColor1 + bumpColor2, 1) * g_xMat_refractionIndex; bumpColor.rg *= g_xMat_normalMapStrength; bumpColor = normalize(max(bumpColor, float3(0, 0, 0.0001f))); diff --git a/WickedEngine/shadowVS_alphatest.hlsl b/WickedEngine/shadowVS_alphatest.hlsl index 71087f142..70b340ced 100644 --- a/WickedEngine/shadowVS_alphatest.hlsl +++ b/WickedEngine/shadowVS_alphatest.hlsl @@ -5,7 +5,7 @@ struct VertexOut { float4 pos : SV_POSITION; - float2 tex : TEXCOORD0; + float2 uv : UV; }; VertexOut main(Input_Object_POS_TEX input) @@ -18,7 +18,7 @@ VertexOut main(Input_Object_POS_TEX input) Out.pos = mul(surface.position, WORLD); Out.pos = mul(Out.pos, g_xCamera_VP); - Out.tex = surface.uv; + Out.uv = g_xMat_uvset_baseColorMap == 0 ? surface.uvsets.xy : surface.uvsets.zw; return Out; } \ No newline at end of file diff --git a/WickedEngine/shadowVS_transparent.hlsl b/WickedEngine/shadowVS_transparent.hlsl index 6b2b1fa84..09fa411fd 100644 --- a/WickedEngine/shadowVS_transparent.hlsl +++ b/WickedEngine/shadowVS_transparent.hlsl @@ -4,7 +4,7 @@ struct VertexOut { float4 pos : SV_POSITION; float4 color : COLOR; - float2 tex : TEXCOORD0; + float4 uvsets : UVSETS; }; VertexOut main(Input_Object_POS_TEX input) @@ -18,7 +18,7 @@ VertexOut main(Input_Object_POS_TEX input) Out.pos = mul(surface.position, g_xCamera_VP); Out.color = surface.color; - Out.tex = surface.uv; + Out.uvsets = surface.uvsets; return Out; } \ No newline at end of file diff --git a/WickedEngine/tracedRenderingHF.hlsli b/WickedEngine/tracedRenderingHF.hlsli index bd79fbda8..8b11fd225 100644 --- a/WickedEngine/tracedRenderingHF.hlsli +++ b/WickedEngine/tracedRenderingHF.hlsli @@ -101,7 +101,7 @@ struct RayHit // these will only be filled when bestHit is determined to avoid recomputing them for every intersection: float3 N; - float2 UV; + float4 uvsets; uint materialIndex; float4 color; }; @@ -115,7 +115,7 @@ inline RayHit CreateRayHit() hit.bary = 0; hit.N = 0; - hit.UV = 0; + hit.uvsets = 0; hit.materialIndex = 0; hit.color = 0; diff --git a/WickedEngine/waterVS.hlsl b/WickedEngine/waterVS.hlsl index 11c8662c2..4b92aaa3c 100644 --- a/WickedEngine/waterVS.hlsl +++ b/WickedEngine/waterVS.hlsl @@ -12,7 +12,7 @@ PixelInputType main(Input_Object_POS_TEX input) Out.pos3D = surface.position.xyz; Out.pos = Out.pos2D = mul(surface.position, g_xCamera_VP); Out.color = surface.color; - Out.tex = surface.uv; + Out.uvsets = surface.uvsets; Out.nor = normalize(mul(surface.normal, (float3x3)WORLD)); Out.nor2D = mul(Out.nor.xyz, (float3x3)g_xCamera_View).xy; diff --git a/WickedEngine/wiArchive.cpp b/WickedEngine/wiArchive.cpp index 8cd6f8746..ced476bdc 100644 --- a/WickedEngine/wiArchive.cpp +++ b/WickedEngine/wiArchive.cpp @@ -7,7 +7,7 @@ using namespace std; // this should always be only INCREMENTED and only if a new serialization is implemeted somewhere! -uint64_t __archiveVersion = 27; +uint64_t __archiveVersion = 28; // this is the version number of which below the archive is not compatible with the current version uint64_t __archiveVersionBarrier = 22; diff --git a/WickedEngine/wiGPUBVH.cpp b/WickedEngine/wiGPUBVH.cpp index 56a20e0b7..9e6e3f4dd 100644 --- a/WickedEngine/wiGPUBVH.cpp +++ b/WickedEngine/wiGPUBVH.cpp @@ -152,7 +152,15 @@ void wiGPUBVH::UpdateGlobalMaterialResources(const Scene& scene, GRAPHICSTHREAD global_material.normalMapStrength = material.normalMapStrength; global_material.normalMapFlip = (material._flags & MaterialComponent::FLIP_NORMALMAP ? -1.0f : 1.0f); global_material.parallaxOcclusionMapping = material.parallaxOcclusionMapping; - global_material.g_xMat_useVertexColors = material.IsUsingVertexColors() ? 1 : 0; + global_material.displacementMapping = material.displacementMapping; + global_material.useVertexColors = material.IsUsingVertexColors() ? 1 : 0; + global_material.uvset_baseColorMap = material.uvset_baseColorMap; + global_material.uvset_surfaceMap = material.uvset_surfaceMap; + global_material.uvset_normalMap = material.uvset_normalMap; + global_material.uvset_displacementMap = material.uvset_displacementMap; + global_material.uvset_emissiveMap = material.uvset_emissiveMap; + global_material.uvset_occlusionMap = material.uvset_occlusionMap; + global_material.specularGlossinessWorkflow = material.IsUsingSpecularGlossinessWorkflow() ? 1 : 0; // Add extended properties: const TextureDesc& desc = globalMaterialAtlas.GetDesc(); @@ -477,7 +485,8 @@ void wiGPUBVH::Build(const Scene& scene, GRAPHICSTHREAD threadID) &globalMaterialBuffer, mesh.indexBuffer.get(), mesh.vertexBuffer_POS.get(), - mesh.vertexBuffer_TEX.get(), + mesh.vertexBuffer_UV0.get(), + mesh.vertexBuffer_UV1.get(), mesh.vertexBuffer_COL.get(), }; device->BindResources(CS, res, TEXSLOT_ONDEMAND0, ARRAYSIZE(res), threadID); diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index 149299efd..64d63b7a7 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -1644,15 +1644,18 @@ void RenderMeshes(const RenderQueue& renderQueue, RENDERPASS renderPass, UINT re { const GPUBuffer* vbs[] = { mesh.streamoutBuffer_POS.get() != nullptr ? mesh.streamoutBuffer_POS.get() : mesh.vertexBuffer_POS.get(), - mesh.vertexBuffer_TEX.get(), + mesh.vertexBuffer_UV0.get(), + mesh.vertexBuffer_UV1.get(), instances.buffer }; UINT strides[] = { sizeof(MeshComponent::Vertex_POS), sizeof(MeshComponent::Vertex_TEX), + sizeof(MeshComponent::Vertex_TEX), instanceDataSize }; UINT offsets[] = { + 0, 0, 0, instancedBatch.dataOffset @@ -1664,7 +1667,8 @@ void RenderMeshes(const RenderQueue& renderQueue, RENDERPASS renderPass, UINT re { const GPUBuffer* vbs[] = { mesh.streamoutBuffer_POS.get() != nullptr ? mesh.streamoutBuffer_POS.get() : mesh.vertexBuffer_POS.get(), - mesh.vertexBuffer_TEX.get(), + mesh.vertexBuffer_UV0.get(), + mesh.vertexBuffer_UV1.get(), mesh.vertexBuffer_ATL.get(), mesh.vertexBuffer_COL.get(), mesh.vertexBuffer_PRE.get() != nullptr ? mesh.vertexBuffer_PRE.get() : mesh.vertexBuffer_POS.get(), @@ -1674,6 +1678,7 @@ void RenderMeshes(const RenderQueue& renderQueue, RENDERPASS renderPass, UINT re sizeof(MeshComponent::Vertex_POS), sizeof(MeshComponent::Vertex_TEX), sizeof(MeshComponent::Vertex_TEX), + sizeof(MeshComponent::Vertex_TEX), sizeof(MeshComponent::Vertex_COL), sizeof(MeshComponent::Vertex_POS), instanceDataSize @@ -1684,6 +1689,7 @@ void RenderMeshes(const RenderQueue& renderQueue, RENDERPASS renderPass, UINT re 0, 0, 0, + 0, instancedBatch.dataOffset }; device->BindVertexBuffers(vbs, 0, ARRAYSIZE(vbs), strides, offsets, threadID); @@ -1708,12 +1714,14 @@ void RenderMeshes(const RenderQueue& renderQueue, RENDERPASS renderPass, UINT re material.GetSurfaceMap(), material.GetDisplacementMap(), material.GetEmissiveMap(), + material.GetOcclusionMap(), }; device->BindResources(PS, res, TEXSLOT_ONDEMAND0, (easyTextureBind ? 2 : ARRAYSIZE(res)), threadID); if (tessellatorRequested) { device->BindResources(DS, res, TEXSLOT_ONDEMAND0, ARRAYSIZE(res), threadID); + device->BindConstantBuffer(DS, material.constantBuffer.get(), CB_GETBINDSLOT(MaterialCB), threadID); } SetAlphaRef(material.alphaRef, threadID); @@ -1822,19 +1830,20 @@ void LoadShaders() VertexLayoutDesc layout[] = { { "POSITION_NORMAL_SUBSETINDEX", 0, MeshComponent::Vertex_POS::FORMAT, 0, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, MeshComponent::Vertex_TEX::FORMAT, 1, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, - { "ATLAS", 0, MeshComponent::Vertex_TEX::FORMAT, 2, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, - { "COLOR", 0, MeshComponent::Vertex_COL::FORMAT, 3, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, - { "PREVPOS", 0, MeshComponent::Vertex_POS::FORMAT, 4, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, + { "UVSET", 0, MeshComponent::Vertex_TEX::FORMAT, 1, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, + { "UVSET", 1, MeshComponent::Vertex_TEX::FORMAT, 2, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, + { "ATLAS", 0, MeshComponent::Vertex_TEX::FORMAT, 3, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, + { "COLOR", 0, MeshComponent::Vertex_COL::FORMAT, 4, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, + { "PREVPOS", 0, MeshComponent::Vertex_POS::FORMAT, 5, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, - { "INSTANCEMATRIX", 0, FORMAT_R32G32B32A32_FLOAT, 5, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCEMATRIX", 1, FORMAT_R32G32B32A32_FLOAT, 5, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCEMATRIX", 2, FORMAT_R32G32B32A32_FLOAT, 5, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCECOLOR", 0, FORMAT_R32G32B32A32_FLOAT, 5, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCEMATRIXPREV", 0, FORMAT_R32G32B32A32_FLOAT, 5, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCEMATRIXPREV", 1, FORMAT_R32G32B32A32_FLOAT, 5, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCEMATRIXPREV", 2, FORMAT_R32G32B32A32_FLOAT, 5, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCEATLAS", 0, FORMAT_R32G32B32A32_FLOAT, 5, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEMATRIX", 0, FORMAT_R32G32B32A32_FLOAT, 6, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEMATRIX", 1, FORMAT_R32G32B32A32_FLOAT, 6, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEMATRIX", 2, FORMAT_R32G32B32A32_FLOAT, 6, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCECOLOR", 0, FORMAT_R32G32B32A32_FLOAT, 6, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEMATRIXPREV", 0, FORMAT_R32G32B32A32_FLOAT, 6, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEMATRIXPREV", 1, FORMAT_R32G32B32A32_FLOAT, 6, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEMATRIXPREV", 2, FORMAT_R32G32B32A32_FLOAT, 6, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEATLAS", 0, FORMAT_R32G32B32A32_FLOAT, 6, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, }; vertexShaders[VSTYPE_OBJECT_COMMON] = static_cast(wiResourceManager::GetShaderManager().add(SHADERPATH + "objectVS_common.cso", wiResourceManager::VERTEXSHADER)); device->CreateInputLayout(layout, ARRAYSIZE(layout), &vertexShaders[VSTYPE_OBJECT_COMMON]->code, &vertexLayouts[VLTYPE_OBJECT_ALL]); @@ -1858,12 +1867,13 @@ void LoadShaders() VertexLayoutDesc layout[] = { { "POSITION_NORMAL_SUBSETINDEX", 0, MeshComponent::Vertex_POS::FORMAT, 0, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, MeshComponent::Vertex_TEX::FORMAT, 1, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, + { "UVSET", 0, MeshComponent::Vertex_TEX::FORMAT, 1, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, + { "UVSET", 1, MeshComponent::Vertex_TEX::FORMAT, 2, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, - { "INSTANCEMATRIX", 0, FORMAT_R32G32B32A32_FLOAT, 2, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCEMATRIX", 1, FORMAT_R32G32B32A32_FLOAT, 2, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCEMATRIX", 2, FORMAT_R32G32B32A32_FLOAT, 2, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCECOLOR", 0, FORMAT_R32G32B32A32_FLOAT, 2, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEMATRIX", 0, FORMAT_R32G32B32A32_FLOAT, 3, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEMATRIX", 1, FORMAT_R32G32B32A32_FLOAT, 3, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEMATRIX", 2, FORMAT_R32G32B32A32_FLOAT, 3, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCECOLOR", 0, FORMAT_R32G32B32A32_FLOAT, 3, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, }; vertexShaders[VSTYPE_OBJECT_SIMPLE] = static_cast(wiResourceManager::GetShaderManager().add(SHADERPATH + "objectVS_simple.cso", wiResourceManager::VERTEXSHADER)); device->CreateInputLayout(layout, ARRAYSIZE(layout), &vertexShaders[VSTYPE_OBJECT_SIMPLE]->code, &vertexLayouts[VLTYPE_OBJECT_POS_TEX]); @@ -1887,12 +1897,13 @@ void LoadShaders() VertexLayoutDesc layout[] = { { "POSITION_NORMAL_SUBSETINDEX", 0, MeshComponent::Vertex_POS::FORMAT, 0, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, MeshComponent::Vertex_TEX::FORMAT, 1, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, + { "UVSET", 0, MeshComponent::Vertex_TEX::FORMAT, 1, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, + { "UVSET", 1, MeshComponent::Vertex_TEX::FORMAT, 2, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_VERTEX_DATA, 0 }, - { "INSTANCEMATRIX", 0, FORMAT_R32G32B32A32_FLOAT, 2, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCEMATRIX", 1, FORMAT_R32G32B32A32_FLOAT, 2, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCEMATRIX", 2, FORMAT_R32G32B32A32_FLOAT, 2, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, - { "INSTANCECOLOR", 0, FORMAT_R32G32B32A32_FLOAT, 2, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEMATRIX", 0, FORMAT_R32G32B32A32_FLOAT, 3, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEMATRIX", 1, FORMAT_R32G32B32A32_FLOAT, 3, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCEMATRIX", 2, FORMAT_R32G32B32A32_FLOAT, 3, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, + { "INSTANCECOLOR", 0, FORMAT_R32G32B32A32_FLOAT, 3, VertexLayoutDesc::APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 }, }; vertexShaders[VSTYPE_SHADOW_ALPHATEST] = static_cast(wiResourceManager::GetShaderManager().add(SHADERPATH + "shadowVS_alphatest.cso", wiResourceManager::VERTEXSHADER)); device->CreateInputLayout(layout, ARRAYSIZE(layout), &vertexShaders[VSTYPE_SHADOW_ALPHATEST]->code, &vertexLayouts[VLTYPE_SHADOW_POS_TEX]); @@ -3780,7 +3791,15 @@ void UpdateRenderData(GRAPHICSTHREAD threadID) materialGPUData.g_xMat_normalMapStrength = (material.normalMap == nullptr ? 0 : material.normalMapStrength); materialGPUData.g_xMat_normalMapFlip = (material._flags & MaterialComponent::FLIP_NORMALMAP ? -1.0f : 1.0f); materialGPUData.g_xMat_parallaxOcclusionMapping = material.parallaxOcclusionMapping; + materialGPUData.g_xMat_displacementMapping = material.displacementMapping; materialGPUData.g_xMat_useVertexColors = material.IsUsingVertexColors() ? 1 : 0; + materialGPUData.g_xMat_uvset_baseColorMap = material.uvset_baseColorMap; + materialGPUData.g_xMat_uvset_surfaceMap = material.uvset_surfaceMap; + materialGPUData.g_xMat_uvset_normalMap = material.uvset_normalMap; + materialGPUData.g_xMat_uvset_displacementMap = material.uvset_displacementMap; + materialGPUData.g_xMat_uvset_emissiveMap = material.uvset_emissiveMap; + materialGPUData.g_xMat_uvset_occlusionMap = material.uvset_occlusionMap; + materialGPUData.g_xMat_specularGlossinessWorkflow = material.IsUsingSpecularGlossinessWorkflow() ? 1 : 0; device->UpdateBuffer(material.constantBuffer.get(), &materialGPUData, threadID); } @@ -6317,7 +6336,8 @@ void RefreshImpostors(GRAPHICSTHREAD threadID) const GPUBuffer* vbs[] = { mesh.IsSkinned() ? mesh.streamoutBuffer_POS.get() : mesh.vertexBuffer_POS.get(), - mesh.vertexBuffer_TEX.get(), + mesh.vertexBuffer_UV0.get(), + mesh.vertexBuffer_UV1.get(), mesh.vertexBuffer_ATL.get(), mesh.vertexBuffer_COL.get(), mesh.IsSkinned() ? mesh.streamoutBuffer_POS.get() : mesh.vertexBuffer_POS.get(), @@ -6327,6 +6347,7 @@ void RefreshImpostors(GRAPHICSTHREAD threadID) sizeof(MeshComponent::Vertex_POS), sizeof(MeshComponent::Vertex_TEX), sizeof(MeshComponent::Vertex_TEX), + sizeof(MeshComponent::Vertex_TEX), sizeof(MeshComponent::Vertex_COL), sizeof(MeshComponent::Vertex_POS), sizeof(InstBuf) @@ -6337,6 +6358,7 @@ void RefreshImpostors(GRAPHICSTHREAD threadID) 0, 0, 0, + 0, mem.offset }; device->BindVertexBuffers(vbs, 0, ARRAYSIZE(vbs), strides, offsets, threadID); diff --git a/WickedEngine/wiSceneSystem.cpp b/WickedEngine/wiSceneSystem.cpp index ff0e03c79..230ad9623 100644 --- a/WickedEngine/wiSceneSystem.cpp +++ b/WickedEngine/wiSceneSystem.cpp @@ -265,6 +265,14 @@ namespace wiSceneSystem } return wiTextureHelper::getWhite(); } + const Texture2D* MaterialComponent::GetOcclusionMap() const + { + if (occlusionMap != nullptr) + { + return occlusionMap; + } + return wiTextureHelper::getWhite(); + } void MeshComponent::CreateRenderData() { @@ -409,13 +417,13 @@ namespace wiSceneSystem assert(SUCCEEDED(hr)); } - // vertexBuffer - TEXCOORDS - if(!vertex_texcoords.empty()) + // vertexBuffer - UV SET 0 + if(!vertex_uvset_0.empty()) { - std::vector vertices(vertex_texcoords.size()); + std::vector vertices(vertex_uvset_0.size()); for (size_t i = 0; i < vertices.size(); ++i) { - vertices[i].FromFULL(vertex_texcoords[i]); + vertices[i].FromFULL(vertex_uvset_0[i]); } GPUBufferDesc bd; @@ -429,8 +437,33 @@ namespace wiSceneSystem SubresourceData InitData; InitData.pSysMem = vertices.data(); - vertexBuffer_TEX.reset(new GPUBuffer); - hr = device->CreateBuffer(&bd, &InitData, vertexBuffer_TEX.get()); + vertexBuffer_UV0.reset(new GPUBuffer); + hr = device->CreateBuffer(&bd, &InitData, vertexBuffer_UV0.get()); + assert(SUCCEEDED(hr)); + } + + // vertexBuffer - UV SET 1 + if (!vertex_uvset_1.empty()) + { + std::vector vertices(vertex_uvset_1.size()); + for (size_t i = 0; i < vertices.size(); ++i) + { + vertices[i].FromFULL(vertex_uvset_1[i]); + } + + GPUBufferDesc bd; + bd.Usage = USAGE_IMMUTABLE; + bd.CPUAccessFlags = 0; + bd.BindFlags = BIND_VERTEX_BUFFER | BIND_SHADER_RESOURCE; + bd.MiscFlags = 0; + bd.StructureByteStride = sizeof(Vertex_TEX); + bd.ByteWidth = (UINT)(bd.StructureByteStride * vertices.size()); + bd.Format = Vertex_TEX::FORMAT; + + SubresourceData InitData; + InitData.pSysMem = vertices.data(); + vertexBuffer_UV1.reset(new GPUBuffer); + hr = device->CreateBuffer(&bd, &InitData, vertexBuffer_UV1.get()); assert(SUCCEEDED(hr)); } @@ -545,7 +578,7 @@ namespace wiSceneSystem } } - // 3.) Find duplicated vertices by POSITION and TEXCOORD and SUBSET and remove them: + // 3.) Find duplicated vertices by POSITION and UV0 and UV1 and ATLAS and SUBSET and remove them: for (auto& subset : subsets) { for (uint32_t i = 0; i < subset.indexCount - 1; i++) @@ -553,7 +586,9 @@ namespace wiSceneSystem uint32_t ind0 = indices[subset.indexOffset + (uint32_t)i]; const XMFLOAT3& p0 = vertex_positions[ind0]; const XMFLOAT3& n0 = vertex_normals[ind0]; - const XMFLOAT2& t0 = vertex_texcoords[ind0]; + const XMFLOAT2& u00 = vertex_uvset_0.empty() ? XMFLOAT2(0, 0) : vertex_uvset_0[ind0]; + const XMFLOAT2& u10 = vertex_uvset_1.empty() ? XMFLOAT2(0, 0) : vertex_uvset_1[ind0]; + const XMFLOAT2& at0 = vertex_atlas.empty() ? XMFLOAT2(0, 0) : vertex_atlas[ind0]; for (uint32_t j = i + 1; j < subset.indexCount; j++) { @@ -566,18 +601,28 @@ namespace wiSceneSystem const XMFLOAT3& p1 = vertex_positions[ind1]; const XMFLOAT3& n1 = vertex_normals[ind1]; - const XMFLOAT2& t1 = vertex_texcoords[ind1]; + const XMFLOAT2& u01 = vertex_uvset_0.empty() ? XMFLOAT2(0, 0) : vertex_uvset_0[ind1]; + const XMFLOAT2& u11 = vertex_uvset_1.empty() ? XMFLOAT2(0, 0) : vertex_uvset_1[ind1]; + const XMFLOAT2& at1 = vertex_atlas.empty() ? XMFLOAT2(0, 0) : vertex_atlas[ind1]; - bool duplicated_pos = + const bool duplicated_pos = fabs(p0.x - p1.x) < FLT_EPSILON && fabs(p0.y - p1.y) < FLT_EPSILON && fabs(p0.z - p1.z) < FLT_EPSILON; - bool duplicated_tex = - fabs(t0.x - t1.x) < FLT_EPSILON && - fabs(t0.y - t1.y) < FLT_EPSILON; + const bool duplicated_uv0 = + fabs(u00.x - u01.x) < FLT_EPSILON && + fabs(u00.y - u01.y) < FLT_EPSILON; - if (duplicated_pos && duplicated_tex) + const bool duplicated_uv1 = + fabs(u10.x - u11.x) < FLT_EPSILON && + fabs(u10.y - u11.y) < FLT_EPSILON; + + const bool duplicated_atl = + fabs(at0.x - at1.x) < FLT_EPSILON && + fabs(at0.y - at1.y) < FLT_EPSILON; + + if (duplicated_pos && duplicated_uv0 && duplicated_uv1 && duplicated_atl) { // Erase vertices[ind1] because it is a duplicate: if (ind1 < vertex_positions.size()) @@ -588,9 +633,17 @@ namespace wiSceneSystem { vertex_normals.erase(vertex_normals.begin() + ind1); } - if (ind1 < vertex_texcoords.size()) + if (ind1 < vertex_uvset_0.size()) { - vertex_texcoords.erase(vertex_texcoords.begin() + ind1); + vertex_uvset_0.erase(vertex_uvset_0.begin() + ind1); + } + if (ind1 < vertex_uvset_1.size()) + { + vertex_uvset_1.erase(vertex_uvset_1.begin() + ind1); + } + if (ind1 < vertex_atlas.size()) + { + vertex_atlas.erase(vertex_atlas.begin() + ind1); } if (ind1 < vertex_boneindices.size()) { @@ -629,7 +682,9 @@ namespace wiSceneSystem std::vector newIndexBuffer; std::vector newPositionsBuffer; std::vector newNormalsBuffer; - std::vector newTexcoordsBuffer; + std::vector newUV0Buffer; + std::vector newUV1Buffer; + std::vector newAtlasBuffer; std::vector newBoneIndicesBuffer; std::vector newBoneWeightsBuffer; @@ -660,9 +715,26 @@ namespace wiSceneSystem newNormalsBuffer.push_back(normal); newNormalsBuffer.push_back(normal); - newTexcoordsBuffer.push_back(vertex_texcoords[i0]); - newTexcoordsBuffer.push_back(vertex_texcoords[i1]); - newTexcoordsBuffer.push_back(vertex_texcoords[i2]); + if (!vertex_uvset_0.empty()) + { + newUV0Buffer.push_back(vertex_uvset_0[i0]); + newUV0Buffer.push_back(vertex_uvset_0[i1]); + newUV0Buffer.push_back(vertex_uvset_0[i2]); + } + + if (!vertex_uvset_1.empty()) + { + newUV1Buffer.push_back(vertex_uvset_1[i0]); + newUV1Buffer.push_back(vertex_uvset_1[i1]); + newUV1Buffer.push_back(vertex_uvset_1[i2]); + } + + if (!vertex_atlas.empty()) + { + newAtlasBuffer.push_back(vertex_atlas[i0]); + newAtlasBuffer.push_back(vertex_atlas[i1]); + newAtlasBuffer.push_back(vertex_atlas[i2]); + } if (!vertex_boneindices.empty()) { @@ -686,7 +758,9 @@ namespace wiSceneSystem // For hard surface normals, we created a new mesh in the previous loop through faces, so swap data: vertex_positions = newPositionsBuffer; vertex_normals = newNormalsBuffer; - vertex_texcoords = newTexcoordsBuffer; + vertex_uvset_0 = newUV0Buffer; + vertex_uvset_1 = newUV1Buffer; + vertex_atlas = newAtlasBuffer; if (!vertex_boneindices.empty()) { vertex_boneindices = newBoneIndicesBuffer; diff --git a/WickedEngine/wiSceneSystem.h b/WickedEngine/wiSceneSystem.h index 9df8d29cc..3d6ccb411 100644 --- a/WickedEngine/wiSceneSystem.h +++ b/WickedEngine/wiSceneSystem.h @@ -105,6 +105,7 @@ namespace wiSceneSystem WATER = 1 << 3, FLIP_NORMALMAP = 1 << 4, USE_VERTEXCOLORS = 1 << 5, + SPECULAR_GLOSSINESS_WORKFLOW = 1 << 6, }; uint32_t _flags = DIRTY | CAST_SHADOW; @@ -122,6 +123,7 @@ namespace wiSceneSystem float subsurfaceScattering = 0.0f; float normalMapStrength = 1.0f; float parallaxOcclusionMapping = 0.0f; + float displacementMapping = 0.0f; float alphaRef = 1.0f; @@ -134,6 +136,14 @@ namespace wiSceneSystem std::string normalMapName; std::string displacementMapName; std::string emissiveMapName; + std::string occlusionMapName; + + uint32_t uvset_baseColorMap = 0; + uint32_t uvset_surfaceMap = 0; + uint32_t uvset_normalMap = 0; + uint32_t uvset_displacementMap = 0; + uint32_t uvset_emissiveMap = 0; + uint32_t uvset_occlusionMap = 0; // Non-serialized attributes: const wiGraphics::Texture2D* baseColorMap = nullptr; @@ -141,6 +151,7 @@ namespace wiSceneSystem const wiGraphics::Texture2D* normalMap = nullptr; const wiGraphics::Texture2D* displacementMap = nullptr; const wiGraphics::Texture2D* emissiveMap = nullptr; + const wiGraphics::Texture2D* occlusionMap = nullptr; std::unique_ptr constantBuffer; int customShaderID = -1; // for now, this is not serialized; need to consider actual proper use case first @@ -160,6 +171,7 @@ namespace wiSceneSystem const wiGraphics::Texture2D* GetSurfaceMap() const; const wiGraphics::Texture2D* GetDisplacementMap() const; const wiGraphics::Texture2D* GetEmissiveMap() const; + const wiGraphics::Texture2D* GetOcclusionMap() const; inline float GetOpacity() const { return baseColor.w; } inline float GetEmissiveStrength() const { return emissiveColor.w; } @@ -179,6 +191,7 @@ namespace wiSceneSystem inline bool IsAlphaTestEnabled() const { return alphaRef <= 1.0f - 1.0f / 256.0f; } inline bool IsFlipNormalMap() const { return _flags & FLIP_NORMALMAP; } inline bool IsUsingVertexColors() const { return _flags & USE_VERTEXCOLORS; } + inline bool IsUsingSpecularGlossinessWorkflow() const { return _flags & SPECULAR_GLOSSINESS_WORKFLOW; } inline bool IsCustomShader() const { return customShaderID >= 0; } inline void SetBaseColor(const XMFLOAT4& value) { SetDirty(); baseColor = value; } @@ -191,12 +204,20 @@ namespace wiSceneSystem inline void SetSubsurfaceScattering(float value) { SetDirty(); subsurfaceScattering = value; } inline void SetNormalMapStrength(float value) { SetDirty(); normalMapStrength = value; } inline void SetParallaxOcclusionMapping(float value) { SetDirty(); parallaxOcclusionMapping = value; } + inline void SetDisplacementMapping(float value) { SetDirty(); displacementMapping = value; } inline void SetOpacity(float value) { SetDirty(); baseColor.w = value; } inline void SetAlphaRef(float value) { SetDirty(); alphaRef = value; } inline void SetFlipNormalMap(bool value) { SetDirty(); if (value) { _flags |= FLIP_NORMALMAP; } else { _flags &= ~FLIP_NORMALMAP; } } - inline void SetUseVertexColors(bool value) { SetDirty(); if (value) { _flags |= USE_VERTEXCOLORS; } else { _flags &= ~USE_VERTEXCOLORS; } } + inline void SetUseVertexColors(bool value) { SetDirty(); if (value) { _flags |= USE_VERTEXCOLORS; } else { _flags &= ~USE_VERTEXCOLORS; } } + inline void SetUseSpecularGlossinessWorkflow(bool value) { SetDirty(); if (value) { _flags |= SPECULAR_GLOSSINESS_WORKFLOW; } else { _flags &= ~SPECULAR_GLOSSINESS_WORKFLOW; } } inline void SetCustomShaderID(int id) { customShaderID = id; } inline void DisableCustomShader() { customShaderID = -1; } + inline void SetUVSet_BaseColorMap(uint32_t value) { uvset_baseColorMap = value; SetDirty(); } + inline void SetUVSet_NormalMap(uint32_t value) { uvset_normalMap = value; SetDirty(); } + inline void SetUVSet_SurfaceMap(uint32_t value) { uvset_surfaceMap = value; SetDirty(); } + inline void SetUVSet_DisplacementMap(uint32_t value) { uvset_displacementMap = value; SetDirty(); } + inline void SetUVSet_EmissiveMap(uint32_t value) { uvset_emissiveMap = value; SetDirty(); } + inline void SetUVSet_OcclusionMap(uint32_t value) { uvset_occlusionMap = value; SetDirty(); } void Serialize(wiArchive& archive, uint32_t seed = 0); }; @@ -214,7 +235,8 @@ namespace wiSceneSystem std::vector vertex_positions; std::vector vertex_normals; - std::vector vertex_texcoords; + std::vector vertex_uvset_0; + std::vector vertex_uvset_1; std::vector vertex_boneindices; std::vector vertex_boneweights; std::vector vertex_atlas; @@ -236,7 +258,8 @@ namespace wiSceneSystem AABB aabb; std::unique_ptr indexBuffer; std::unique_ptr vertexBuffer_POS; - std::unique_ptr vertexBuffer_TEX; + std::unique_ptr vertexBuffer_UV0; + std::unique_ptr vertexBuffer_UV1; std::unique_ptr vertexBuffer_BON; std::unique_ptr vertexBuffer_COL; std::unique_ptr vertexBuffer_ATL; diff --git a/WickedEngine/wiSceneSystem_Serializers.cpp b/WickedEngine/wiSceneSystem_Serializers.cpp index 499368db2..0ae1482eb 100644 --- a/WickedEngine/wiSceneSystem_Serializers.cpp +++ b/WickedEngine/wiSceneSystem_Serializers.cpp @@ -113,6 +113,20 @@ namespace wiSceneSystem archive >> emissiveMapName; } + if (archive.GetVersion() >= 28) + { + archive >> occlusionMapName; + + archive >> uvset_baseColorMap; + archive >> uvset_surfaceMap; + archive >> uvset_normalMap; + archive >> uvset_displacementMap; + archive >> uvset_emissiveMap; + archive >> uvset_occlusionMap; + + archive >> displacementMapping; + } + SetDirty(); if (!baseColorMapName.empty()) @@ -135,6 +149,10 @@ namespace wiSceneSystem { emissiveMap = (wiGraphics::Texture2D*)wiResourceManager::GetGlobal().add(dir + emissiveMapName); } + if (!occlusionMapName.empty()) + { + occlusionMap = (wiGraphics::Texture2D*)wiResourceManager::GetGlobal().add(dir + occlusionMapName); + } } else @@ -207,6 +225,20 @@ namespace wiSceneSystem { archive << emissiveMapName; } + + if (archive.GetVersion() >= 28) + { + archive << occlusionMapName; + + archive << uvset_baseColorMap; + archive << uvset_surfaceMap; + archive << uvset_normalMap; + archive << uvset_displacementMap; + archive << uvset_emissiveMap; + archive << uvset_occlusionMap; + + archive << displacementMapping; + } } } void MeshComponent::Serialize(wiArchive& archive, uint32_t seed) @@ -217,7 +249,7 @@ namespace wiSceneSystem archive >> _flags; archive >> vertex_positions; archive >> vertex_normals; - archive >> vertex_texcoords; + archive >> vertex_uvset_0; archive >> vertex_boneindices; archive >> vertex_boneweights; archive >> vertex_atlas; @@ -237,6 +269,11 @@ namespace wiSceneSystem archive >> tessellationFactor; SerializeEntity(archive, armatureID, seed); + if (archive.GetVersion() >= 28) + { + archive >> vertex_uvset_1; + } + CreateRenderData(); } else @@ -244,7 +281,7 @@ namespace wiSceneSystem archive << _flags; archive << vertex_positions; archive << vertex_normals; - archive << vertex_texcoords; + archive << vertex_uvset_0; archive << vertex_boneindices; archive << vertex_boneweights; archive << vertex_atlas; @@ -262,6 +299,11 @@ namespace wiSceneSystem archive << tessellationFactor; SerializeEntity(archive, armatureID, seed); + if (archive.GetVersion() >= 28) + { + archive << vertex_uvset_1; + } + } } void ImpostorComponent::Serialize(wiArchive& archive, uint32_t seed) diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index a7dbdb443..5d1478ef5 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wiVersion // minor features, major updates const int minor = 25; // minor bug fixes, alterations, refactors, updates - const int revision = 6; + const int revision = 7; long GetVersion()