From 9bdf218ff02feaae242e4897727b00371b79abf5 Mon Sep 17 00:00:00 2001 From: seungho baek Date: Fri, 24 Feb 2023 10:40:25 +0900 Subject: [PATCH] [Tizen] Add glb-loader to load gltf2-binary - extract gltf2-util to share it in gltf2-loader and glb-loader Change-Id: I048d8903d27fea47a322dcf4d4cc56a16c10acec --- automated-tests/resources/BoxAnimated.glb | Bin 0 -> 11944 bytes .../src/dali-scene3d-internal/CMakeLists.txt | 1 + .../utc-Dali-GlbLoaderImpl.cpp | 175 +++ dali-scene3d/internal/file.list | 2 + dali-scene3d/internal/loader/dli-loader-impl.h | 4 +- dali-scene3d/internal/loader/glb-loader-impl.cpp | 149 +++ dali-scene3d/internal/loader/glb-loader-impl.h | 52 + dali-scene3d/internal/loader/gltf2-loader-impl.cpp | 1342 +------------------- dali-scene3d/internal/loader/gltf2-loader-impl.h | 9 +- dali-scene3d/internal/loader/gltf2-util.cpp | 1329 +++++++++++++++++++ dali-scene3d/internal/loader/gltf2-util.h | 135 ++ .../public-api/loader/buffer-definition.cpp | 8 + dali-scene3d/public-api/loader/buffer-definition.h | 3 + dali-scene3d/public-api/loader/model-loader.cpp | 6 + 14 files changed, 1873 insertions(+), 1342 deletions(-) create mode 100644 automated-tests/resources/BoxAnimated.glb create mode 100644 automated-tests/src/dali-scene3d-internal/utc-Dali-GlbLoaderImpl.cpp create mode 100644 dali-scene3d/internal/loader/glb-loader-impl.cpp create mode 100644 dali-scene3d/internal/loader/glb-loader-impl.h create mode 100644 dali-scene3d/internal/loader/gltf2-util.cpp create mode 100644 dali-scene3d/internal/loader/gltf2-util.h diff --git a/automated-tests/resources/BoxAnimated.glb b/automated-tests/resources/BoxAnimated.glb new file mode 100644 index 0000000000000000000000000000000000000000..69481ec3caeb57428c806fdac08bfbe40d19ff55 GIT binary patch literal 11944 zcmds5Yjm7t5gvpBf{KC)hzcwS0%_R$CDz@R($+#~pe;o$6}IWNT}iSr*%mNHqNpgS zsEDYjpr{}ymjafH-KZ!R5fM>QQTfe(&+(Vw^UTh>JDY5>yB_s;%sJC}=6&b#%)IaS zeG5BAHm-h!h}_mM5Bh!76$zeE02BL;0BLkbKB4d?pC89)Mbj$RXzJ@&&8QxhQ z*)~x^g3U2(OjN4Hs^;6gG}<5SbN|Hp5!=@i5!Z#$(&SDp*hO&NH%1PGN@^-HK2aVm zSIfK2ELN)%<*mD_G^Zl#hSs06VlXlg>x-N_v|;VWwL|M7195a(9@|zPrVn}@MT3!n zBtWrRnkW}Xu&Rv~N3jl94Dh0@mCG=pF3TZTvs}uTOSCPgJ&o1NRlwcKq0!=48LTkW z`dJL`EP~BQ-Iy$nj*paZuIQm!oY+x6ZypzV?2C*Ss~BzbRHjM45rc`$jX@fVsZJEf zCP(UrIn7a6SiNp}Y`;*YS#Z4nzjmu3NQYJ#H47P@fEInt!$n|J<}*vEne! zX0kF-ueWvA_U)yKP36+1+UnNJtEHjs+x2>b@^EEzyfRiAt8Tn}9F3=9am;wQvTFj+cujgVT3|G`=!>^3+QJI%5lS$9X7f&wYKF2q)Sn7m5DS>D* z+nkb32;a}Jn@dEoKYiSVCyJc0b{&32%1l#cW?>e%$iKsPt->$N*1tQ~9KSrQcpTQK z|9(Jf7NU7NVeTVj#{9a$1&d*wZZNrYlUIFu~ha` z!kzYQc7dy}ANmY?f#X8!K6XKze(H$XE~MamI$`bypSgI}@`Qcb?Qpv_@5}w*(+P+D zc6!$GaIY=KqoQZZbL$S*J-hR5JpVKGD18@nz>WNR{w~fon(q^EFrT35(iQHy=acrk zy@sT-(0Pi-PwalFw&m(OxL%us^H{IBb^56f*9>di+?#Jyu`yQEYsepEh}hTFRL@Y@PYF27S0@c|6t}j0yb` zyL%O|#)cl5$N3P?GS1eCwXUsu%zX0j150Q39=uH7S>0fJH_tG(6L!A^7W&S%tdj>h zFmac-VEzLCj+l&h;#>91lJoT4-VL^ysDGEZ;O4m~29H^CV831Bg82)3=!DsCr!(?C zHgoN@OU#)E|BjfoZ078_BX;*^!QWus$8r|j`F6y;%m?{r*e~x|H`x6iC4cRidLe9Q zs=2$wyg&5kedvhk*Gs>SC!f>6e?iRbYL>c7oOk}c;5_mN|HW|N zL!WMO!SU~e$szdkf&*v9E{4fD_;kdDNB@?;{-$NK)Lr75^X~=MkU#h@h65k^bc+j) zeSkLEh&;R|!Sct}TgX!L5 z5C2R4|JnI;^Um}>z0b0@``~IV_zjxlqi2gXzVmy*%pd$0#4nz74}a^{j=y_P9$0@r z6W@099{nA=`R!kM~y-st~r0^)!)*rW4K1_>IR(WK5LT? zxj0;7ZRTfghdm#UTWYzw%&qy6tH3%GV#hxZthLZfo;>Ko9G8}nE0=C9cN z3*>J8irJI#&>Vr2pRL36Y2h7W$(6at-C=(g?5jo}?)#kN&2{p!nX_;9tn*?|HuC~~ z9HtLvsB?0-W;yBavCfj5S&wl6yP3D#B|k&M%FAl$yj|UJb_Mom{VHZ1)3D418+r z$<94s>X!MMf4F<>g>zxd+UB=2Q;MY56(-TBvWl)OJ|; zT5RCcvPKRR#5C@|=WMk!)@o_I!#X4D*FPh9my?)tQ-0)4o4FmgyzO(3@7Knh&0o*X z{6p`JevPND8-Mj@uE5o_a&bQ7z%|B_gNktov4N-hI6j&y#6Sl=`h2nnyQ^H+bL3eP z^Q^f?AA6QH7qPpyEqA~@FX6ce&ki|joQuu7`$imUI)BDFOux2$xtv-%%-`y2oV%Yb&%2+qo1uSSdF;>8NN_ndbu z%-nNmIT!alwww{~5$^!SVZC4MmtyP0^i%D8yNZLqe^-PUpObay6P{(YttS1s9%=_1 z;uqYXn-6yAm$4xq*VN{|`2?G_@?P5H415}XmM6r9UNooW;u>n*jGm8M4vfD#UUUBF za*_Sy6)O8f?~lC$WQiQ8auD=Eun(43${{L;LLUnIRq|?ijmlxrhrxcWyiN{RIRg3! z*sqs2$QxCTggz4XQF64rN#z*mV_?5o-Xd>RITrd@*vHB75>Yt;`UKb~%G=~5m6M@Q zhW&OqMNU;Y4f-_LeX>;gRZfRK9d=Y=5|@M|B_(OeNLF$(An%Z6@=nR)_?OEHIYU;; znX*b&%Nlu?td+A=&W1i4_Mn_2>r{rIhhU#8=gE4N4bU54ZNEJfL>G7LQ|7t1y&$##_;&^uu7l(M{EljnFs3{+fJUzM*mx^i8l&!4tSrg{O4}>?L?>oTkF(^HkX9z6196ykaReRH><+MJ3Ap@?-gl{8Z%;=tp4xOnxqpsyqh$80^R83HgP}lh9AX z{-r!6zfyS``f1p|mS^NQD$hbc3;Vb7JNdoJbI{Mh{)7Bc{-p9e^z*R)EPs)|s=NUG H0_=YRA*={7 literal 0 HcmV?d00001 diff --git a/automated-tests/src/dali-scene3d-internal/CMakeLists.txt b/automated-tests/src/dali-scene3d-internal/CMakeLists.txt index 0109332..a7a419e 100755 --- a/automated-tests/src/dali-scene3d-internal/CMakeLists.txt +++ b/automated-tests/src/dali-scene3d-internal/CMakeLists.txt @@ -8,6 +8,7 @@ SET(CAPI_LIB "dali-scene3d") # List of test case sources (Only these get parsed for test cases) SET(TC_SOURCES utc-Dali-DliLoaderImpl.cpp + utc-Dali-GlbLoaderImpl.cpp utc-Dali-Gltf2Asset.cpp utc-Dali-Gltf2LoaderImpl.cpp utc-Dali-Hash.cpp diff --git a/automated-tests/src/dali-scene3d-internal/utc-Dali-GlbLoaderImpl.cpp b/automated-tests/src/dali-scene3d-internal/utc-Dali-GlbLoaderImpl.cpp new file mode 100644 index 0000000..f3d8a93 --- /dev/null +++ b/automated-tests/src/dali-scene3d-internal/utc-Dali-GlbLoaderImpl.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// Enable debug log for test coverage +#define DEBUG_ENABLED 1 + +#include +#include +#include +#include +#include +#include +#include + +using namespace Dali; +using namespace Dali::Scene3D::Loader; + +#define DALI_TEST_THROW(expression, exception, predicate) \ + { \ + bool daliTestThrowSuccess__ = false; \ + try \ + { \ + do \ + { \ + expression; \ + } while(0); \ + printf("No exception was thrown.\n"); \ + } \ + catch(std::decay::type & ex) \ + { \ + daliTestThrowSuccess__ = predicate(ex); \ + } \ + catch(...) \ + { \ + printf("Wrong type of exception thrown.\n"); \ + } \ + DALI_TEST_CHECK(daliTestThrowSuccess__); \ + } + +namespace +{ +struct Context +{ + ResourceBundle::PathProvider pathProvider = [](ResourceType::Value type) { + return TEST_RESOURCE_DIR "/"; + }; + + ResourceBundle resources; + SceneDefinition scene; + SceneMetadata metaData; + + std::vector animations; + std::vector animationGroups; + std::vector cameras; + std::vector lights; + + LoadResult loadResult{ + resources, + scene, + metaData, + animations, + animationGroups, + cameras, + lights}; + + Dali::Scene3D::Loader::Internal::GlbLoaderImpl loader; +}; + +struct ExceptionMessageStartsWith +{ + const std::string_view expected; + + bool operator()(const std::runtime_error& e) + { + const bool success = (0 == strncmp(e.what(), expected.data(), expected.size())); + if(!success) + { + printf("Expected: %s, got: %s.\n", expected.data(), e.what()); + } + return success; + } +}; + +} // namespace + +int UtcDaliGlbLoaderFailedToLoad(void) +{ + Context ctx; + + DALI_TEST_EQUAL(ctx.loader.LoadModel("non-existent.glb", ctx.loadResult), false); + + DALI_TEST_EQUAL(0, ctx.scene.GetRoots().size()); + DALI_TEST_EQUAL(0, ctx.scene.GetNodeCount()); + + DALI_TEST_EQUAL(0, ctx.resources.mEnvironmentMaps.size()); + DALI_TEST_EQUAL(0, ctx.resources.mMaterials.size()); + DALI_TEST_EQUAL(0, ctx.resources.mMeshes.size()); + DALI_TEST_EQUAL(0, ctx.resources.mShaders.size()); + DALI_TEST_EQUAL(0, ctx.resources.mSkeletons.size()); + + DALI_TEST_EQUAL(0, ctx.cameras.size()); + DALI_TEST_EQUAL(0, ctx.lights.size()); + DALI_TEST_EQUAL(0, ctx.animations.size()); + DALI_TEST_EQUAL(0, ctx.animationGroups.size()); + + END_TEST; +} + +int UtcDaliGlbLoaderFailedToParse(void) +{ + Context ctx; + + ShaderDefinitionFactory sdf; + sdf.SetResources(ctx.resources); + + DALI_TEST_EQUAL(ctx.loader.LoadModel(TEST_RESOURCE_DIR "/invalid.glb", ctx.loadResult), false); + + DALI_TEST_EQUAL(0, ctx.scene.GetRoots().size()); + DALI_TEST_EQUAL(0, ctx.scene.GetNodeCount()); + + DALI_TEST_EQUAL(0, ctx.resources.mEnvironmentMaps.size()); + DALI_TEST_EQUAL(0, ctx.resources.mMaterials.size()); + DALI_TEST_EQUAL(0, ctx.resources.mMeshes.size()); + DALI_TEST_EQUAL(0, ctx.resources.mShaders.size()); + DALI_TEST_EQUAL(0, ctx.resources.mSkeletons.size()); + + DALI_TEST_EQUAL(0, ctx.cameras.size()); + DALI_TEST_EQUAL(0, ctx.lights.size()); + DALI_TEST_EQUAL(0, ctx.animations.size()); + DALI_TEST_EQUAL(0, ctx.animationGroups.size()); + + END_TEST; +} + +int UtcDaliGlbLoaderSuccess1(void) +{ + Context ctx; + ShaderDefinitionFactory sdf; + sdf.SetResources(ctx.resources); + + ctx.loader.LoadModel(TEST_RESOURCE_DIR "/BoxAnimated.glb", ctx.loadResult); + + DALI_TEST_EQUAL(1u, ctx.scene.GetRoots().size()); + DALI_TEST_EQUAL(5u, ctx.scene.GetNodeCount()); + + TestApplication app; + + Customization::Choices choices; + for(auto iRoot : ctx.scene.GetRoots()) + { + auto resourceRefs = ctx.resources.CreateRefCounter(); + ctx.scene.CountResourceRefs(iRoot, choices, resourceRefs); + ctx.resources.mReferenceCounts = std::move(resourceRefs); + ctx.resources.LoadResources(ctx.pathProvider); + } + + DALI_TEST_EQUAL(true, ctx.resources.mMeshes[0u].first.mPositions.IsDefined()); + DALI_TEST_EQUAL(1152, ctx.resources.mMeshes[0u].first.mPositions.mBlob.mLength); + + END_TEST; +} diff --git a/dali-scene3d/internal/file.list b/dali-scene3d/internal/file.list index 6503cd8..cff1bca 100644 --- a/dali-scene3d/internal/file.list +++ b/dali-scene3d/internal/file.list @@ -8,7 +8,9 @@ set(scene3d_src_files ${scene3d_src_files} ${scene3d_internal_dir}/controls/scene-view/scene-view-impl.cpp ${scene3d_internal_dir}/loader/dli-loader-impl.cpp ${scene3d_internal_dir}/loader/gltf2-asset.cpp + ${scene3d_internal_dir}/loader/gltf2-util.cpp ${scene3d_internal_dir}/loader/gltf2-loader-impl.cpp + ${scene3d_internal_dir}/loader/glb-loader-impl.cpp ${scene3d_internal_dir}/loader/hash.cpp ${scene3d_internal_dir}/loader/json-reader.cpp ${scene3d_internal_dir}/loader/json-util.cpp diff --git a/dali-scene3d/internal/loader/dli-loader-impl.h b/dali-scene3d/internal/loader/dli-loader-impl.h index a68432c..ed0af39 100644 --- a/dali-scene3d/internal/loader/dli-loader-impl.h +++ b/dali-scene3d/internal/loader/dli-loader-impl.h @@ -28,7 +28,7 @@ #include // EXTERNAL INCLUDES -#include "dali/public-api/common/vector-wrapper.h" +#include namespace Dali { @@ -62,7 +62,7 @@ public: void SetErrorCallback(StringCallback onError); /** - * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl() + * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl::LoadMode() */ bool LoadModel(const std::string& uri, Dali::Scene3D::Loader::LoadResult& result) override; diff --git a/dali-scene3d/internal/loader/glb-loader-impl.cpp b/dali-scene3d/internal/loader/glb-loader-impl.cpp new file mode 100644 index 0000000..0bfae86 --- /dev/null +++ b/dali-scene3d/internal/loader/glb-loader-impl.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// FILE HEADER +#include + +// EXTERNAL INCLUDES +#include +#include + +// INTERNAL INCLUDES +#include +#include +#include + +namespace gt = gltf2; +namespace js = json; + +namespace Dali +{ +namespace Scene3D +{ +namespace Loader +{ +namespace Internal +{ +namespace +{ +static constexpr uint32_t GLB_MAGIC = 0x46546C67; +static constexpr uint32_t JSON_CHUNK_TYPE = 0x4E4F534A; +static constexpr uint32_t DATA_CHUNK_TYPE = 0x004E4942; + +struct GlbHeader +{ + uint32_t magic; + uint32_t version; + uint32_t length; +}; + +struct ChunkHeader +{ + uint32_t chunkLength; + uint32_t chunkType; +}; + +} // namespace + +bool GlbLoaderImpl::LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result) +{ + Dali::FileStream fileStream(url, FileStream::READ | FileStream::BINARY); + auto& stream = fileStream.GetStream(); + if(!stream.rdbuf()->in_avail()) + { + DALI_LOG_ERROR("Load Model file is failed, url : %s\n", url.c_str()); + return false; + } + + GlbHeader glbHeader; + stream.clear(); + stream.seekg(0u, stream.beg); + stream.read(reinterpret_cast(&glbHeader), sizeof(GlbHeader)); + + if(glbHeader.magic != GLB_MAGIC) + { + DALI_LOG_ERROR("Wrong file format, url : %s\n", url.c_str()); + return false; + } + + ChunkHeader jsonChunkHeader; + stream.read(reinterpret_cast(&jsonChunkHeader), sizeof(ChunkHeader)); + + if(jsonChunkHeader.chunkType != JSON_CHUNK_TYPE) + { + DALI_LOG_ERROR("Glb files first chunk is not a json chunk.\n"); + return false; + } + + std::vector jsonChunkData; + jsonChunkData.resize(jsonChunkHeader.chunkLength); + stream.read(reinterpret_cast(&jsonChunkData[0]), jsonChunkHeader.chunkLength); + std::string gltfText(jsonChunkData.begin(), jsonChunkData.end()); + + uint32_t binaryChunkOffset = sizeof(GlbHeader) + sizeof(ChunkHeader) + jsonChunkHeader.chunkLength; + std::vector binaryChunkData; + if(glbHeader.length > binaryChunkOffset) + { + ChunkHeader binaryChunkHeader; + stream.read(reinterpret_cast(&binaryChunkHeader), sizeof(ChunkHeader)); + + if(binaryChunkHeader.chunkType != DATA_CHUNK_TYPE) + { + DALI_LOG_ERROR("Glb files has wrong binary chunk data.\n"); + return false; + } + + binaryChunkData.resize(binaryChunkHeader.chunkLength); + stream.read(reinterpret_cast(&binaryChunkData[0]), binaryChunkHeader.chunkLength); + } + + json::unique_ptr root(json_parse(gltfText.c_str(), gltfText.size())); + if(!root) + { + DALI_LOG_ERROR("Failed to parse %s\n", url.c_str()); + return false; + } + + gt::Document document; + + bool isMRendererModel(false); + if(!Gltf2Util::GenerateDocument(root, document, isMRendererModel)) + { + DALI_LOG_ERROR("Failed to parse %s\n", url.c_str()); + return false; + } + + auto path = url.substr(0, url.rfind('/') + 1); + Gltf2Util::ConversionContext context{result, path, INVALID_INDEX}; + + auto& outBuffers = context.mOutput.mResources.mBuffers; + outBuffers.reserve(document.mBuffers.size()); + if(!binaryChunkData.empty()) + { + BufferDefinition dataBuffer(binaryChunkData); + outBuffers.emplace_back(std::move(dataBuffer)); + } + + Gltf2Util::ConvertGltfToContext(document, context,isMRendererModel); + + return true; +} + +} // namespace Internal +} // namespace Loader +} // namespace Scene3D +} // namespace Dali diff --git a/dali-scene3d/internal/loader/glb-loader-impl.h b/dali-scene3d/internal/loader/glb-loader-impl.h new file mode 100644 index 0000000..4e9607b --- /dev/null +++ b/dali-scene3d/internal/loader/glb-loader-impl.h @@ -0,0 +1,52 @@ +#ifndef DALI_SCENE3D_LOADER_GLB_LOADER_IMPL_H +#define DALI_SCENE3D_LOADER_GLB_LOADER_IMPL_H +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// INTERNAL INCLUDES +#include +#include + +// EXTERNAL INCLUDES +#include +#include + +namespace Dali +{ +namespace Scene3D +{ +namespace Loader +{ +namespace Internal +{ + +class GlbLoaderImpl : public ModelLoaderImpl +{ +public: + + /** + * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl::LoadMode() + */ + bool LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result) override; +}; + +} // namespace Internal +} // namespace Loader +} // namespace Scene3D +} // namespace Dali + +#endif // DALI_SCENE3D_LOADER_GLB_LOADER_IMPL_H diff --git a/dali-scene3d/internal/loader/gltf2-loader-impl.cpp b/dali-scene3d/internal/loader/gltf2-loader-impl.cpp index 324d5ad..ae08709 100644 --- a/dali-scene3d/internal/loader/gltf2-loader-impl.cpp +++ b/dali-scene3d/internal/loader/gltf2-loader-impl.cpp @@ -20,16 +20,10 @@ // EXTERNAL INCLUDES #include -#include -#include -#include // INTERNAL INCLUDES -#include +#include #include -#include -#include -#include #include namespace gt = gltf2; @@ -43,1352 +37,36 @@ namespace Loader { namespace Internal { -namespace -{ - -const std::string POSITION_PROPERTY("position"); -const std::string ORIENTATION_PROPERTY("orientation"); -const std::string SCALE_PROPERTY("scale"); -const std::string BLEND_SHAPE_WEIGHTS_UNIFORM("uBlendShapeWeight"); -const std::string MRENDERER_MODEL_IDENTIFICATION("M-Renderer"); -const std::string ROOT_NODE_NAME("RootNode"); -const Vector3 SCALE_TO_ADJUST(100.0f, 100.0f, 100.0f); - -const Geometry::Type GLTF2_TO_DALI_PRIMITIVES[]{ - Geometry::POINTS, - Geometry::LINES, - Geometry::LINE_LOOP, - Geometry::LINE_STRIP, - Geometry::TRIANGLES, - Geometry::TRIANGLE_STRIP, - Geometry::TRIANGLE_FAN}; //...because Dali swaps the last two. - -struct AttributeMapping -{ - gt::Attribute::Type mType; - MeshDefinition::Accessor MeshDefinition::*mAccessor; - uint16_t mElementSizeRequired; -} ATTRIBUTE_MAPPINGS[]{ - {gt::Attribute::NORMAL, &MeshDefinition::mNormals, sizeof(Vector3)}, - {gt::Attribute::TANGENT, &MeshDefinition::mTangents, sizeof(Vector3)}, - {gt::Attribute::TEXCOORD_0, &MeshDefinition::mTexCoords, sizeof(Vector2)}, - {gt::Attribute::COLOR_0, &MeshDefinition::mColors, sizeof(Vector4)}, - {gt::Attribute::JOINTS_0, &MeshDefinition::mJoints0, sizeof(Vector4)}, - {gt::Attribute::WEIGHTS_0, &MeshDefinition::mWeights0, sizeof(Vector4)}, -}; - -std::vector ReadAnimationArray(const json_value_s& j) -{ - auto results = js::Read::Array::Read>(j); - - for(auto& animation : results) - { - for(auto& channel : animation.mChannels) - { - channel.mSampler.UpdateVector(animation.mSamplers); - } - } - - return results; -} - -void ApplyAccessorMinMax(const gt::Accessor& acc, float* values) -{ - DALI_ASSERT_ALWAYS(acc.mMax.empty() || gt::AccessorType::ElementCount(acc.mType) == acc.mMax.size()); - DALI_ASSERT_ALWAYS(acc.mMin.empty() || gt::AccessorType::ElementCount(acc.mType) == acc.mMin.size()); - MeshDefinition::Blob::ApplyMinMax(acc.mMin, acc.mMax, acc.mCount, values); -} - -const auto BUFFER_READER = std::move(js::Reader() - .Register(*js::MakeProperty("byteLength", js::Read::Number, >::Buffer::mByteLength)) - .Register(*js::MakeProperty("uri", js::Read::StringView, >::Buffer::mUri))); - -const auto BUFFER_VIEW_READER = std::move(js::Reader() - .Register(*js::MakeProperty("buffer", gt::RefReader::Read, >::BufferView::mBuffer)) - .Register(*js::MakeProperty("byteOffset", js::Read::Number, >::BufferView::mByteOffset)) - .Register(*js::MakeProperty("byteLength", js::Read::Number, >::BufferView::mByteLength)) - .Register(*js::MakeProperty("byteStride", js::Read::Number, >::BufferView::mByteStride)) - .Register(*js::MakeProperty("target", js::Read::Number, >::BufferView::mTarget))); - -const auto BUFFER_VIEW_CLIENT_READER = std::move(js::Reader() - .Register(*js::MakeProperty("bufferView", gt::RefReader::Read, >::BufferViewClient::mBufferView)) - .Register(*js::MakeProperty("byteOffset", js::Read::Number, >::BufferViewClient::mByteOffset))); - -const auto COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER = std::move(js::Reader() - .Register(*new js::Property>("bufferView", gt::RefReader::Read, >::ComponentTypedBufferViewClient::mBufferView)) - .Register(*new js::Property("byteOffset", js::Read::Number, >::ComponentTypedBufferViewClient::mByteOffset)) - .Register(*js::MakeProperty("componentType", js::Read::Enum, >::ComponentTypedBufferViewClient::mComponentType))); - -const auto ACCESSOR_SPARSE_READER = std::move(js::Reader() - .Register(*js::MakeProperty("count", js::Read::Number, >::Accessor::Sparse::mCount)) - .Register(*js::MakeProperty("indices", js::ObjectReader::Read, >::Accessor::Sparse::mIndices)) - .Register(*js::MakeProperty("values", js::ObjectReader::Read, >::Accessor::Sparse::mValues))); - -const auto ACCESSOR_READER = std::move(js::Reader() - .Register(*new js::Property>("bufferView", - gt::RefReader::Read, - >::Accessor::mBufferView)) - .Register(*new js::Property("byteOffset", - js::Read::Number, - >::Accessor::mByteOffset)) - .Register(*new js::Property("componentType", - js::Read::Enum, - >::Accessor::mComponentType)) - .Register(*new js::Property("name", js::Read::StringView, >::Accessor::mName)) - .Register(*js::MakeProperty("count", js::Read::Number, >::Accessor::mCount)) - .Register(*js::MakeProperty("normalized", js::Read::Boolean, >::Accessor::mNormalized)) - .Register(*js::MakeProperty("type", gt::ReadStringEnum, >::Accessor::mType)) - .Register(*js::MakeProperty("min", js::Read::Array, >::Accessor::mMin)) - .Register(*js::MakeProperty("max", js::Read::Array, >::Accessor::mMax)) - .Register(*new js::Property("sparse", js::ObjectReader::Read, >::Accessor::SetSparse))); - -const auto IMAGE_READER = std::move(js::Reader() - .Register(*new js::Property("name", js::Read::StringView, >::Material::mName)) - .Register(*js::MakeProperty("uri", js::Read::StringView, >::Image::mUri)) - .Register(*js::MakeProperty("mimeType", js::Read::StringView, >::Image::mMimeType)) - .Register(*js::MakeProperty("bufferView", gt::RefReader::Read, >::Image::mBufferView))); - -const auto SAMPLER_READER = std::move(js::Reader() - .Register(*js::MakeProperty("minFilter", js::Read::Enum, >::Sampler::mMinFilter)) - .Register(*js::MakeProperty("magFilter", js::Read::Enum, >::Sampler::mMagFilter)) - .Register(*js::MakeProperty("wrapS", js::Read::Enum, >::Sampler::mWrapS)) - .Register(*js::MakeProperty("wrapT", js::Read::Enum, >::Sampler::mWrapT))); - -const auto TEXURE_READER = std::move(js::Reader() - .Register(*js::MakeProperty("source", gt::RefReader::Read, >::Texture::mSource)) - .Register(*js::MakeProperty("sampler", gt::RefReader::Read, >::Texture::mSampler))); - -const auto TEXURE_INFO_READER = std::move(js::Reader() - .Register(*js::MakeProperty("index", gt::RefReader::Read, >::TextureInfo::mTexture)) - .Register(*js::MakeProperty("texCoord", js::Read::Number, >::TextureInfo::mTexCoord)) - .Register(*js::MakeProperty("scale", js::Read::Number, >::TextureInfo::mScale)) - .Register(*js::MakeProperty("strength", js::Read::Number, >::TextureInfo::mStrength))); - -const auto MATERIAL_PBR_READER = std::move(js::Reader() - .Register(*js::MakeProperty("baseColorFactor", gt::ReadDaliVector, >::Material::Pbr::mBaseColorFactor)) - .Register(*js::MakeProperty("baseColorTexture", js::ObjectReader::Read, >::Material::Pbr::mBaseColorTexture)) - .Register(*js::MakeProperty("metallicFactor", js::Read::Number, >::Material::Pbr::mMetallicFactor)) - .Register(*js::MakeProperty("roughnessFactor", js::Read::Number, >::Material::Pbr::mRoughnessFactor)) - .Register(*js::MakeProperty("metallicRoughnessTexture", js::ObjectReader::Read, >::Material::Pbr::mMetallicRoughnessTexture))); - -const auto MATERIAL_SPECULAR_READER = std::move(js::Reader() - .Register(*js::MakeProperty("specularFactor", js::Read::Number, >::MaterialSpecular::mSpecularFactor)) - .Register(*js::MakeProperty("specularTexture", js::ObjectReader::Read, >::MaterialSpecular::mSpecularTexture)) - .Register(*js::MakeProperty("specularColorFactor", gt::ReadDaliVector, >::MaterialSpecular::mSpecularColorFactor)) - .Register(*js::MakeProperty("specularColorTexture", js::ObjectReader::Read, >::MaterialSpecular::mSpecularColorTexture))); - -const auto MATERIAL_IOR_READER = std::move(js::Reader() - .Register(*js::MakeProperty("ior", js::Read::Number, >::MaterialIor::mIor))); - -const auto MATERIAL_EXTENSION_READER = std::move(js::Reader() - .Register(*js::MakeProperty("KHR_materials_ior", js::ObjectReader::Read, >::MaterialExtensions::mMaterialIor)) - .Register(*js::MakeProperty("KHR_materials_specular", js::ObjectReader::Read, >::MaterialExtensions::mMaterialSpecular))); - -const auto MATERIAL_READER = std::move(js::Reader() - .Register(*new js::Property("name", js::Read::StringView, >::Material::mName)) - .Register(*js::MakeProperty("pbrMetallicRoughness", js::ObjectReader::Read, >::Material::mPbrMetallicRoughness)) - .Register(*js::MakeProperty("normalTexture", js::ObjectReader::Read, >::Material::mNormalTexture)) - .Register(*js::MakeProperty("occlusionTexture", js::ObjectReader::Read, >::Material::mOcclusionTexture)) - .Register(*js::MakeProperty("emissiveTexture", js::ObjectReader::Read, >::Material::mEmissiveTexture)) - .Register(*js::MakeProperty("emissiveFactor", gt::ReadDaliVector, >::Material::mEmissiveFactor)) - .Register(*js::MakeProperty("alphaMode", gt::ReadStringEnum, >::Material::mAlphaMode)) - .Register(*js::MakeProperty("alphaCutoff", js::Read::Number, >::Material::mAlphaCutoff)) - .Register(*js::MakeProperty("doubleSided", js::Read::Boolean, >::Material::mDoubleSided)) - .Register(*js::MakeProperty("extensions", js::ObjectReader::Read, >::Material::mMaterialExtensions))); - -std::map> ReadMeshPrimitiveAttributes(const json_value_s& j) -{ - auto& jo = js::Cast(j); - std::map> result; - - auto i = jo.start; - while(i) - { - auto jstr = *i->name; - result[gt::Attribute::FromString(jstr.string, jstr.string_size)] = gt::RefReader::Read(*i->value); - i = i->next; - } - return result; -} - -std::vector>> ReadMeshPrimitiveTargets(const json_value_s& j) -{ - auto& jo = js::Cast(j); - std::vector>> result; - - result.reserve(jo.length); - - auto i = jo.start; - while(i) - { - result.push_back(std::move(ReadMeshPrimitiveAttributes(*i->value))); - i = i->next; - } - - return result; -} - -const auto MESH_PRIMITIVE_READER = std::move(js::Reader() - .Register(*js::MakeProperty("attributes", ReadMeshPrimitiveAttributes, >::Mesh::Primitive::mAttributes)) - .Register(*js::MakeProperty("indices", gt::RefReader::Read, >::Mesh::Primitive::mIndices)) - .Register(*js::MakeProperty("material", gt::RefReader::Read, >::Mesh::Primitive::mMaterial)) - .Register(*js::MakeProperty("mode", js::Read::Enum, >::Mesh::Primitive::mMode)) - .Register(*js::MakeProperty("targets", ReadMeshPrimitiveTargets, >::Mesh::Primitive::mTargets))); - -const auto MESH_READER = std::move(js::Reader() - .Register(*new js::Property("name", js::Read::StringView, >::Mesh::mName)) - .Register(*js::MakeProperty("primitives", - js::Read::Array::Read>, - >::Mesh::mPrimitives)) - .Register(*js::MakeProperty("weights", js::Read::Array, >::Mesh::mWeights))); - -const auto SKIN_READER = std::move(js::Reader() - .Register(*new js::Property("name", js::Read::StringView, >::Skin::mName)) - .Register(*js::MakeProperty("inverseBindMatrices", - gt::RefReader::Read, - >::Skin::mInverseBindMatrices)) - .Register(*js::MakeProperty("skeleton", - gt::RefReader::Read, - >::Skin::mSkeleton)) - .Register(*js::MakeProperty("joints", - js::Read::Array, gt::RefReader::Read>, - >::Skin::mJoints))); - -const auto CAMERA_PERSPECTIVE_READER = std::move(js::Reader() - .Register(*js::MakeProperty("aspectRatio", js::Read::Number, >::Camera::Perspective::mAspectRatio)) - .Register(*js::MakeProperty("yfov", js::Read::Number, >::Camera::Perspective::mYFov)) - .Register(*js::MakeProperty("zfar", js::Read::Number, >::Camera::Perspective::mZFar)) - .Register(*js::MakeProperty("znear", js::Read::Number, >::Camera::Perspective::mZNear))); // TODO: infinite perspective projection, where znear is omitted - -const auto CAMERA_ORTHOGRAPHIC_READER = std::move(js::Reader() - .Register(*js::MakeProperty("xmag", js::Read::Number, >::Camera::Orthographic::mXMag)) - .Register(*js::MakeProperty("ymag", js::Read::Number, >::Camera::Orthographic::mYMag)) - .Register(*js::MakeProperty("zfar", js::Read::Number, >::Camera::Orthographic::mZFar)) - .Register(*js::MakeProperty("znear", js::Read::Number, >::Camera::Orthographic::mZNear))); - -const auto CAMERA_READER = std::move(js::Reader() - .Register(*new js::Property("name", js::Read::StringView, >::Camera::mName)) - .Register(*js::MakeProperty("type", js::Read::StringView, >::Camera::mType)) - .Register(*js::MakeProperty("perspective", js::ObjectReader::Read, >::Camera::mPerspective)) - .Register(*js::MakeProperty("orthographic", js::ObjectReader::Read, >::Camera::mOrthographic))); - -const auto NODE_READER = std::move(js::Reader() - .Register(*new js::Property("name", js::Read::StringView, >::Node::mName)) - .Register(*js::MakeProperty("translation", gt::ReadDaliVector, >::Node::mTranslation)) - .Register(*js::MakeProperty("rotation", gt::ReadQuaternion, >::Node::mRotation)) - .Register(*js::MakeProperty("scale", gt::ReadDaliVector, >::Node::mScale)) - .Register(*new js::Property("matrix", gt::ReadDaliVector, >::Node::SetMatrix)) - .Register(*js::MakeProperty("camera", gt::RefReader::Read, >::Node::mCamera)) - .Register(*js::MakeProperty("children", js::Read::Array, gt::RefReader::Read>, >::Node::mChildren)) - .Register(*js::MakeProperty("mesh", gt::RefReader::Read, >::Node::mMesh)) - .Register(*js::MakeProperty("skin", gt::RefReader::Read, >::Node::mSkin))); - -const auto ANIMATION_SAMPLER_READER = std::move(js::Reader() - .Register(*js::MakeProperty("input", gt::RefReader::Read, >::Animation::Sampler::mInput)) - .Register(*js::MakeProperty("output", gt::RefReader::Read, >::Animation::Sampler::mOutput)) - .Register(*js::MakeProperty("interpolation", gt::ReadStringEnum, >::Animation::Sampler::mInterpolation))); - -const auto ANIMATION_TARGET_READER = std::move(js::Reader() - .Register(*js::MakeProperty("node", gt::RefReader::Read, >::Animation::Channel::Target::mNode)) - .Register(*js::MakeProperty("path", gt::ReadStringEnum, >::Animation::Channel::Target::mPath))); - -const auto ANIMATION_CHANNEL_READER = std::move(js::Reader() - .Register(*js::MakeProperty("target", js::ObjectReader::Read, >::Animation::Channel::mTarget)) - .Register(*js::MakeProperty("sampler", gt::RefReader::Read, >::Animation::Channel::mSampler))); - -const auto ANIMATION_READER = std::move(js::Reader() - .Register(*new js::Property("name", js::Read::StringView, >::Animation::mName)) - .Register(*js::MakeProperty("samplers", - js::Read::Array::Read>, - >::Animation::mSamplers)) - .Register(*js::MakeProperty("channels", - js::Read::Array::Read>, - >::Animation::mChannels))); - -const auto SCENE_READER = std::move(js::Reader() - .Register(*new js::Property("name", js::Read::StringView, >::Scene::mName)) - .Register(*js::MakeProperty("nodes", - js::Read::Array, gt::RefReader::Read>, - >::Scene::mNodes))); - -const auto DOCUMENT_READER = std::move(js::Reader() - .Register(*js::MakeProperty("buffers", - js::Read::Array::Read>, - >::Document::mBuffers)) - .Register(*js::MakeProperty("bufferViews", - js::Read::Array::Read>, - >::Document::mBufferViews)) - .Register(*js::MakeProperty("accessors", - js::Read::Array::Read>, - >::Document::mAccessors)) - .Register(*js::MakeProperty("images", - js::Read::Array::Read>, - >::Document::mImages)) - .Register(*js::MakeProperty("samplers", - js::Read::Array::Read>, - >::Document::mSamplers)) - .Register(*js::MakeProperty("textures", - js::Read::Array::Read>, - >::Document::mTextures)) - .Register(*js::MakeProperty("materials", - js::Read::Array::Read>, - >::Document::mMaterials)) - .Register(*js::MakeProperty("meshes", - js::Read::Array::Read>, - >::Document::mMeshes)) - .Register(*js::MakeProperty("skins", - js::Read::Array::Read>, - >::Document::mSkins)) - .Register(*js::MakeProperty("cameras", - js::Read::Array::Read>, - >::Document::mCameras)) - .Register(*js::MakeProperty("nodes", - js::Read::Array::Read>, - >::Document::mNodes)) - .Register(*js::MakeProperty("animations", - ReadAnimationArray, - >::Document::mAnimations)) - .Register(*js::MakeProperty("scenes", - js::Read::Array::Read>, - >::Document::mScenes)) - .Register(*js::MakeProperty("scene", gt::RefReader::Read, >::Document::mScene))); - -struct NodeMapping -{ - Index gltfIdx; - Index runtimeIdx; -}; - -bool operator<(const NodeMapping& mapping, Index gltfIdx) -{ - return mapping.gltfIdx < gltfIdx; -} - -class NodeIndexMapper -{ -public: - NodeIndexMapper() = default; - NodeIndexMapper(const NodeIndexMapper&) = delete; - NodeIndexMapper& operator=(const NodeIndexMapper&) = delete; - - ///@brief Registers a mapping of the @a gltfIdx of a node to its @a runtimeIdx . - ///@note If the indices are the same, the registration is omitted, in order to - /// save growing a vector. - void RegisterMapping(Index gltfIdx, Index runtimeIdx) - { - if(gltfIdx != runtimeIdx) - { - auto iInsert = std::lower_bound(mNodes.begin(), mNodes.end(), gltfIdx); - DALI_ASSERT_DEBUG(iInsert == mNodes.end() || iInsert->gltfIdx != gltfIdx); - mNodes.insert(iInsert, NodeMapping{gltfIdx, runtimeIdx}); - } - } - - ///@brief Retrieves the runtime index of a Node, mapped to the given @a gltfIdx. - Index GetRuntimeId(Index gltfIdx) const - { - auto iFind = std::lower_bound(mNodes.begin(), mNodes.end(), gltfIdx); // using custom operator< - return (iFind != mNodes.end() && iFind->gltfIdx == gltfIdx) ? iFind->runtimeIdx : gltfIdx; - } - -private: - std::vector mNodes; -}; - -struct ConversionContext -{ - LoadResult& mOutput; - - std::string mPath; - Index mDefaultMaterial; - - std::vector mMeshIds; - NodeIndexMapper mNodeIndices; -}; - -void ConvertBuffer(const gt::Buffer& buffer, decltype(ResourceBundle::mBuffers)& outBuffers, const std::string& resourcePath) -{ - BufferDefinition bufferDefinition; - - bufferDefinition.mResourcePath = resourcePath; - bufferDefinition.mUri = buffer.mUri; - bufferDefinition.mByteLength = buffer.mByteLength; - - outBuffers.emplace_back(std::move(bufferDefinition)); -} - -void ConvertBuffers(const gt::Document& doc, ConversionContext& context) -{ - auto& outBuffers = context.mOutput.mResources.mBuffers; - outBuffers.reserve(doc.mBuffers.size()); - - for(auto& buffer : doc.mBuffers) - { - ConvertBuffer(buffer, outBuffers, context.mPath); - } -} - -SamplerFlags::Type ConvertWrapMode(gt::Wrap::Type wrapMode) -{ - switch(wrapMode) - { - case gt::Wrap::REPEAT: - return SamplerFlags::WRAP_REPEAT; - case gt::Wrap::CLAMP_TO_EDGE: - return SamplerFlags::WRAP_CLAMP; - case gt::Wrap::MIRRORED_REPEAT: - return SamplerFlags::WRAP_MIRROR; - default: - throw std::runtime_error("Invalid wrap type."); - } -} - -SamplerFlags::Type ConvertSampler(const gt::Ref& sampler) -{ - if(sampler) - { - return ((sampler->mMinFilter < gt::Filter::NEAREST_MIPMAP_NEAREST) ? (sampler->mMinFilter - gt::Filter::NEAREST) : ((sampler->mMinFilter - gt::Filter::NEAREST_MIPMAP_NEAREST) + 2)) | - ((sampler->mMagFilter - gt::Filter::NEAREST) << SamplerFlags::FILTER_MAG_SHIFT) | - (ConvertWrapMode(sampler->mWrapS) << SamplerFlags::WRAP_S_SHIFT) | - (ConvertWrapMode(sampler->mWrapT) << SamplerFlags::WRAP_T_SHIFT); - } - else - { - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#texturesampler - // "The index of the sampler used by this texture. When undefined, a sampler with repeat wrapping and auto filtering should be used." - // "What is an auto filtering", I hear you ask. Since there's nothing else to determine mipmapping from - including glTF image - // properties, if not in some extension -, we will simply assume linear filtering. - return SamplerFlags::FILTER_LINEAR | (SamplerFlags::FILTER_LINEAR << SamplerFlags::FILTER_MAG_SHIFT) | - (SamplerFlags::WRAP_REPEAT << SamplerFlags::WRAP_S_SHIFT) | (SamplerFlags::WRAP_REPEAT << SamplerFlags::WRAP_T_SHIFT); - } -} - -TextureDefinition ConvertTextureInfo(const gt::TextureInfo& mm, ConversionContext& context, const ImageMetadata& metaData = ImageMetadata()) -{ - TextureDefinition textureDefinition; - std::string uri = std::string(mm.mTexture->mSource->mUri); - if(uri.empty()) - { - uint32_t bufferIndex = mm.mTexture->mSource->mBufferView->mBuffer.GetIndex(); - if(bufferIndex != INVALID_INDEX && context.mOutput.mResources.mBuffers[bufferIndex].IsAvailable()) - { - auto& stream = context.mOutput.mResources.mBuffers[bufferIndex].GetBufferStream(); - stream.clear(); - stream.seekg(mm.mTexture->mSource->mBufferView->mByteOffset, stream.beg); - std::vector dataBuffer; - dataBuffer.resize(mm.mTexture->mSource->mBufferView->mByteLength); - stream.read(reinterpret_cast(dataBuffer.data()), static_cast(static_cast(mm.mTexture->mSource->mBufferView->mByteLength))); - return TextureDefinition{std::move(dataBuffer), ConvertSampler(mm.mTexture->mSampler), metaData.mMinSize, metaData.mSamplingMode}; - } - return TextureDefinition(); - } - else - { - return TextureDefinition{uri, ConvertSampler(mm.mTexture->mSampler), metaData.mMinSize, metaData.mSamplingMode}; - } -} - -void ConvertMaterial(const gt::Material& material, const std::unordered_map& imageMetaData, decltype(ResourceBundle::mMaterials)& outMaterials, ConversionContext& context) -{ - auto getTextureMetaData = [](const std::unordered_map& metaData, const gt::TextureInfo& info) - { - if(!info.mTexture->mSource->mUri.empty()) - { - if(auto search = metaData.find(info.mTexture->mSource->mUri.data()); search != metaData.end()) - { - return search->second; - } - } - return ImageMetadata(); - }; - - MaterialDefinition matDef; - - auto& pbr = material.mPbrMetallicRoughness; - if(material.mAlphaMode == gt::AlphaMode::BLEND) - { - matDef.mIsOpaque = false; - matDef.mFlags |= MaterialDefinition::TRANSPARENCY; - } - else if(material.mAlphaMode == gt::AlphaMode::MASK) - { - matDef.mIsMask = true; - matDef.SetAlphaCutoff(std::min(1.f, std::max(0.f, material.mAlphaCutoff))); - } - - matDef.mBaseColorFactor = pbr.mBaseColorFactor; - - matDef.mTextureStages.reserve(!!pbr.mBaseColorTexture + !!pbr.mMetallicRoughnessTexture + !!material.mNormalTexture + !!material.mOcclusionTexture + !!material.mEmissiveTexture); - if(pbr.mBaseColorTexture) - { - const auto semantic = MaterialDefinition::ALBEDO; - matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(pbr.mBaseColorTexture, context, getTextureMetaData(imageMetaData, pbr.mBaseColorTexture))}); - // TODO: and there had better be one - matDef.mFlags |= semantic; - } - else - { - matDef.mNeedAlbedoTexture = false; - } - - matDef.mMetallic = pbr.mMetallicFactor; - matDef.mRoughness = pbr.mRoughnessFactor; - - if(pbr.mMetallicRoughnessTexture) - { - const auto semantic = MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS | - MaterialDefinition::GLTF_CHANNELS; - matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(pbr.mMetallicRoughnessTexture, context, getTextureMetaData(imageMetaData, pbr.mMetallicRoughnessTexture))}); - // TODO: and there had better be one - matDef.mFlags |= semantic; - } - else - { - matDef.mNeedMetallicRoughnessTexture = false; - } - - matDef.mNormalScale = material.mNormalTexture.mScale; - if(material.mNormalTexture) - { - const auto semantic = MaterialDefinition::NORMAL; - matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mNormalTexture, context, getTextureMetaData(imageMetaData, material.mNormalTexture))}); - // TODO: and there had better be one - matDef.mFlags |= semantic; - } - else - { - matDef.mNeedNormalTexture = false; - } - - if(material.mOcclusionTexture) - { - const auto semantic = MaterialDefinition::OCCLUSION; - matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mOcclusionTexture, context, getTextureMetaData(imageMetaData, material.mOcclusionTexture))}); - // TODO: and there had better be one - matDef.mFlags |= semantic; - matDef.mOcclusionStrength = material.mOcclusionTexture.mStrength; - } - - if(material.mEmissiveTexture) - { - const auto semantic = MaterialDefinition::EMISSIVE; - matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mEmissiveTexture, context, getTextureMetaData(imageMetaData, material.mEmissiveTexture))}); - // TODO: and there had better be one - matDef.mFlags |= semantic; - matDef.mEmissiveFactor = material.mEmissiveFactor; - } - - if(!Dali::Equals(material.mMaterialExtensions.mMaterialIor.mIor, gltf2::UNDEFINED_FLOAT_VALUE)) - { - float ior = material.mMaterialExtensions.mMaterialIor.mIor; - matDef.mDielectricSpecular = powf((ior - 1.0f) / (ior + 1.0f), 2.0f); - } - matDef.mSpecularFactor = material.mMaterialExtensions.mMaterialSpecular.mSpecularFactor; - matDef.mSpecularColorFactor = material.mMaterialExtensions.mMaterialSpecular.mSpecularColorFactor; - - if(material.mMaterialExtensions.mMaterialSpecular.mSpecularTexture) - { - const auto semantic = MaterialDefinition::SPECULAR; - matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mMaterialExtensions.mMaterialSpecular.mSpecularTexture, context, getTextureMetaData(imageMetaData, material.mMaterialExtensions.mMaterialSpecular.mSpecularTexture))}); - matDef.mFlags |= semantic; - } - - if(material.mMaterialExtensions.mMaterialSpecular.mSpecularColorTexture) - { - const auto semantic = MaterialDefinition::SPECULAR_COLOR; - matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mMaterialExtensions.mMaterialSpecular.mSpecularColorTexture, context, getTextureMetaData(imageMetaData, material.mMaterialExtensions.mMaterialSpecular.mSpecularColorTexture))}); - matDef.mFlags |= semantic; - } - - matDef.mDoubleSided = material.mDoubleSided; - - outMaterials.emplace_back(std::move(matDef), TextureSet()); -} - -void ConvertMaterials(const gt::Document& doc, ConversionContext& context) -{ - auto& imageMetaData = context.mOutput.mSceneMetadata.mImageMetadata; - - auto& outMaterials = context.mOutput.mResources.mMaterials; - outMaterials.reserve(doc.mMaterials.size()); - - for(auto& m : doc.mMaterials) - { - ConvertMaterial(m, imageMetaData, outMaterials, context); - } -} - -MeshDefinition::Accessor ConvertMeshPrimitiveAccessor(const gt::Accessor& acc) -{ - DALI_ASSERT_ALWAYS((acc.mBufferView && - (acc.mBufferView->mByteStride < std::numeric_limits::max())) || - (acc.mSparse && !acc.mBufferView)); - - DALI_ASSERT_ALWAYS(!acc.mSparse || - ((acc.mSparse->mIndices.mBufferView && (acc.mSparse->mIndices.mBufferView->mByteStride < std::numeric_limits::max())) && - (acc.mSparse->mValues.mBufferView && (acc.mSparse->mValues.mBufferView->mByteStride < std::numeric_limits::max())))); - - MeshDefinition::SparseBlob sparseBlob; - if(acc.mSparse) - { - const gt::Accessor::Sparse& sparse = *acc.mSparse; - const gt::ComponentTypedBufferViewClient& indices = sparse.mIndices; - const gt::BufferViewClient& values = sparse.mValues; - - MeshDefinition::Blob indicesBlob( - indices.mBufferView->mByteOffset + indices.mByteOffset, - sparse.mCount * indices.GetBytesPerComponent(), - static_cast(indices.mBufferView->mByteStride), - static_cast(indices.GetBytesPerComponent()), - {}, - {}); - MeshDefinition::Blob valuesBlob( - values.mBufferView->mByteOffset + values.mByteOffset, - sparse.mCount * acc.GetElementSizeBytes(), - static_cast(values.mBufferView->mByteStride), - static_cast(acc.GetElementSizeBytes()), - {}, - {}); - - sparseBlob = std::move(MeshDefinition::SparseBlob(std::move(indicesBlob), std::move(valuesBlob), acc.mSparse->mCount)); - } - - uint32_t bufferViewOffset = 0u; - uint32_t bufferViewStride = 0u; - if(acc.mBufferView) - { - bufferViewOffset = acc.mBufferView->mByteOffset; - bufferViewStride = acc.mBufferView->mByteStride; - } - - return MeshDefinition::Accessor{ - std::move(MeshDefinition::Blob{bufferViewOffset + acc.mByteOffset, - acc.GetBytesLength(), - static_cast(bufferViewStride), - static_cast(acc.GetElementSizeBytes()), - acc.mMin, - acc.mMax}), - std::move(sparseBlob), - acc.mBufferView ? acc.mBufferView->mBuffer.GetIndex() : 0}; -} - -void ConvertMeshes(const gt::Document& doc, ConversionContext& context) -{ - uint32_t meshCount = 0; - context.mMeshIds.reserve(doc.mMeshes.size()); - for(auto& mesh : doc.mMeshes) - { - context.mMeshIds.push_back(meshCount); - meshCount += mesh.mPrimitives.size(); - } - - auto& outMeshes = context.mOutput.mResources.mMeshes; - outMeshes.reserve(meshCount); - for(auto& mesh : doc.mMeshes) - { - for(auto& primitive : mesh.mPrimitives) - { - MeshDefinition meshDefinition; - - auto& attribs = primitive.mAttributes; - meshDefinition.mPrimitiveType = GLTF2_TO_DALI_PRIMITIVES[primitive.mMode]; - - auto& accPositions = *attribs.find(gt::Attribute::POSITION)->second; - meshDefinition.mPositions = ConvertMeshPrimitiveAccessor(accPositions); - // glTF2 support vector4 tangent for mesh. - // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#meshes-overview - meshDefinition.mTangentType = Property::VECTOR4; - - const bool needNormalsTangents = accPositions.mType == gt::AccessorType::VEC3; - for(auto& am : ATTRIBUTE_MAPPINGS) - { - auto iFind = attribs.find(am.mType); - if(iFind != attribs.end()) - { - auto& accessor = meshDefinition.*(am.mAccessor); - accessor = ConvertMeshPrimitiveAccessor(*iFind->second); - - if(iFind->first == gt::Attribute::JOINTS_0) - { - meshDefinition.mFlags |= (iFind->second->mComponentType == gt::Component::UNSIGNED_SHORT) * MeshDefinition::U16_JOINT_IDS; - meshDefinition.mFlags |= (iFind->second->mComponentType == gt::Component::UNSIGNED_BYTE) * MeshDefinition::U8_JOINT_IDS; - DALI_ASSERT_DEBUG(MaskMatch(meshDefinition.mFlags, MeshDefinition::U16_JOINT_IDS) || MaskMatch(meshDefinition.mFlags, MeshDefinition::U8_JOINT_IDS) || iFind->second->mComponentType == gt::Component::FLOAT); - } - } - else if(needNormalsTangents) - { - switch(am.mType) - { - case gt::Attribute::NORMAL: - meshDefinition.RequestNormals(); - break; - - case gt::Attribute::TANGENT: - meshDefinition.RequestTangents(); - break; - - default: - break; - } - } - } - - if(primitive.mIndices) - { - meshDefinition.mIndices = ConvertMeshPrimitiveAccessor(*primitive.mIndices); - meshDefinition.mFlags |= (primitive.mIndices->mComponentType == gt::Component::UNSIGNED_INT) * MeshDefinition::U32_INDICES; - meshDefinition.mFlags |= (primitive.mIndices->mComponentType == gt::Component::UNSIGNED_BYTE) * MeshDefinition::U8_INDICES; - DALI_ASSERT_DEBUG(MaskMatch(meshDefinition.mFlags, MeshDefinition::U32_INDICES) || MaskMatch(meshDefinition.mFlags, MeshDefinition::U8_INDICES) || primitive.mIndices->mComponentType == gt::Component::UNSIGNED_SHORT); - } - - if(!primitive.mTargets.empty()) - { - meshDefinition.mBlendShapes.reserve(primitive.mTargets.size()); - meshDefinition.mBlendShapeVersion = BlendShapes::Version::VERSION_2_0; - for(const auto& target : primitive.mTargets) - { - MeshDefinition::BlendShape blendShape; - - auto endIt = target.end(); - auto it = target.find(gt::Attribute::POSITION); - if(it != endIt) - { - blendShape.deltas = ConvertMeshPrimitiveAccessor(*it->second); - } - it = target.find(gt::Attribute::NORMAL); - if(it != endIt) - { - blendShape.normals = ConvertMeshPrimitiveAccessor(*it->second); - } - it = target.find(gt::Attribute::TANGENT); - if(it != endIt) - { - blendShape.tangents = ConvertMeshPrimitiveAccessor(*it->second); - } - - if(!mesh.mWeights.empty()) - { - blendShape.weight = mesh.mWeights[meshDefinition.mBlendShapes.size()]; - } - - meshDefinition.mBlendShapes.push_back(std::move(blendShape)); - } - } - - outMeshes.push_back({std::move(meshDefinition), MeshGeometry{}}); - } - } -} - -ModelRenderable* MakeModelRenderable(const gt::Mesh::Primitive& prim, ConversionContext& context) -{ - auto modelRenderable = new ModelRenderable(); - - modelRenderable->mShaderIdx = 0; // TODO: further thought - - auto materialIdx = prim.mMaterial.GetIndex(); - if(INVALID_INDEX == materialIdx) - { - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#default-material - if(INVALID_INDEX == context.mDefaultMaterial) - { - auto& outMaterials = context.mOutput.mResources.mMaterials; - context.mDefaultMaterial = outMaterials.size(); - - ConvertMaterial(gt::Material{}, context.mOutput.mSceneMetadata.mImageMetadata, outMaterials, context); - } - - materialIdx = context.mDefaultMaterial; - } - - modelRenderable->mMaterialIdx = materialIdx; - - return modelRenderable; -} - -void ConvertCamera(const gt::Camera& camera, CameraParameters& camParams) -{ - camParams.isPerspective = camera.mType.compare("perspective") == 0; - if(camParams.isPerspective) - { - auto& perspective = camera.mPerspective; - if(!Dali::Equals(perspective.mYFov, gltf2::UNDEFINED_FLOAT_VALUE)) - { - camParams.yFovDegree = Degree(Radian(perspective.mYFov)); - } - else - { - camParams.yFovDegree = Degree(gltf2::UNDEFINED_FLOAT_VALUE); - } - camParams.zNear = perspective.mZNear; - camParams.zFar = perspective.mZFar; - // TODO: yes, we seem to ignore aspectRatio in CameraParameters. - } - else - { - auto& ortho = camera.mOrthographic; - if(!Dali::Equals(ortho.mYMag, gltf2::UNDEFINED_FLOAT_VALUE) && !Dali::Equals(ortho.mXMag, gltf2::UNDEFINED_FLOAT_VALUE)) - { - camParams.orthographicSize = ortho.mYMag * .5f; - camParams.aspectRatio = ortho.mXMag / ortho.mYMag; - } - else - { - camParams.orthographicSize = gltf2::UNDEFINED_FLOAT_VALUE; - camParams.aspectRatio = gltf2::UNDEFINED_FLOAT_VALUE; - } - camParams.zNear = ortho.mZNear; - camParams.zFar = ortho.mZFar; - } -} - -void ConvertNode(gt::Node const& node, const Index gltfIdx, Index parentIdx, ConversionContext& context, bool isMRendererModel) -{ - auto& output = context.mOutput; - auto& scene = output.mScene; - auto& resources = output.mResources; - - const auto idx = scene.GetNodeCount(); - auto weakNode = scene.AddNode([&]() - { - std::unique_ptr nodeDef{new NodeDefinition()}; - - nodeDef->mParentIdx = parentIdx; - nodeDef->mName = node.mName; - if(nodeDef->mName.empty()) - { - // TODO: Production quality generation of unique names. - nodeDef->mName = std::to_string(reinterpret_cast(nodeDef.get())); - } - - if(!node.mSkin) // Nodes with skinned meshes are not supposed to have local transforms. - { - nodeDef->mPosition = node.mTranslation; - nodeDef->mOrientation = node.mRotation; - nodeDef->mScale = node.mScale; - - if(isMRendererModel && node.mName == ROOT_NODE_NAME && node.mScale == SCALE_TO_ADJUST) - { - nodeDef->mScale *= 0.01f; - } - } - - return nodeDef; }()); - if(!weakNode) - { - ExceptionFlinger(ASSERT_LOCATION) << "Node name '" << node.mName << "' is not unique; scene is invalid."; - } - - context.mNodeIndices.RegisterMapping(gltfIdx, idx); - - Index skeletonIdx = node.mSkin ? node.mSkin.GetIndex() : INVALID_INDEX; - if(node.mMesh) - { - auto& mesh = *node.mMesh; - uint32_t primitiveCount = mesh.mPrimitives.size(); - auto meshIdx = context.mMeshIds[node.mMesh.GetIndex()]; - weakNode->mRenderables.reserve(primitiveCount); - for(uint32_t i = 0; i < primitiveCount; ++i) - { - std::unique_ptr renderable; - auto modelRenderable = MakeModelRenderable(mesh.mPrimitives[i], context); - modelRenderable->mMeshIdx = meshIdx + i; - - DALI_ASSERT_DEBUG(resources.mMeshes[modelRenderable->mMeshIdx].first.mSkeletonIdx == INVALID_INDEX || - resources.mMeshes[modelRenderable->mMeshIdx].first.mSkeletonIdx == skeletonIdx); - resources.mMeshes[modelRenderable->mMeshIdx].first.mSkeletonIdx = skeletonIdx; - - renderable.reset(modelRenderable); - weakNode->mRenderables.push_back(std::move(renderable)); - } - } - - if(node.mCamera) - { - CameraParameters camParams; - ConvertCamera(*node.mCamera, camParams); - - camParams.matrix.SetTransformComponents(node.mScale, node.mRotation, node.mTranslation); - output.mCameraParameters.push_back(camParams); - } - - for(auto& n : node.mChildren) - { - ConvertNode(*n, n.GetIndex(), idx, context, isMRendererModel); - } -} - -void ConvertSceneNodes(const gt::Scene& scene, ConversionContext& context, bool isMRendererModel) -{ - auto& outScene = context.mOutput.mScene; - Index rootIdx = outScene.GetNodeCount(); - switch(scene.mNodes.size()) - { - case 0: - break; - - case 1: - ConvertNode(*scene.mNodes[0], scene.mNodes[0].GetIndex(), INVALID_INDEX, context, isMRendererModel); - outScene.AddRootNode(rootIdx); - break; - - default: - { - std::unique_ptr sceneRoot{new NodeDefinition()}; - sceneRoot->mName = "GLTF_LOADER_SCENE_ROOT_" + std::to_string(outScene.GetRoots().size()); - - outScene.AddNode(std::move(sceneRoot)); - outScene.AddRootNode(rootIdx); - - for(auto& n : scene.mNodes) - { - ConvertNode(*n, n.GetIndex(), rootIdx, context, isMRendererModel); - } - break; - } - } -} - -void ConvertNodes(const gt::Document& doc, ConversionContext& context, bool isMRendererModel) -{ - if(!doc.mScenes.empty()) - { - uint32_t rootSceneIndex = 0u; - if(doc.mScene) - { - rootSceneIndex = doc.mScene.GetIndex(); - } - ConvertSceneNodes(doc.mScenes[rootSceneIndex], context, isMRendererModel); - - for(uint32_t i = 0, i1 = rootSceneIndex; i < i1; ++i) - { - ConvertSceneNodes(doc.mScenes[i], context, isMRendererModel); - } - - for(uint32_t i = rootSceneIndex + 1; i < doc.mScenes.size(); ++i) - { - ConvertSceneNodes(doc.mScenes[i], context, isMRendererModel); - } - } -} - -template -void LoadDataFromAccessor(ConversionContext& context, uint32_t bufferIndex, Vector& dataBuffer, uint32_t offset, uint32_t size) -{ - if(bufferIndex >= context.mOutput.mResources.mBuffers.size()) - { - DALI_LOG_ERROR("Invailid buffer index\n"); - return; - } - - auto& buffer = context.mOutput.mResources.mBuffers[bufferIndex]; - if(!buffer.IsAvailable()) - { - DALI_LOG_ERROR("Failed to load from buffer stream.\n"); - } - auto& stream = buffer.GetBufferStream(); - stream.clear(); - stream.seekg(offset, stream.beg); - stream.read(reinterpret_cast(dataBuffer.Begin()), static_cast(static_cast(size))); -} - -template -float LoadDataFromAccessors(ConversionContext& context, const gltf2::Accessor& input, const gltf2::Accessor& output, Vector& inputDataBuffer, Vector& outputDataBuffer) -{ - inputDataBuffer.Resize(input.mCount); - outputDataBuffer.Resize(output.mCount); - - const uint32_t inputDataBufferSize = input.GetBytesLength(); - const uint32_t outputDataBufferSize = output.GetBytesLength(); - - LoadDataFromAccessor(context, output.mBufferView->mBuffer.GetIndex(), inputDataBuffer, input.mBufferView->mByteOffset + input.mByteOffset, inputDataBufferSize); - LoadDataFromAccessor(context, output.mBufferView->mBuffer.GetIndex(), outputDataBuffer, output.mBufferView->mByteOffset + output.mByteOffset, outputDataBufferSize); - ApplyAccessorMinMax(input, reinterpret_cast(inputDataBuffer.begin())); - ApplyAccessorMinMax(output, reinterpret_cast(outputDataBuffer.begin())); - - return inputDataBuffer[input.mCount - 1u]; -} - -template -float LoadKeyFrames(ConversionContext& context, const gt::Animation::Channel& channel, KeyFrames& keyFrames, gt::Animation::Channel::Target::Type type) -{ - const gltf2::Accessor& input = *channel.mSampler->mInput; - const gltf2::Accessor& output = *channel.mSampler->mOutput; - - Vector inputDataBuffer; - Vector outputDataBuffer; - - const float duration = std::max(LoadDataFromAccessors(context, input, output, inputDataBuffer, outputDataBuffer), AnimationDefinition::MIN_DURATION_SECONDS); - - // Set first frame value as first keyframe (gltf animation spec) - if(input.mCount > 0 && !Dali::EqualsZero(inputDataBuffer[0])) - { - keyFrames.Add(0.0f, outputDataBuffer[0]); - } - - for(uint32_t i = 0; i < input.mCount; ++i) - { - keyFrames.Add(inputDataBuffer[i] / duration, outputDataBuffer[i]); - } - - return duration; -} - -float LoadBlendShapeKeyFrames(ConversionContext& context, const gt::Animation::Channel& channel, Index nodeIndex, uint32_t& propertyIndex, std::vector& properties) -{ - const gltf2::Accessor& input = *channel.mSampler->mInput; - const gltf2::Accessor& output = *channel.mSampler->mOutput; - - Vector inputDataBuffer; - Vector outputDataBuffer; - - const float duration = std::max(LoadDataFromAccessors(context, input, output, inputDataBuffer, outputDataBuffer), AnimationDefinition::MIN_DURATION_SECONDS); - - char weightNameBuffer[32]; - auto prefixSize = snprintf(weightNameBuffer, sizeof(weightNameBuffer), "%s[", BLEND_SHAPE_WEIGHTS_UNIFORM.c_str()); - char* const pWeightName = weightNameBuffer + prefixSize; - const auto remainingSize = sizeof(weightNameBuffer) - prefixSize; - for(uint32_t weightIndex = 0u, endWeightIndex = channel.mSampler->mOutput->mCount / channel.mSampler->mInput->mCount; weightIndex < endWeightIndex; ++weightIndex) - { - AnimatedProperty& animatedProperty = properties[propertyIndex++]; - - animatedProperty.mNodeIndex = nodeIndex; - snprintf(pWeightName, remainingSize, "%d]", weightIndex); - animatedProperty.mPropertyName = std::string(weightNameBuffer); - - animatedProperty.mKeyFrames = KeyFrames::New(); - - // Set first frame value as first keyframe (gltf animation spec) - if(input.mCount > 0 && !Dali::EqualsZero(inputDataBuffer[0])) - { - animatedProperty.mKeyFrames.Add(0.0f, outputDataBuffer[weightIndex]); - } - - for(uint32_t i = 0; i < input.mCount; ++i) - { - animatedProperty.mKeyFrames.Add(inputDataBuffer[i] / duration, outputDataBuffer[i * endWeightIndex + weightIndex]); - } - - animatedProperty.mTimePeriod = {0.f, duration}; - } - - return duration; -} - -void ConvertAnimations(const gt::Document& doc, ConversionContext& context) -{ - auto& output = context.mOutput; - - output.mAnimationDefinitions.reserve(output.mAnimationDefinitions.size() + doc.mAnimations.size()); - - for(const auto& animation : doc.mAnimations) - { - AnimationDefinition animationDef; - - if(!animation.mName.empty()) - { - animationDef.mName = animation.mName; - } - - uint32_t numberOfProperties = 0u; - for(const auto& channel : animation.mChannels) - { - if(channel.mTarget.mPath == gt::Animation::Channel::Target::WEIGHTS) - { - numberOfProperties += channel.mSampler->mOutput->mCount / channel.mSampler->mInput->mCount; - } - else - { - numberOfProperties++; - } - } - animationDef.mProperties.resize(numberOfProperties); - - Index propertyIndex = 0u; - for(const auto& channel : animation.mChannels) - { - Index nodeIndex = context.mNodeIndices.GetRuntimeId(channel.mTarget.mNode.GetIndex()); - float duration = 0.f; - - switch(channel.mTarget.mPath) - { - case gt::Animation::Channel::Target::TRANSLATION: - { - AnimatedProperty& animatedProperty = animationDef.mProperties[propertyIndex]; - - animatedProperty.mNodeIndex = nodeIndex; - animatedProperty.mPropertyName = POSITION_PROPERTY; - - animatedProperty.mKeyFrames = KeyFrames::New(); - duration = LoadKeyFrames(context, channel, animatedProperty.mKeyFrames, channel.mTarget.mPath); - - animatedProperty.mTimePeriod = {0.f, duration}; - break; - } - case gt::Animation::Channel::Target::ROTATION: - { - AnimatedProperty& animatedProperty = animationDef.mProperties[propertyIndex]; - - animatedProperty.mNodeIndex = nodeIndex; - animatedProperty.mPropertyName = ORIENTATION_PROPERTY; - - animatedProperty.mKeyFrames = KeyFrames::New(); - duration = LoadKeyFrames(context, channel, animatedProperty.mKeyFrames, channel.mTarget.mPath); - - animatedProperty.mTimePeriod = {0.f, duration}; - break; - } - case gt::Animation::Channel::Target::SCALE: - { - AnimatedProperty& animatedProperty = animationDef.mProperties[propertyIndex]; - - animatedProperty.mNodeIndex = nodeIndex; - animatedProperty.mPropertyName = SCALE_PROPERTY; - - animatedProperty.mKeyFrames = KeyFrames::New(); - duration = LoadKeyFrames(context, channel, animatedProperty.mKeyFrames, channel.mTarget.mPath); - - animatedProperty.mTimePeriod = {0.f, duration}; - break; - } - case gt::Animation::Channel::Target::WEIGHTS: - { - duration = LoadBlendShapeKeyFrames(context, channel, nodeIndex, propertyIndex, animationDef.mProperties); - - break; - } - default: - { - // nothing to animate. - break; - } - } - - animationDef.mDuration = std::max(duration, animationDef.mDuration); - - ++propertyIndex; - } - - output.mAnimationDefinitions.push_back(std::move(animationDef)); - } -} - -void ProcessSkins(const gt::Document& doc, ConversionContext& context) -{ - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skininversebindmatrices - // If an inverseBindMatrices accessor was provided, we'll load the joint data from the buffer, - // otherwise we'll set identity matrices for inverse bind pose. - struct IInverseBindMatrixProvider - { - virtual ~IInverseBindMatrixProvider() - { - } - virtual void Provide(Matrix& ibm) = 0; - }; - - struct InverseBindMatrixAccessor : public IInverseBindMatrixProvider - { - std::istream& mStream; - const uint32_t mElementSizeBytes; - - InverseBindMatrixAccessor(const gt::Accessor& accessor, ConversionContext& context) - : mStream(context.mOutput.mResources.mBuffers[accessor.mBufferView->mBuffer.GetIndex()].GetBufferStream()), - mElementSizeBytes(accessor.GetElementSizeBytes()) - { - DALI_ASSERT_DEBUG(accessor.mType == gt::AccessorType::MAT4 && accessor.mComponentType == gt::Component::FLOAT); - - if(!mStream.rdbuf()->in_avail()) - { - DALI_LOG_ERROR("Failed to load from stream\n"); - } - mStream.clear(); - mStream.seekg(accessor.mBufferView->mByteOffset + accessor.mByteOffset, mStream.beg); - } - - virtual void Provide(Matrix& ibm) override - { - DALI_ASSERT_ALWAYS(mStream.read(reinterpret_cast(ibm.AsFloat()), static_cast(static_cast(mElementSizeBytes)))); - } - }; - - struct DefaultInverseBindMatrixProvider : public IInverseBindMatrixProvider - { - virtual void Provide(Matrix& ibm) override - { - ibm = Matrix::IDENTITY; - } - }; - - auto& resources = context.mOutput.mResources; - resources.mSkeletons.reserve(doc.mSkins.size()); - - for(auto& skin : doc.mSkins) - { - std::unique_ptr ibmProvider; - if(skin.mInverseBindMatrices) - { - ibmProvider.reset(new InverseBindMatrixAccessor(*skin.mInverseBindMatrices, context)); - } - else - { - ibmProvider.reset(new DefaultInverseBindMatrixProvider()); - } - - SkeletonDefinition skeleton; - if(skin.mSkeleton.GetIndex() != INVALID_INDEX) - { - skeleton.mRootNodeIdx = context.mNodeIndices.GetRuntimeId(skin.mSkeleton.GetIndex()); - } - - skeleton.mJoints.resize(skin.mJoints.size()); - auto iJoint = skeleton.mJoints.begin(); - for(auto& joint : skin.mJoints) - { - iJoint->mNodeIdx = context.mNodeIndices.GetRuntimeId(joint.GetIndex()); - - ibmProvider->Provide(iJoint->mInverseBindMatrix); - - ++iJoint; - } - - resources.mSkeletons.push_back(std::move(skeleton)); - } -} - -void ProduceShaders(ShaderDefinitionFactory& shaderFactory, SceneDefinition& scene) -{ - uint32_t nodeCount = scene.GetNodeCount(); - for(uint32_t i = 0; i < nodeCount; ++i) - { - auto nodeDef = scene.GetNode(i); - for(auto& renderable : nodeDef->mRenderables) - { - if(shaderFactory.ProduceShader(*renderable) == INVALID_INDEX) - { - DALI_LOG_ERROR("Fail to produce shader\n"); - } - } - } -} - -void SetObjectReaders() -{ - js::SetObjectReader(BUFFER_READER); - js::SetObjectReader(BUFFER_VIEW_READER); - js::SetObjectReader(BUFFER_VIEW_CLIENT_READER); - js::SetObjectReader(COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER); - js::SetObjectReader(ACCESSOR_SPARSE_READER); - js::SetObjectReader(ACCESSOR_READER); - js::SetObjectReader(IMAGE_READER); - js::SetObjectReader(SAMPLER_READER); - js::SetObjectReader(TEXURE_READER); - js::SetObjectReader(TEXURE_INFO_READER); - js::SetObjectReader(MATERIAL_PBR_READER); - js::SetObjectReader(MATERIAL_SPECULAR_READER); - js::SetObjectReader(MATERIAL_IOR_READER); - js::SetObjectReader(MATERIAL_EXTENSION_READER); - js::SetObjectReader(MATERIAL_READER); - js::SetObjectReader(MESH_PRIMITIVE_READER); - js::SetObjectReader(MESH_READER); - js::SetObjectReader(SKIN_READER); - js::SetObjectReader(CAMERA_PERSPECTIVE_READER); - js::SetObjectReader(CAMERA_ORTHOGRAPHIC_READER); - js::SetObjectReader(CAMERA_READER); - js::SetObjectReader(NODE_READER); - js::SetObjectReader(ANIMATION_SAMPLER_READER); - js::SetObjectReader(ANIMATION_TARGET_READER); - js::SetObjectReader(ANIMATION_CHANNEL_READER); - js::SetObjectReader(ANIMATION_READER); - js::SetObjectReader(SCENE_READER); -} - -void SetDefaultEnvironmentMap(const gt::Document& doc, ConversionContext& context) -{ - EnvironmentDefinition envDef; - envDef.mUseBrdfTexture = true; - envDef.mIblIntensity = Scene3D::Loader::EnvironmentDefinition::GetDefaultIntensity(); - context.mOutput.mResources.mEnvironmentMaps.push_back({std::move(envDef), EnvironmentDefinition::Textures()}); -} - -} // namespace - -void Gltf2LoaderImpl::InitializeGltfLoader() -{ - static Dali::Mutex mInitializeMutex; - // Set ObjectReader only once (for all gltf loading). - static bool setObjectReadersRequired = true; - { - Mutex::ScopedLock lock(mInitializeMutex); - if(setObjectReadersRequired) - { - // NOTE: only referencing own, anonymous namespace, const objects; the pointers will never need to change. - SetObjectReaders(); - setObjectReadersRequired = false; - } - } -} bool Gltf2LoaderImpl::LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result) { - bool failed = false; - auto js = LoadTextFile(url.c_str(), &failed); + bool failed = false; + auto gltfText = LoadTextFile(url.c_str(), &failed); if(failed) { DALI_LOG_ERROR("Failed to load %s\n", url.c_str()); return false; } - json::unique_ptr root(json_parse(js.c_str(), js.size())); + json::unique_ptr root(json_parse(gltfText.c_str(), gltfText.size())); if(!root) { DALI_LOG_ERROR("Failed to parse %s\n", url.c_str()); return false; } - gt::Document doc; - - Dali::Scene3D::Loader::ShaderDefinitionFactory shaderFactory; - shaderFactory.SetResources(result.mResources); - - auto& rootObj = js::Cast(*root); - auto jsAsset = js::FindObjectChild("asset", rootObj); - - auto jsAssetVersion = js::FindObjectChild("version", js::Cast(*jsAsset)); - if(jsAssetVersion) - { - doc.mAsset.mVersion = js::Read::StringView(*jsAssetVersion); - } + gt::Document document; bool isMRendererModel(false); - auto jsAssetGenerator = js::FindObjectChild("generator", js::Cast(*jsAsset)); - if(jsAssetGenerator) + if(!Gltf2Util::GenerateDocument(root, document, isMRendererModel)) { - doc.mAsset.mGenerator = js::Read::StringView(*jsAssetGenerator); - isMRendererModel = (doc.mAsset.mGenerator.find(MRENDERER_MODEL_IDENTIFICATION) != std::string_view::npos); - } - - InitializeGltfLoader(); - { - static Dali::Mutex mReadMutex; - Mutex::ScopedLock lock(mReadMutex); - gt::SetRefReaderObject(doc); - DOCUMENT_READER.Read(rootObj, doc); + return false; } - auto path = url.substr(0, url.rfind('/') + 1); - ConversionContext context{result, path, INVALID_INDEX}; - - ConvertBuffers(doc, context); - ConvertMaterials(doc, context); - ConvertMeshes(doc, context); - ConvertNodes(doc, context, isMRendererModel); - ConvertAnimations(doc, context); - ProcessSkins(doc, context); - ProduceShaders(shaderFactory, result.mScene); - result.mScene.EnsureUniqueSkinningShaderInstances(result.mResources); + auto path = url.substr(0, url.rfind('/') + 1); + Gltf2Util::ConversionContext context{result, path, INVALID_INDEX}; - // Set Default Environment map - SetDefaultEnvironmentMap(doc, context); + Gltf2Util::ConvertGltfToContext(document, context, isMRendererModel); return true; } diff --git a/dali-scene3d/internal/loader/gltf2-loader-impl.h b/dali-scene3d/internal/loader/gltf2-loader-impl.h index 2b96608..319d91c 100644 --- a/dali-scene3d/internal/loader/gltf2-loader-impl.h +++ b/dali-scene3d/internal/loader/gltf2-loader-impl.h @@ -39,16 +39,9 @@ class Gltf2LoaderImpl : public ModelLoaderImpl public: /** - * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl() + * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl::LoadMode() */ bool LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result) override; - -private: - /** - * @brief Initialize glTF Loader. - * @note This method should be called once before LoadGltfScene() is called. - */ - void InitializeGltfLoader(); }; } // namespace Internal diff --git a/dali-scene3d/internal/loader/gltf2-util.cpp b/dali-scene3d/internal/loader/gltf2-util.cpp new file mode 100644 index 0000000..15361b7 --- /dev/null +++ b/dali-scene3d/internal/loader/gltf2-util.cpp @@ -0,0 +1,1329 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include + +using namespace Dali::Scene3D::Loader; + +namespace Dali +{ +namespace Scene3D +{ +namespace Loader +{ +namespace Internal +{ +namespace Gltf2Util +{ +static constexpr std::string_view MRENDERER_MODEL_IDENTIFICATION = "M-Renderer"; +static constexpr std::string_view POSITION_PROPERTY = "position"; +static constexpr std::string_view ORIENTATION_PROPERTY = "orientation"; +static constexpr std::string_view SCALE_PROPERTY = "scale"; +static constexpr std::string_view BLEND_SHAPE_WEIGHTS_UNIFORM = "uBlendShapeWeight"; +static constexpr std::string_view ROOT_NODE_NAME = "RootNode"; +static const Vector3 SCALE_TO_ADJUST(100.0f, 100.0f, 100.0f); + +static const Geometry::Type GLTF2_TO_DALI_PRIMITIVES[]{ + Geometry::POINTS, + Geometry::LINES, + Geometry::LINE_LOOP, + Geometry::LINE_STRIP, + Geometry::TRIANGLES, + Geometry::TRIANGLE_STRIP, + Geometry::TRIANGLE_FAN}; //...because Dali swaps the last two. + +static struct AttributeMapping +{ + gltf2::Attribute::Type mType; + MeshDefinition::Accessor MeshDefinition::*mAccessor; + uint16_t mElementSizeRequired; +} ATTRIBUTE_MAPPINGS[]{ + {gltf2::Attribute::NORMAL, &MeshDefinition::mNormals, sizeof(Vector3)}, + {gltf2::Attribute::TANGENT, &MeshDefinition::mTangents, sizeof(Vector3)}, + {gltf2::Attribute::TEXCOORD_0, &MeshDefinition::mTexCoords, sizeof(Vector2)}, + {gltf2::Attribute::COLOR_0, &MeshDefinition::mColors, sizeof(Vector4)}, + {gltf2::Attribute::JOINTS_0, &MeshDefinition::mJoints0, sizeof(Vector4)}, + {gltf2::Attribute::WEIGHTS_0, &MeshDefinition::mWeights0, sizeof(Vector4)}, +}; + +std::vector ReadAnimationArray(const json_value_s& j) +{ + auto results = json::Read::Array::Read>(j); + + for(auto& animation : results) + { + for(auto& channel : animation.mChannels) + { + channel.mSampler.UpdateVector(animation.mSamplers); + } + } + + return results; +} + +void ApplyAccessorMinMax(const gltf2::Accessor& acc, float* values) +{ + DALI_ASSERT_ALWAYS(acc.mMax.empty() || gltf2::AccessorType::ElementCount(acc.mType) == acc.mMax.size()); + DALI_ASSERT_ALWAYS(acc.mMin.empty() || gltf2::AccessorType::ElementCount(acc.mType) == acc.mMin.size()); + MeshDefinition::Blob::ApplyMinMax(acc.mMin, acc.mMax, acc.mCount, values); +} + +const auto BUFFER_READER = std::move(json::Reader() + .Register(*json::MakeProperty("byteLength", json::Read::Number, &gltf2::Buffer::mByteLength)) + .Register(*json::MakeProperty("uri", json::Read::StringView, &gltf2::Buffer::mUri))); + +const auto BUFFER_VIEW_READER = std::move(json::Reader() + .Register(*json::MakeProperty("buffer", gltf2::RefReader::Read, &gltf2::BufferView::mBuffer)) + .Register(*json::MakeProperty("byteOffset", json::Read::Number, &gltf2::BufferView::mByteOffset)) + .Register(*json::MakeProperty("byteLength", json::Read::Number, &gltf2::BufferView::mByteLength)) + .Register(*json::MakeProperty("byteStride", json::Read::Number, &gltf2::BufferView::mByteStride)) + .Register(*json::MakeProperty("target", json::Read::Number, &gltf2::BufferView::mTarget))); + +const auto BUFFER_VIEW_CLIENT_READER = std::move(json::Reader() + .Register(*json::MakeProperty("bufferView", gltf2::RefReader::Read, &gltf2::BufferViewClient::mBufferView)) + .Register(*json::MakeProperty("byteOffset", json::Read::Number, &gltf2::BufferViewClient::mByteOffset))); + +const auto COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER = std::move(json::Reader() + .Register(*new json::Property>("bufferView", gltf2::RefReader::Read, &gltf2::ComponentTypedBufferViewClient::mBufferView)) + .Register(*new json::Property("byteOffset", json::Read::Number, &gltf2::ComponentTypedBufferViewClient::mByteOffset)) + .Register(*json::MakeProperty("componentType", json::Read::Enum, &gltf2::ComponentTypedBufferViewClient::mComponentType))); + +const auto ACCESSOR_SPARSE_READER = std::move(json::Reader() + .Register(*json::MakeProperty("count", json::Read::Number, &gltf2::Accessor::Sparse::mCount)) + .Register(*json::MakeProperty("indices", json::ObjectReader::Read, &gltf2::Accessor::Sparse::mIndices)) + .Register(*json::MakeProperty("values", json::ObjectReader::Read, &gltf2::Accessor::Sparse::mValues))); + +const auto ACCESSOR_READER = std::move(json::Reader() + .Register(*new json::Property>("bufferView", + gltf2::RefReader::Read, + &gltf2::Accessor::mBufferView)) + .Register(*new json::Property("byteOffset", + json::Read::Number, + &gltf2::Accessor::mByteOffset)) + .Register(*new json::Property("componentType", + json::Read::Enum, + &gltf2::Accessor::mComponentType)) + .Register(*new json::Property("name", json::Read::StringView, &gltf2::Accessor::mName)) + .Register(*json::MakeProperty("count", json::Read::Number, &gltf2::Accessor::mCount)) + .Register(*json::MakeProperty("normalized", json::Read::Boolean, &gltf2::Accessor::mNormalized)) + .Register(*json::MakeProperty("type", gltf2::ReadStringEnum, &gltf2::Accessor::mType)) + .Register(*json::MakeProperty("min", json::Read::Array, &gltf2::Accessor::mMin)) + .Register(*json::MakeProperty("max", json::Read::Array, &gltf2::Accessor::mMax)) + .Register(*new json::Property("sparse", json::ObjectReader::Read, &gltf2::Accessor::SetSparse))); + +const auto IMAGE_READER = std::move(json::Reader() + .Register(*new json::Property("name", json::Read::StringView, &gltf2::Material::mName)) + .Register(*json::MakeProperty("uri", json::Read::StringView, &gltf2::Image::mUri)) + .Register(*json::MakeProperty("mimeType", json::Read::StringView, &gltf2::Image::mMimeType)) + .Register(*json::MakeProperty("bufferView", gltf2::RefReader::Read, &gltf2::Image::mBufferView))); + +const auto SAMPLER_READER = std::move(json::Reader() + .Register(*json::MakeProperty("minFilter", json::Read::Enum, &gltf2::Sampler::mMinFilter)) + .Register(*json::MakeProperty("magFilter", json::Read::Enum, &gltf2::Sampler::mMagFilter)) + .Register(*json::MakeProperty("wrapS", json::Read::Enum, &gltf2::Sampler::mWrapS)) + .Register(*json::MakeProperty("wrapT", json::Read::Enum, &gltf2::Sampler::mWrapT))); + +const auto TEXURE_READER = std::move(json::Reader() + .Register(*json::MakeProperty("source", gltf2::RefReader::Read, &gltf2::Texture::mSource)) + .Register(*json::MakeProperty("sampler", gltf2::RefReader::Read, &gltf2::Texture::mSampler))); + +const auto TEXURE_INFO_READER = std::move(json::Reader() + .Register(*json::MakeProperty("index", gltf2::RefReader::Read, &gltf2::TextureInfo::mTexture)) + .Register(*json::MakeProperty("texCoord", json::Read::Number, &gltf2::TextureInfo::mTexCoord)) + .Register(*json::MakeProperty("scale", json::Read::Number, &gltf2::TextureInfo::mScale)) + .Register(*json::MakeProperty("strength", json::Read::Number, &gltf2::TextureInfo::mStrength))); + +const auto MATERIAL_PBR_READER = std::move(json::Reader() + .Register(*json::MakeProperty("baseColorFactor", gltf2::ReadDaliVector, &gltf2::Material::Pbr::mBaseColorFactor)) + .Register(*json::MakeProperty("baseColorTexture", json::ObjectReader::Read, &gltf2::Material::Pbr::mBaseColorTexture)) + .Register(*json::MakeProperty("metallicFactor", json::Read::Number, &gltf2::Material::Pbr::mMetallicFactor)) + .Register(*json::MakeProperty("roughnessFactor", json::Read::Number, &gltf2::Material::Pbr::mRoughnessFactor)) + .Register(*json::MakeProperty("metallicRoughnessTexture", json::ObjectReader::Read, &gltf2::Material::Pbr::mMetallicRoughnessTexture))); + +const auto MATERIAL_SPECULAR_READER = std::move(json::Reader() + .Register(*json::MakeProperty("specularFactor", json::Read::Number, &gltf2::MaterialSpecular::mSpecularFactor)) + .Register(*json::MakeProperty("specularTexture", json::ObjectReader::Read, &gltf2::MaterialSpecular::mSpecularTexture)) + .Register(*json::MakeProperty("specularColorFactor", gltf2::ReadDaliVector, &gltf2::MaterialSpecular::mSpecularColorFactor)) + .Register(*json::MakeProperty("specularColorTexture", json::ObjectReader::Read, &gltf2::MaterialSpecular::mSpecularColorTexture))); + +const auto MATERIAL_IOR_READER = std::move(json::Reader() + .Register(*json::MakeProperty("ior", json::Read::Number, &gltf2::MaterialIor::mIor))); + +const auto MATERIAL_EXTENSION_READER = std::move(json::Reader() + .Register(*json::MakeProperty("KHR_materials_ior", json::ObjectReader::Read, &gltf2::MaterialExtensions::mMaterialIor)) + .Register(*json::MakeProperty("KHR_materials_specular", json::ObjectReader::Read, &gltf2::MaterialExtensions::mMaterialSpecular))); + +const auto MATERIAL_READER = std::move(json::Reader() + .Register(*new json::Property("name", json::Read::StringView, &gltf2::Material::mName)) + .Register(*json::MakeProperty("pbrMetallicRoughness", json::ObjectReader::Read, &gltf2::Material::mPbrMetallicRoughness)) + .Register(*json::MakeProperty("normalTexture", json::ObjectReader::Read, &gltf2::Material::mNormalTexture)) + .Register(*json::MakeProperty("occlusionTexture", json::ObjectReader::Read, &gltf2::Material::mOcclusionTexture)) + .Register(*json::MakeProperty("emissiveTexture", json::ObjectReader::Read, &gltf2::Material::mEmissiveTexture)) + .Register(*json::MakeProperty("emissiveFactor", gltf2::ReadDaliVector, &gltf2::Material::mEmissiveFactor)) + .Register(*json::MakeProperty("alphaMode", gltf2::ReadStringEnum, &gltf2::Material::mAlphaMode)) + .Register(*json::MakeProperty("alphaCutoff", json::Read::Number, &gltf2::Material::mAlphaCutoff)) + .Register(*json::MakeProperty("doubleSided", json::Read::Boolean, &gltf2::Material::mDoubleSided)) + .Register(*json::MakeProperty("extensions", json::ObjectReader::Read, &gltf2::Material::mMaterialExtensions))); + +std::map> ReadMeshPrimitiveAttributes(const json_value_s& j) +{ + auto& jo = json::Cast(j); + std::map> result; + + auto i = jo.start; + while(i) + { + auto jstr = *i->name; + result[gltf2::Attribute::FromString(jstr.string, jstr.string_size)] = gltf2::RefReader::Read(*i->value); + i = i->next; + } + return result; +} + +std::vector>> ReadMeshPrimitiveTargets(const json_value_s& j) +{ + auto& jo = json::Cast(j); + std::vector>> result; + + result.reserve(jo.length); + + auto i = jo.start; + while(i) + { + result.push_back(std::move(ReadMeshPrimitiveAttributes(*i->value))); + i = i->next; + } + + return result; +} + +const auto MESH_PRIMITIVE_READER = std::move(json::Reader() + .Register(*json::MakeProperty("attributes", ReadMeshPrimitiveAttributes, &gltf2::Mesh::Primitive::mAttributes)) + .Register(*json::MakeProperty("indices", gltf2::RefReader::Read, &gltf2::Mesh::Primitive::mIndices)) + .Register(*json::MakeProperty("material", gltf2::RefReader::Read, &gltf2::Mesh::Primitive::mMaterial)) + .Register(*json::MakeProperty("mode", json::Read::Enum, &gltf2::Mesh::Primitive::mMode)) + .Register(*json::MakeProperty("targets", ReadMeshPrimitiveTargets, &gltf2::Mesh::Primitive::mTargets))); + +const auto MESH_READER = std::move(json::Reader() + .Register(*new json::Property("name", json::Read::StringView, &gltf2::Mesh::mName)) + .Register(*json::MakeProperty("primitives", + json::Read::Array::Read>, + &gltf2::Mesh::mPrimitives)) + .Register(*json::MakeProperty("weights", json::Read::Array, &gltf2::Mesh::mWeights))); + +const auto SKIN_READER = std::move(json::Reader() + .Register(*new json::Property("name", json::Read::StringView, &gltf2::Skin::mName)) + .Register(*json::MakeProperty("inverseBindMatrices", + gltf2::RefReader::Read, + &gltf2::Skin::mInverseBindMatrices)) + .Register(*json::MakeProperty("skeleton", + gltf2::RefReader::Read, + &gltf2::Skin::mSkeleton)) + .Register(*json::MakeProperty("joints", + json::Read::Array, gltf2::RefReader::Read>, + &gltf2::Skin::mJoints))); + +const auto CAMERA_PERSPECTIVE_READER = std::move(json::Reader() + .Register(*json::MakeProperty("aspectRatio", json::Read::Number, &gltf2::Camera::Perspective::mAspectRatio)) + .Register(*json::MakeProperty("yfov", json::Read::Number, &gltf2::Camera::Perspective::mYFov)) + .Register(*json::MakeProperty("zfar", json::Read::Number, &gltf2::Camera::Perspective::mZFar)) + .Register(*json::MakeProperty("znear", json::Read::Number, &gltf2::Camera::Perspective::mZNear))); // TODO: infinite perspective projection, where znear is omitted + +const auto CAMERA_ORTHOGRAPHIC_READER = std::move(json::Reader() + .Register(*json::MakeProperty("xmag", json::Read::Number, &gltf2::Camera::Orthographic::mXMag)) + .Register(*json::MakeProperty("ymag", json::Read::Number, &gltf2::Camera::Orthographic::mYMag)) + .Register(*json::MakeProperty("zfar", json::Read::Number, &gltf2::Camera::Orthographic::mZFar)) + .Register(*json::MakeProperty("znear", json::Read::Number, &gltf2::Camera::Orthographic::mZNear))); + +const auto CAMERA_READER = std::move(json::Reader() + .Register(*new json::Property("name", json::Read::StringView, &gltf2::Camera::mName)) + .Register(*json::MakeProperty("type", json::Read::StringView, &gltf2::Camera::mType)) + .Register(*json::MakeProperty("perspective", json::ObjectReader::Read, &gltf2::Camera::mPerspective)) + .Register(*json::MakeProperty("orthographic", json::ObjectReader::Read, &gltf2::Camera::mOrthographic))); + +const auto NODE_READER = std::move(json::Reader() + .Register(*new json::Property("name", json::Read::StringView, &gltf2::Node::mName)) + .Register(*json::MakeProperty("translation", gltf2::ReadDaliVector, &gltf2::Node::mTranslation)) + .Register(*json::MakeProperty("rotation", gltf2::ReadQuaternion, &gltf2::Node::mRotation)) + .Register(*json::MakeProperty("scale", gltf2::ReadDaliVector, &gltf2::Node::mScale)) + .Register(*new json::Property("matrix", gltf2::ReadDaliVector, &gltf2::Node::SetMatrix)) + .Register(*json::MakeProperty("camera", gltf2::RefReader::Read, &gltf2::Node::mCamera)) + .Register(*json::MakeProperty("children", json::Read::Array, gltf2::RefReader::Read>, &gltf2::Node::mChildren)) + .Register(*json::MakeProperty("mesh", gltf2::RefReader::Read, &gltf2::Node::mMesh)) + .Register(*json::MakeProperty("skin", gltf2::RefReader::Read, &gltf2::Node::mSkin))); + +const auto ANIMATION_SAMPLER_READER = std::move(json::Reader() + .Register(*json::MakeProperty("input", gltf2::RefReader::Read, &gltf2::Animation::Sampler::mInput)) + .Register(*json::MakeProperty("output", gltf2::RefReader::Read, &gltf2::Animation::Sampler::mOutput)) + .Register(*json::MakeProperty("interpolation", gltf2::ReadStringEnum, &gltf2::Animation::Sampler::mInterpolation))); + +const auto ANIMATION_TARGET_READER = std::move(json::Reader() + .Register(*json::MakeProperty("node", gltf2::RefReader::Read, &gltf2::Animation::Channel::Target::mNode)) + .Register(*json::MakeProperty("path", gltf2::ReadStringEnum, &gltf2::Animation::Channel::Target::mPath))); + +const auto ANIMATION_CHANNEL_READER = std::move(json::Reader() + .Register(*json::MakeProperty("target", json::ObjectReader::Read, &gltf2::Animation::Channel::mTarget)) + .Register(*json::MakeProperty("sampler", gltf2::RefReader::Read, &gltf2::Animation::Channel::mSampler))); + +const auto ANIMATION_READER = std::move(json::Reader() + .Register(*new json::Property("name", json::Read::StringView, &gltf2::Animation::mName)) + .Register(*json::MakeProperty("samplers", + json::Read::Array::Read>, + &gltf2::Animation::mSamplers)) + .Register(*json::MakeProperty("channels", + json::Read::Array::Read>, + &gltf2::Animation::mChannels))); + +const auto SCENE_READER = std::move(json::Reader() + .Register(*new json::Property("name", json::Read::StringView, &gltf2::Scene::mName)) + .Register(*json::MakeProperty("nodes", + json::Read::Array, gltf2::RefReader::Read>, + &gltf2::Scene::mNodes))); + +const auto DOCUMENT_READER = std::move(json::Reader() + .Register(*json::MakeProperty("buffers", + json::Read::Array::Read>, + &gltf2::Document::mBuffers)) + .Register(*json::MakeProperty("bufferViews", + json::Read::Array::Read>, + &gltf2::Document::mBufferViews)) + .Register(*json::MakeProperty("accessors", + json::Read::Array::Read>, + &gltf2::Document::mAccessors)) + .Register(*json::MakeProperty("images", + json::Read::Array::Read>, + &gltf2::Document::mImages)) + .Register(*json::MakeProperty("samplers", + json::Read::Array::Read>, + &gltf2::Document::mSamplers)) + .Register(*json::MakeProperty("textures", + json::Read::Array::Read>, + &gltf2::Document::mTextures)) + .Register(*json::MakeProperty("materials", + json::Read::Array::Read>, + &gltf2::Document::mMaterials)) + .Register(*json::MakeProperty("meshes", + json::Read::Array::Read>, + &gltf2::Document::mMeshes)) + .Register(*json::MakeProperty("skins", + json::Read::Array::Read>, + &gltf2::Document::mSkins)) + .Register(*json::MakeProperty("cameras", + json::Read::Array::Read>, + &gltf2::Document::mCameras)) + .Register(*json::MakeProperty("nodes", + json::Read::Array::Read>, + &gltf2::Document::mNodes)) + .Register(*json::MakeProperty("animations", + ReadAnimationArray, + &gltf2::Document::mAnimations)) + .Register(*json::MakeProperty("scenes", + json::Read::Array::Read>, + &gltf2::Document::mScenes)) + .Register(*json::MakeProperty("scene", gltf2::RefReader::Read, &gltf2::Document::mScene))); + +void ConvertBuffer(const gltf2::Buffer& buffer, decltype(ResourceBundle::mBuffers)& outBuffers, const std::string& resourcePath) +{ + BufferDefinition bufferDefinition; + + bufferDefinition.mResourcePath = resourcePath; + bufferDefinition.mUri = buffer.mUri; + bufferDefinition.mByteLength = buffer.mByteLength; + + outBuffers.emplace_back(std::move(bufferDefinition)); +} + +void ConvertBuffers(const gltf2::Document& doc, ConversionContext& context) +{ + auto& outBuffers = context.mOutput.mResources.mBuffers; + outBuffers.reserve(doc.mBuffers.size()); + + for(auto& buffer : doc.mBuffers) + { + if(buffer.mUri.empty()) + { + continue; + } + ConvertBuffer(buffer, outBuffers, context.mPath); + } +} + +SamplerFlags::Type ConvertWrapMode(gltf2::Wrap::Type wrapMode) +{ + switch(wrapMode) + { + case gltf2::Wrap::REPEAT: + return SamplerFlags::WRAP_REPEAT; + case gltf2::Wrap::CLAMP_TO_EDGE: + return SamplerFlags::WRAP_CLAMP; + case gltf2::Wrap::MIRRORED_REPEAT: + return SamplerFlags::WRAP_MIRROR; + default: + throw std::runtime_error("Invalid wrap type."); + } +} + +SamplerFlags::Type ConvertSampler(const gltf2::Ref& sampler) +{ + if(sampler) + { + return ((sampler->mMinFilter < gltf2::Filter::NEAREST_MIPMAP_NEAREST) ? (sampler->mMinFilter - gltf2::Filter::NEAREST) : ((sampler->mMinFilter - gltf2::Filter::NEAREST_MIPMAP_NEAREST) + 2)) | + ((sampler->mMagFilter - gltf2::Filter::NEAREST) << SamplerFlags::FILTER_MAG_SHIFT) | + (ConvertWrapMode(sampler->mWrapS) << SamplerFlags::WRAP_S_SHIFT) | + (ConvertWrapMode(sampler->mWrapT) << SamplerFlags::WRAP_T_SHIFT); + } + else + { + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#texturesampler + // "The index of the sampler used by this texture. When undefined, a sampler with repeat wrapping and auto filtering should be used." + // "What is an auto filtering", I hear you ask. Since there's nothing else to determine mipmapping from - including glTF image + // properties, if not in some extension -, we will simply assume linear filtering. + return SamplerFlags::FILTER_LINEAR | (SamplerFlags::FILTER_LINEAR << SamplerFlags::FILTER_MAG_SHIFT) | + (SamplerFlags::WRAP_REPEAT << SamplerFlags::WRAP_S_SHIFT) | (SamplerFlags::WRAP_REPEAT << SamplerFlags::WRAP_T_SHIFT); + } +} + +TextureDefinition ConvertTextureInfo(const gltf2::TextureInfo& mm, ConversionContext& context, const ImageMetadata& metaData = ImageMetadata()) +{ + TextureDefinition textureDefinition; + std::string uri = std::string(mm.mTexture->mSource->mUri); + if(uri.empty()) + { + uint32_t bufferIndex = mm.mTexture->mSource->mBufferView->mBuffer.GetIndex(); + if(bufferIndex != INVALID_INDEX && context.mOutput.mResources.mBuffers[bufferIndex].IsAvailable()) + { + auto& stream = context.mOutput.mResources.mBuffers[bufferIndex].GetBufferStream(); + stream.clear(); + stream.seekg(mm.mTexture->mSource->mBufferView->mByteOffset, stream.beg); + std::vector dataBuffer; + dataBuffer.resize(mm.mTexture->mSource->mBufferView->mByteLength); + stream.read(reinterpret_cast(dataBuffer.data()), static_cast(static_cast(mm.mTexture->mSource->mBufferView->mByteLength))); + return TextureDefinition{std::move(dataBuffer), ConvertSampler(mm.mTexture->mSampler), metaData.mMinSize, metaData.mSamplingMode}; + } + return TextureDefinition(); + } + else + { + return TextureDefinition{uri, ConvertSampler(mm.mTexture->mSampler), metaData.mMinSize, metaData.mSamplingMode}; + } +} + +void ConvertMaterial(const gltf2::Material& material, const std::unordered_map& imageMetaData, decltype(ResourceBundle::mMaterials)& outMaterials, ConversionContext& context) +{ + auto getTextureMetaData = [](const std::unordered_map& metaData, const gltf2::TextureInfo& info) + { + if(!info.mTexture->mSource->mUri.empty()) + { + if(auto search = metaData.find(info.mTexture->mSource->mUri.data()); search != metaData.end()) + { + return search->second; + } + } + return ImageMetadata(); + }; + + MaterialDefinition matDef; + + auto& pbr = material.mPbrMetallicRoughness; + if(material.mAlphaMode == gltf2::AlphaMode::BLEND) + { + matDef.mIsOpaque = false; + matDef.mFlags |= MaterialDefinition::TRANSPARENCY; + } + else if(material.mAlphaMode == gltf2::AlphaMode::MASK) + { + matDef.mIsMask = true; + matDef.SetAlphaCutoff(std::min(1.f, std::max(0.f, material.mAlphaCutoff))); + } + + matDef.mBaseColorFactor = pbr.mBaseColorFactor; + + matDef.mTextureStages.reserve(!!pbr.mBaseColorTexture + !!pbr.mMetallicRoughnessTexture + !!material.mNormalTexture + !!material.mOcclusionTexture + !!material.mEmissiveTexture); + if(pbr.mBaseColorTexture) + { + const auto semantic = MaterialDefinition::ALBEDO; + matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(pbr.mBaseColorTexture, context, getTextureMetaData(imageMetaData, pbr.mBaseColorTexture))}); + // TODO: and there had better be one + matDef.mFlags |= semantic; + } + else + { + matDef.mNeedAlbedoTexture = false; + } + + matDef.mMetallic = pbr.mMetallicFactor; + matDef.mRoughness = pbr.mRoughnessFactor; + + if(pbr.mMetallicRoughnessTexture) + { + const auto semantic = MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS | + MaterialDefinition::GLTF_CHANNELS; + matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(pbr.mMetallicRoughnessTexture, context, getTextureMetaData(imageMetaData, pbr.mMetallicRoughnessTexture))}); + // TODO: and there had better be one + matDef.mFlags |= semantic; + } + else + { + matDef.mNeedMetallicRoughnessTexture = false; + } + + matDef.mNormalScale = material.mNormalTexture.mScale; + if(material.mNormalTexture) + { + const auto semantic = MaterialDefinition::NORMAL; + matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mNormalTexture, context, getTextureMetaData(imageMetaData, material.mNormalTexture))}); + // TODO: and there had better be one + matDef.mFlags |= semantic; + } + else + { + matDef.mNeedNormalTexture = false; + } + + if(material.mOcclusionTexture) + { + const auto semantic = MaterialDefinition::OCCLUSION; + matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mOcclusionTexture, context, getTextureMetaData(imageMetaData, material.mOcclusionTexture))}); + // TODO: and there had better be one + matDef.mFlags |= semantic; + matDef.mOcclusionStrength = material.mOcclusionTexture.mStrength; + } + + if(material.mEmissiveTexture) + { + const auto semantic = MaterialDefinition::EMISSIVE; + matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mEmissiveTexture, context, getTextureMetaData(imageMetaData, material.mEmissiveTexture))}); + // TODO: and there had better be one + matDef.mFlags |= semantic; + matDef.mEmissiveFactor = material.mEmissiveFactor; + } + + if(!Dali::Equals(material.mMaterialExtensions.mMaterialIor.mIor, gltf2::UNDEFINED_FLOAT_VALUE)) + { + float ior = material.mMaterialExtensions.mMaterialIor.mIor; + matDef.mDielectricSpecular = powf((ior - 1.0f) / (ior + 1.0f), 2.0f); + } + matDef.mSpecularFactor = material.mMaterialExtensions.mMaterialSpecular.mSpecularFactor; + matDef.mSpecularColorFactor = material.mMaterialExtensions.mMaterialSpecular.mSpecularColorFactor; + + if(material.mMaterialExtensions.mMaterialSpecular.mSpecularTexture) + { + const auto semantic = MaterialDefinition::SPECULAR; + matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mMaterialExtensions.mMaterialSpecular.mSpecularTexture, context, getTextureMetaData(imageMetaData, material.mMaterialExtensions.mMaterialSpecular.mSpecularTexture))}); + matDef.mFlags |= semantic; + } + + if(material.mMaterialExtensions.mMaterialSpecular.mSpecularColorTexture) + { + const auto semantic = MaterialDefinition::SPECULAR_COLOR; + matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mMaterialExtensions.mMaterialSpecular.mSpecularColorTexture, context, getTextureMetaData(imageMetaData, material.mMaterialExtensions.mMaterialSpecular.mSpecularColorTexture))}); + matDef.mFlags |= semantic; + } + + matDef.mDoubleSided = material.mDoubleSided; + + outMaterials.emplace_back(std::move(matDef), TextureSet()); +} + +void ConvertMaterials(const gltf2::Document& doc, ConversionContext& context) +{ + auto& imageMetaData = context.mOutput.mSceneMetadata.mImageMetadata; + + auto& outMaterials = context.mOutput.mResources.mMaterials; + outMaterials.reserve(doc.mMaterials.size()); + + for(auto& m : doc.mMaterials) + { + ConvertMaterial(m, imageMetaData, outMaterials, context); + } +} + +MeshDefinition::Accessor ConvertMeshPrimitiveAccessor(const gltf2::Accessor& acc) +{ + DALI_ASSERT_ALWAYS((acc.mBufferView && + (acc.mBufferView->mByteStride < std::numeric_limits::max())) || + (acc.mSparse && !acc.mBufferView)); + + DALI_ASSERT_ALWAYS(!acc.mSparse || + ((acc.mSparse->mIndices.mBufferView && (acc.mSparse->mIndices.mBufferView->mByteStride < std::numeric_limits::max())) && + (acc.mSparse->mValues.mBufferView && (acc.mSparse->mValues.mBufferView->mByteStride < std::numeric_limits::max())))); + + MeshDefinition::SparseBlob sparseBlob; + if(acc.mSparse) + { + const gltf2::Accessor::Sparse& sparse = *acc.mSparse; + const gltf2::ComponentTypedBufferViewClient& indices = sparse.mIndices; + const gltf2::BufferViewClient& values = sparse.mValues; + + MeshDefinition::Blob indicesBlob( + indices.mBufferView->mByteOffset + indices.mByteOffset, + sparse.mCount * indices.GetBytesPerComponent(), + static_cast(indices.mBufferView->mByteStride), + static_cast(indices.GetBytesPerComponent()), + {}, + {}); + MeshDefinition::Blob valuesBlob( + values.mBufferView->mByteOffset + values.mByteOffset, + sparse.mCount * acc.GetElementSizeBytes(), + static_cast(values.mBufferView->mByteStride), + static_cast(acc.GetElementSizeBytes()), + {}, + {}); + + sparseBlob = std::move(MeshDefinition::SparseBlob(std::move(indicesBlob), std::move(valuesBlob), acc.mSparse->mCount)); + } + + uint32_t bufferViewOffset = 0u; + uint32_t bufferViewStride = 0u; + if(acc.mBufferView) + { + bufferViewOffset = acc.mBufferView->mByteOffset; + bufferViewStride = acc.mBufferView->mByteStride; + } + + return MeshDefinition::Accessor{ + std::move(MeshDefinition::Blob{bufferViewOffset + acc.mByteOffset, + acc.GetBytesLength(), + static_cast(bufferViewStride), + static_cast(acc.GetElementSizeBytes()), + acc.mMin, + acc.mMax}), + std::move(sparseBlob), + acc.mBufferView ? acc.mBufferView->mBuffer.GetIndex() : 0}; +} + +void ConvertMeshes(const gltf2::Document& doc, ConversionContext& context) +{ + uint32_t meshCount = 0; + context.mMeshIds.reserve(doc.mMeshes.size()); + for(auto& mesh : doc.mMeshes) + { + context.mMeshIds.push_back(meshCount); + meshCount += mesh.mPrimitives.size(); + } + + auto& outMeshes = context.mOutput.mResources.mMeshes; + outMeshes.reserve(meshCount); + for(auto& mesh : doc.mMeshes) + { + for(auto& primitive : mesh.mPrimitives) + { + MeshDefinition meshDefinition; + + auto& attribs = primitive.mAttributes; + meshDefinition.mPrimitiveType = GLTF2_TO_DALI_PRIMITIVES[primitive.mMode]; + + auto& accPositions = *attribs.find(gltf2::Attribute::POSITION)->second; + meshDefinition.mPositions = ConvertMeshPrimitiveAccessor(accPositions); + // glTF2 support vector4 tangent for mesh. + // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#meshes-overview + meshDefinition.mTangentType = Property::VECTOR4; + + const bool needNormalsTangents = accPositions.mType == gltf2::AccessorType::VEC3; + for(auto& am : ATTRIBUTE_MAPPINGS) + { + auto iFind = attribs.find(am.mType); + if(iFind != attribs.end()) + { + auto& accessor = meshDefinition.*(am.mAccessor); + accessor = ConvertMeshPrimitiveAccessor(*iFind->second); + + if(iFind->first == gltf2::Attribute::JOINTS_0) + { + meshDefinition.mFlags |= (iFind->second->mComponentType == gltf2::Component::UNSIGNED_SHORT) * MeshDefinition::U16_JOINT_IDS; + meshDefinition.mFlags |= (iFind->second->mComponentType == gltf2::Component::UNSIGNED_BYTE) * MeshDefinition::U8_JOINT_IDS; + DALI_ASSERT_DEBUG(MaskMatch(meshDefinition.mFlags, MeshDefinition::U16_JOINT_IDS) || MaskMatch(meshDefinition.mFlags, MeshDefinition::U8_JOINT_IDS) || iFind->second->mComponentType == gltf2::Component::FLOAT); + } + } + else if(needNormalsTangents) + { + switch(am.mType) + { + case gltf2::Attribute::NORMAL: + meshDefinition.RequestNormals(); + break; + + case gltf2::Attribute::TANGENT: + meshDefinition.RequestTangents(); + break; + + default: + break; + } + } + } + + if(primitive.mIndices) + { + meshDefinition.mIndices = ConvertMeshPrimitiveAccessor(*primitive.mIndices); + meshDefinition.mFlags |= (primitive.mIndices->mComponentType == gltf2::Component::UNSIGNED_INT) * MeshDefinition::U32_INDICES; + meshDefinition.mFlags |= (primitive.mIndices->mComponentType == gltf2::Component::UNSIGNED_BYTE) * MeshDefinition::U8_INDICES; + DALI_ASSERT_DEBUG(MaskMatch(meshDefinition.mFlags, MeshDefinition::U32_INDICES) || MaskMatch(meshDefinition.mFlags, MeshDefinition::U8_INDICES) || primitive.mIndices->mComponentType == gltf2::Component::UNSIGNED_SHORT); + } + + if(!primitive.mTargets.empty()) + { + meshDefinition.mBlendShapes.reserve(primitive.mTargets.size()); + meshDefinition.mBlendShapeVersion = BlendShapes::Version::VERSION_2_0; + for(const auto& target : primitive.mTargets) + { + MeshDefinition::BlendShape blendShape; + + auto endIt = target.end(); + auto it = target.find(gltf2::Attribute::POSITION); + if(it != endIt) + { + blendShape.deltas = ConvertMeshPrimitiveAccessor(*it->second); + } + it = target.find(gltf2::Attribute::NORMAL); + if(it != endIt) + { + blendShape.normals = ConvertMeshPrimitiveAccessor(*it->second); + } + it = target.find(gltf2::Attribute::TANGENT); + if(it != endIt) + { + blendShape.tangents = ConvertMeshPrimitiveAccessor(*it->second); + } + + if(!mesh.mWeights.empty()) + { + blendShape.weight = mesh.mWeights[meshDefinition.mBlendShapes.size()]; + } + + meshDefinition.mBlendShapes.push_back(std::move(blendShape)); + } + } + + outMeshes.push_back({std::move(meshDefinition), MeshGeometry{}}); + } + } +} + +ModelRenderable* MakeModelRenderable(const gltf2::Mesh::Primitive& prim, ConversionContext& context) +{ + auto modelRenderable = new ModelRenderable(); + + modelRenderable->mShaderIdx = 0; // TODO: further thought + + auto materialIdx = prim.mMaterial.GetIndex(); + if(INVALID_INDEX == materialIdx) + { + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#default-material + if(INVALID_INDEX == context.mDefaultMaterial) + { + auto& outMaterials = context.mOutput.mResources.mMaterials; + context.mDefaultMaterial = outMaterials.size(); + + ConvertMaterial(gltf2::Material{}, context.mOutput.mSceneMetadata.mImageMetadata, outMaterials, context); + } + + materialIdx = context.mDefaultMaterial; + } + + modelRenderable->mMaterialIdx = materialIdx; + + return modelRenderable; +} + +void ConvertCamera(const gltf2::Camera& camera, CameraParameters& camParams) +{ + camParams.isPerspective = camera.mType.compare("perspective") == 0; + if(camParams.isPerspective) + { + auto& perspective = camera.mPerspective; + if(!Dali::Equals(perspective.mYFov, gltf2::UNDEFINED_FLOAT_VALUE)) + { + camParams.yFovDegree = Degree(Radian(perspective.mYFov)); + } + else + { + camParams.yFovDegree = Degree(gltf2::UNDEFINED_FLOAT_VALUE); + } + camParams.zNear = perspective.mZNear; + camParams.zFar = perspective.mZFar; + // TODO: yes, we seem to ignore aspectRatio in CameraParameters. + } + else + { + auto& ortho = camera.mOrthographic; + if(!Dali::Equals(ortho.mYMag, gltf2::UNDEFINED_FLOAT_VALUE) && !Dali::Equals(ortho.mXMag, gltf2::UNDEFINED_FLOAT_VALUE)) + { + camParams.orthographicSize = ortho.mYMag * .5f; + camParams.aspectRatio = ortho.mXMag / ortho.mYMag; + } + else + { + camParams.orthographicSize = gltf2::UNDEFINED_FLOAT_VALUE; + camParams.aspectRatio = gltf2::UNDEFINED_FLOAT_VALUE; + } + camParams.zNear = ortho.mZNear; + camParams.zFar = ortho.mZFar; + } +} + +void ConvertNode(gltf2::Node const& node, const Index gltfIdx, Index parentIdx, ConversionContext& context, bool isMRendererModel) +{ + auto& output = context.mOutput; + auto& scene = output.mScene; + auto& resources = output.mResources; + + const auto idx = scene.GetNodeCount(); + auto weakNode = scene.AddNode([&]() + { + std::unique_ptr nodeDef{new NodeDefinition()}; + + nodeDef->mParentIdx = parentIdx; + nodeDef->mName = node.mName; + if(nodeDef->mName.empty()) + { + // TODO: Production quality generation of unique names. + nodeDef->mName = std::to_string(reinterpret_cast(nodeDef.get())); + } + + if(!node.mSkin) // Nodes with skinned meshes are not supposed to have local transforms. + { + nodeDef->mPosition = node.mTranslation; + nodeDef->mOrientation = node.mRotation; + nodeDef->mScale = node.mScale; + + if(isMRendererModel && node.mName == ROOT_NODE_NAME && node.mScale == SCALE_TO_ADJUST) + { + nodeDef->mScale *= 0.01f; + } + } + + return nodeDef; }()); + if(!weakNode) + { + ExceptionFlinger(ASSERT_LOCATION) << "Node name '" << node.mName << "' is not unique; scene is invalid."; + } + + context.mNodeIndices.RegisterMapping(gltfIdx, idx); + + Index skeletonIdx = node.mSkin ? node.mSkin.GetIndex() : INVALID_INDEX; + if(node.mMesh) + { + auto& mesh = *node.mMesh; + uint32_t primitiveCount = mesh.mPrimitives.size(); + auto meshIdx = context.mMeshIds[node.mMesh.GetIndex()]; + weakNode->mRenderables.reserve(primitiveCount); + for(uint32_t i = 0; i < primitiveCount; ++i) + { + std::unique_ptr renderable; + auto modelRenderable = MakeModelRenderable(mesh.mPrimitives[i], context); + modelRenderable->mMeshIdx = meshIdx + i; + + DALI_ASSERT_DEBUG(resources.mMeshes[modelRenderable->mMeshIdx].first.mSkeletonIdx == INVALID_INDEX || + resources.mMeshes[modelRenderable->mMeshIdx].first.mSkeletonIdx == skeletonIdx); + resources.mMeshes[modelRenderable->mMeshIdx].first.mSkeletonIdx = skeletonIdx; + + renderable.reset(modelRenderable); + weakNode->mRenderables.push_back(std::move(renderable)); + } + } + + if(node.mCamera) + { + CameraParameters camParams; + ConvertCamera(*node.mCamera, camParams); + + camParams.matrix.SetTransformComponents(node.mScale, node.mRotation, node.mTranslation); + output.mCameraParameters.push_back(camParams); + } + + for(auto& n : node.mChildren) + { + ConvertNode(*n, n.GetIndex(), idx, context, isMRendererModel); + } +} + +void ConvertSceneNodes(const gltf2::Scene& scene, ConversionContext& context, bool isMRendererModel) +{ + auto& outScene = context.mOutput.mScene; + Index rootIdx = outScene.GetNodeCount(); + switch(scene.mNodes.size()) + { + case 0: + break; + + case 1: + ConvertNode(*scene.mNodes[0], scene.mNodes[0].GetIndex(), INVALID_INDEX, context, isMRendererModel); + outScene.AddRootNode(rootIdx); + break; + + default: + { + std::unique_ptr sceneRoot{new NodeDefinition()}; + sceneRoot->mName = "GLTF_LOADER_SCENE_ROOT_" + std::to_string(outScene.GetRoots().size()); + + outScene.AddNode(std::move(sceneRoot)); + outScene.AddRootNode(rootIdx); + + for(auto& n : scene.mNodes) + { + ConvertNode(*n, n.GetIndex(), rootIdx, context, isMRendererModel); + } + break; + } + } +} + +void ConvertNodes(const gltf2::Document& doc, ConversionContext& context, bool isMRendererModel) +{ + if(!doc.mScenes.empty()) + { + uint32_t rootSceneIndex = 0u; + if(doc.mScene) + { + rootSceneIndex = doc.mScene.GetIndex(); + } + ConvertSceneNodes(doc.mScenes[rootSceneIndex], context, isMRendererModel); + + for(uint32_t i = 0, i1 = rootSceneIndex; i < i1; ++i) + { + ConvertSceneNodes(doc.mScenes[i], context, isMRendererModel); + } + + for(uint32_t i = rootSceneIndex + 1; i < doc.mScenes.size(); ++i) + { + ConvertSceneNodes(doc.mScenes[i], context, isMRendererModel); + } + } +} + +template +void LoadDataFromAccessor(ConversionContext& context, uint32_t bufferIndex, Vector& dataBuffer, uint32_t offset, uint32_t size) +{ + if(bufferIndex >= context.mOutput.mResources.mBuffers.size()) + { + DALI_LOG_ERROR("Invailid buffer index\n"); + return; + } + + auto& buffer = context.mOutput.mResources.mBuffers[bufferIndex]; + if(!buffer.IsAvailable()) + { + DALI_LOG_ERROR("Failed to load from buffer stream.\n"); + } + auto& stream = buffer.GetBufferStream(); + stream.clear(); + stream.seekg(offset, stream.beg); + stream.read(reinterpret_cast(dataBuffer.Begin()), static_cast(static_cast(size))); +} + +template +float LoadDataFromAccessors(ConversionContext& context, const gltf2::Accessor& input, const gltf2::Accessor& output, Vector& inputDataBuffer, Vector& outputDataBuffer) +{ + inputDataBuffer.Resize(input.mCount); + outputDataBuffer.Resize(output.mCount); + + const uint32_t inputDataBufferSize = input.GetBytesLength(); + const uint32_t outputDataBufferSize = output.GetBytesLength(); + + LoadDataFromAccessor(context, output.mBufferView->mBuffer.GetIndex(), inputDataBuffer, input.mBufferView->mByteOffset + input.mByteOffset, inputDataBufferSize); + LoadDataFromAccessor(context, output.mBufferView->mBuffer.GetIndex(), outputDataBuffer, output.mBufferView->mByteOffset + output.mByteOffset, outputDataBufferSize); + ApplyAccessorMinMax(input, reinterpret_cast(inputDataBuffer.begin())); + ApplyAccessorMinMax(output, reinterpret_cast(outputDataBuffer.begin())); + + return inputDataBuffer[input.mCount - 1u]; +} + +template +float LoadKeyFrames(ConversionContext& context, const gltf2::Animation::Channel& channel, KeyFrames& keyFrames, gltf2::Animation::Channel::Target::Type type) +{ + const gltf2::Accessor& input = *channel.mSampler->mInput; + const gltf2::Accessor& output = *channel.mSampler->mOutput; + + Vector inputDataBuffer; + Vector outputDataBuffer; + + const float duration = std::max(LoadDataFromAccessors(context, input, output, inputDataBuffer, outputDataBuffer), AnimationDefinition::MIN_DURATION_SECONDS); + + // Set first frame value as first keyframe (gltf animation spec) + if(input.mCount > 0 && !Dali::EqualsZero(inputDataBuffer[0])) + { + keyFrames.Add(0.0f, outputDataBuffer[0]); + } + + for(uint32_t i = 0; i < input.mCount; ++i) + { + keyFrames.Add(inputDataBuffer[i] / duration, outputDataBuffer[i]); + } + + return duration; +} + +float LoadBlendShapeKeyFrames(ConversionContext& context, const gltf2::Animation::Channel& channel, Index nodeIndex, uint32_t& propertyIndex, std::vector& properties) +{ + const gltf2::Accessor& input = *channel.mSampler->mInput; + const gltf2::Accessor& output = *channel.mSampler->mOutput; + + Vector inputDataBuffer; + Vector outputDataBuffer; + + const float duration = std::max(LoadDataFromAccessors(context, input, output, inputDataBuffer, outputDataBuffer), AnimationDefinition::MIN_DURATION_SECONDS); + + char weightNameBuffer[32]; + auto prefixSize = snprintf(weightNameBuffer, sizeof(weightNameBuffer), "%s[", BLEND_SHAPE_WEIGHTS_UNIFORM.data()); + char* const pWeightName = weightNameBuffer + prefixSize; + const auto remainingSize = sizeof(weightNameBuffer) - prefixSize; + for(uint32_t weightIndex = 0u, endWeightIndex = channel.mSampler->mOutput->mCount / channel.mSampler->mInput->mCount; weightIndex < endWeightIndex; ++weightIndex) + { + AnimatedProperty& animatedProperty = properties[propertyIndex++]; + + animatedProperty.mNodeIndex = nodeIndex; + snprintf(pWeightName, remainingSize, "%d]", weightIndex); + animatedProperty.mPropertyName = std::string(weightNameBuffer); + + animatedProperty.mKeyFrames = KeyFrames::New(); + + // Set first frame value as first keyframe (gltf animation spec) + if(input.mCount > 0 && !Dali::EqualsZero(inputDataBuffer[0])) + { + animatedProperty.mKeyFrames.Add(0.0f, outputDataBuffer[weightIndex]); + } + + for(uint32_t i = 0; i < input.mCount; ++i) + { + animatedProperty.mKeyFrames.Add(inputDataBuffer[i] / duration, outputDataBuffer[i * endWeightIndex + weightIndex]); + } + + animatedProperty.mTimePeriod = {0.f, duration}; + } + + return duration; +} + +void ConvertAnimations(const gltf2::Document& doc, ConversionContext& context) +{ + auto& output = context.mOutput; + + output.mAnimationDefinitions.reserve(output.mAnimationDefinitions.size() + doc.mAnimations.size()); + + for(const auto& animation : doc.mAnimations) + { + AnimationDefinition animationDef; + + if(!animation.mName.empty()) + { + animationDef.mName = animation.mName; + } + + uint32_t numberOfProperties = 0u; + for(const auto& channel : animation.mChannels) + { + if(channel.mTarget.mPath == gltf2::Animation::Channel::Target::WEIGHTS) + { + numberOfProperties += channel.mSampler->mOutput->mCount / channel.mSampler->mInput->mCount; + } + else + { + numberOfProperties++; + } + } + animationDef.mProperties.resize(numberOfProperties); + + Index propertyIndex = 0u; + for(const auto& channel : animation.mChannels) + { + Index nodeIndex = context.mNodeIndices.GetRuntimeId(channel.mTarget.mNode.GetIndex()); + float duration = 0.f; + + switch(channel.mTarget.mPath) + { + case gltf2::Animation::Channel::Target::TRANSLATION: + { + AnimatedProperty& animatedProperty = animationDef.mProperties[propertyIndex]; + + animatedProperty.mNodeIndex = nodeIndex; + animatedProperty.mPropertyName = POSITION_PROPERTY; + + animatedProperty.mKeyFrames = KeyFrames::New(); + duration = LoadKeyFrames(context, channel, animatedProperty.mKeyFrames, channel.mTarget.mPath); + + animatedProperty.mTimePeriod = {0.f, duration}; + break; + } + case gltf2::Animation::Channel::Target::ROTATION: + { + AnimatedProperty& animatedProperty = animationDef.mProperties[propertyIndex]; + + animatedProperty.mNodeIndex = nodeIndex; + animatedProperty.mPropertyName = ORIENTATION_PROPERTY; + + animatedProperty.mKeyFrames = KeyFrames::New(); + duration = LoadKeyFrames(context, channel, animatedProperty.mKeyFrames, channel.mTarget.mPath); + + animatedProperty.mTimePeriod = {0.f, duration}; + break; + } + case gltf2::Animation::Channel::Target::SCALE: + { + AnimatedProperty& animatedProperty = animationDef.mProperties[propertyIndex]; + + animatedProperty.mNodeIndex = nodeIndex; + animatedProperty.mPropertyName = SCALE_PROPERTY; + + animatedProperty.mKeyFrames = KeyFrames::New(); + duration = LoadKeyFrames(context, channel, animatedProperty.mKeyFrames, channel.mTarget.mPath); + + animatedProperty.mTimePeriod = {0.f, duration}; + break; + } + case gltf2::Animation::Channel::Target::WEIGHTS: + { + duration = LoadBlendShapeKeyFrames(context, channel, nodeIndex, propertyIndex, animationDef.mProperties); + + break; + } + default: + { + // nothing to animate. + break; + } + } + + animationDef.mDuration = std::max(duration, animationDef.mDuration); + + ++propertyIndex; + } + + output.mAnimationDefinitions.push_back(std::move(animationDef)); + } +} + +void ProcessSkins(const gltf2::Document& doc, ConversionContext& context) +{ + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skininversebindmatrices + // If an inverseBindMatrices accessor was provided, we'll load the joint data from the buffer, + // otherwise we'll set identity matrices for inverse bind pose. + struct IInverseBindMatrixProvider + { + virtual ~IInverseBindMatrixProvider() + { + } + virtual void Provide(Matrix& ibm) = 0; + }; + + struct InverseBindMatrixAccessor : public IInverseBindMatrixProvider + { + std::istream& mStream; + const uint32_t mElementSizeBytes; + + InverseBindMatrixAccessor(const gltf2::Accessor& accessor, ConversionContext& context) + : mStream(context.mOutput.mResources.mBuffers[accessor.mBufferView->mBuffer.GetIndex()].GetBufferStream()), + mElementSizeBytes(accessor.GetElementSizeBytes()) + { + DALI_ASSERT_DEBUG(accessor.mType == gltf2::AccessorType::MAT4 && accessor.mComponentType == gltf2::Component::FLOAT); + + if(!mStream.rdbuf()->in_avail()) + { + DALI_LOG_ERROR("Failed to load from stream\n"); + } + mStream.clear(); + mStream.seekg(accessor.mBufferView->mByteOffset + accessor.mByteOffset, mStream.beg); + } + + virtual void Provide(Matrix& ibm) override + { + DALI_ASSERT_ALWAYS(mStream.read(reinterpret_cast(ibm.AsFloat()), static_cast(static_cast(mElementSizeBytes)))); + } + }; + + struct DefaultInverseBindMatrixProvider : public IInverseBindMatrixProvider + { + virtual void Provide(Matrix& ibm) override + { + ibm = Matrix::IDENTITY; + } + }; + + auto& resources = context.mOutput.mResources; + resources.mSkeletons.reserve(doc.mSkins.size()); + + for(auto& skin : doc.mSkins) + { + std::unique_ptr ibmProvider; + if(skin.mInverseBindMatrices) + { + ibmProvider.reset(new InverseBindMatrixAccessor(*skin.mInverseBindMatrices, context)); + } + else + { + ibmProvider.reset(new DefaultInverseBindMatrixProvider()); + } + + SkeletonDefinition skeleton; + if(skin.mSkeleton.GetIndex() != INVALID_INDEX) + { + skeleton.mRootNodeIdx = context.mNodeIndices.GetRuntimeId(skin.mSkeleton.GetIndex()); + } + + skeleton.mJoints.resize(skin.mJoints.size()); + auto iJoint = skeleton.mJoints.begin(); + for(auto& joint : skin.mJoints) + { + iJoint->mNodeIdx = context.mNodeIndices.GetRuntimeId(joint.GetIndex()); + + ibmProvider->Provide(iJoint->mInverseBindMatrix); + + ++iJoint; + } + + resources.mSkeletons.push_back(std::move(skeleton)); + } +} + +void ProduceShaders(ShaderDefinitionFactory& shaderFactory, Dali::Scene3D::Loader::SceneDefinition& scene) +{ + uint32_t nodeCount = scene.GetNodeCount(); + for(uint32_t i = 0; i < nodeCount; ++i) + { + auto nodeDef = scene.GetNode(i); + for(auto& renderable : nodeDef->mRenderables) + { + if(shaderFactory.ProduceShader(*renderable) == INVALID_INDEX) + { + DALI_LOG_ERROR("Fail to produce shader\n"); + } + } + } +} + +void SetObjectReaders() +{ + json::SetObjectReader(BUFFER_READER); + json::SetObjectReader(BUFFER_VIEW_READER); + json::SetObjectReader(BUFFER_VIEW_CLIENT_READER); + json::SetObjectReader(COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER); + json::SetObjectReader(ACCESSOR_SPARSE_READER); + json::SetObjectReader(ACCESSOR_READER); + json::SetObjectReader(IMAGE_READER); + json::SetObjectReader(SAMPLER_READER); + json::SetObjectReader(TEXURE_READER); + json::SetObjectReader(TEXURE_INFO_READER); + json::SetObjectReader(MATERIAL_PBR_READER); + json::SetObjectReader(MATERIAL_SPECULAR_READER); + json::SetObjectReader(MATERIAL_IOR_READER); + json::SetObjectReader(MATERIAL_EXTENSION_READER); + json::SetObjectReader(MATERIAL_READER); + json::SetObjectReader(MESH_PRIMITIVE_READER); + json::SetObjectReader(MESH_READER); + json::SetObjectReader(SKIN_READER); + json::SetObjectReader(CAMERA_PERSPECTIVE_READER); + json::SetObjectReader(CAMERA_ORTHOGRAPHIC_READER); + json::SetObjectReader(CAMERA_READER); + json::SetObjectReader(NODE_READER); + json::SetObjectReader(ANIMATION_SAMPLER_READER); + json::SetObjectReader(ANIMATION_TARGET_READER); + json::SetObjectReader(ANIMATION_CHANNEL_READER); + json::SetObjectReader(ANIMATION_READER); + json::SetObjectReader(SCENE_READER); +} + +void SetDefaultEnvironmentMap(const gltf2::Document& doc, ConversionContext& context) +{ + EnvironmentDefinition envDef; + envDef.mUseBrdfTexture = true; + envDef.mIblIntensity = Scene3D::Loader::EnvironmentDefinition::GetDefaultIntensity(); + context.mOutput.mResources.mEnvironmentMaps.push_back({std::move(envDef), EnvironmentDefinition::Textures()}); +} + +void InitializeGltfLoader() +{ + static Dali::Mutex gInitializeMutex; + // Set ObjectReader only once (for all gltf loading). + static bool setObjectReadersRequired = true; + { + Mutex::ScopedLock lock(gInitializeMutex); + if(setObjectReadersRequired) + { + // NOTE: only referencing own, anonymous namespace, const objects; the pointers will never need to change. + SetObjectReaders(); + setObjectReadersRequired = false; + } + } +} + +const std::string_view GetRendererModelIdentification() +{ + return MRENDERER_MODEL_IDENTIFICATION; +} + +void ReadDocument(const json_object_s& jsonObject, gltf2::Document& document) +{ + DOCUMENT_READER.Read(jsonObject, document); +} + +void ReadDocumentFromParsedData(const json_object_s& jsonObject, gltf2::Document& document) +{ + static Dali::Mutex gReadMutex; + Mutex::ScopedLock lock(gReadMutex); + gt::SetRefReaderObject(document); + Gltf2Util::ReadDocument(jsonObject, document); +} + +bool GenerateDocument(json::unique_ptr& root, gt::Document& document, bool& isMRendererModel) +{ + auto& rootObj = js::Cast(*root); + auto jsAsset = js::FindObjectChild("asset", rootObj); + + auto jsAssetVersion = js::FindObjectChild("version", js::Cast(*jsAsset)); + if(jsAssetVersion) + { + document.mAsset.mVersion = js::Read::StringView(*jsAssetVersion); + } + + auto jsAssetGenerator = js::FindObjectChild("generator", js::Cast(*jsAsset)); + if(jsAssetGenerator) + { + document.mAsset.mGenerator = js::Read::StringView(*jsAssetGenerator); + isMRendererModel = (document.mAsset.mGenerator.find(Gltf2Util::GetRendererModelIdentification().data()) != std::string_view::npos); + } + + Gltf2Util::InitializeGltfLoader(); + Gltf2Util::ReadDocumentFromParsedData(rootObj, document); + + return true; +} + +void ConvertGltfToContext(gt::Document& document, Gltf2Util::ConversionContext& context, bool isMRendererModel) +{ + Dali::Scene3D::Loader::ShaderDefinitionFactory shaderFactory; + shaderFactory.SetResources(context.mOutput.mResources); + + Gltf2Util::ConvertBuffers(document, context); + Gltf2Util::ConvertMaterials(document, context); + Gltf2Util::ConvertMeshes(document, context); + Gltf2Util::ConvertNodes(document, context, isMRendererModel); + Gltf2Util::ConvertAnimations(document, context); + Gltf2Util::ProcessSkins(document, context); + Gltf2Util::ProduceShaders(shaderFactory, context.mOutput.mScene); + context.mOutput.mScene.EnsureUniqueSkinningShaderInstances(context.mOutput.mResources); + + // Set Default Environment map + Gltf2Util::SetDefaultEnvironmentMap(document, context); +} + +} // namespace Gltf2Util + +} // namespace Internal +} // namespace Loader +} // namespace Scene3D +} // namespace Dali diff --git a/dali-scene3d/internal/loader/gltf2-util.h b/dali-scene3d/internal/loader/gltf2-util.h new file mode 100644 index 0000000..d84ce05 --- /dev/null +++ b/dali-scene3d/internal/loader/gltf2-util.h @@ -0,0 +1,135 @@ +#ifndef DALI_SCENE3D_LOADER_GLTF2_UTIL_H +#define DALI_SCENE3D_LOADER_GLTF2_UTIL_H +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// INTERNAL INCLUDES +#include +#include +#include +#include +#include + +// EXTERNAL INCLUDES +#include +#include + +namespace gt = gltf2; +namespace js = json; + +namespace Dali +{ +namespace Scene3D +{ +namespace Loader +{ +namespace Internal +{ +namespace Gltf2Util +{ + +struct NodeMapping +{ + Index gltfIdx; + Index runtimeIdx; + + bool operator<(Index gltfIdx) const + { + return this->gltfIdx < gltfIdx; + } +}; + +class NodeIndexMapper +{ +public: + NodeIndexMapper() = default; + NodeIndexMapper(const NodeIndexMapper&) = delete; + NodeIndexMapper& operator=(const NodeIndexMapper&) = delete; + + ///@brief Registers a mapping of the @a gltfIdx of a node to its @a runtimeIdx . + ///@note If the indices are the same, the registration is omitted, in order to + /// save growing a vector. + void RegisterMapping(Index gltfIdx, Index runtimeIdx) + { + if(gltfIdx != runtimeIdx) + { + auto iInsert = std::lower_bound(mNodes.begin(), mNodes.end(), gltfIdx); + DALI_ASSERT_DEBUG(iInsert == mNodes.end() || iInsert->gltfIdx != gltfIdx); + mNodes.insert(iInsert, NodeMapping{gltfIdx, runtimeIdx}); + } + } + + ///@brief Retrieves the runtime index of a Node, mapped to the given @a gltfIdx. + Index GetRuntimeId(Index gltfIdx) const + { + auto iFind = std::lower_bound(mNodes.begin(), mNodes.end(), gltfIdx); // using custom operator< + return (iFind != mNodes.end() && iFind->gltfIdx == gltfIdx) ? iFind->runtimeIdx : gltfIdx; + } + +private: + std::vector mNodes; +}; + +struct ConversionContext +{ + LoadResult& mOutput; + + std::string mPath; + Index mDefaultMaterial; + + std::vector mMeshIds; + NodeIndexMapper mNodeIndices; +}; + +void ConvertBuffers(const gt::Document& doc, ConversionContext& context); + +void ConvertMaterials(const gt::Document& doc, ConversionContext& context); + +void ConvertMeshes(const gt::Document& doc, ConversionContext& context); + +void ConvertCamera(const gt::Camera& camera, CameraParameters& camParams); + +void ConvertNodes(const gt::Document& doc, ConversionContext& context, bool isMRendererModel); + +void ConvertAnimations(const gt::Document& doc, ConversionContext& context); + +void ProcessSkins(const gt::Document& doc, ConversionContext& context); + +void ProduceShaders(ShaderDefinitionFactory& shaderFactory, SceneDefinition& scene); + +void SetDefaultEnvironmentMap(const gt::Document& doc, ConversionContext& context); + +const std::string_view GetRendererModelIdentification(); + +void ReadDocument(const json_object_s& jsonObject, gt::Document& document); + +void InitializeGltfLoader(); + +void ReadDocumentFromParsedData(const json_object_s& jsonObject, gltf2::Document& document); + +bool GenerateDocument(json::unique_ptr& root, gt::Document& document, bool& isMRendererModel); + +void ConvertGltfToContext(gt::Document& document, Gltf2Util::ConversionContext& context, bool isMRendererModel); + +} // namespace Gltf2Util + +} // namespace Internal +} // namespace Loader +} // namespace Scene3D +} // namespace Dali + +#endif // DALI_SCENE3D_LOADER_GLTF2_UTIL_H diff --git a/dali-scene3d/public-api/loader/buffer-definition.cpp b/dali-scene3d/public-api/loader/buffer-definition.cpp index 1704927..2b85631 100644 --- a/dali-scene3d/public-api/loader/buffer-definition.cpp +++ b/dali-scene3d/public-api/loader/buffer-definition.cpp @@ -42,6 +42,14 @@ struct BufferDefinition::Impl std::shared_ptr stream; }; +BufferDefinition::BufferDefinition(std::vector& buffer) +: mImpl{new BufferDefinition::Impl} +{ + mImpl.get()->buffer = std::move(buffer); + mImpl.get()->stream = std::make_shared(reinterpret_cast(mImpl.get()->buffer.data()), mImpl.get()->buffer.size(), FileStream::READ | FileStream::BINARY); + mIsEmbedded = true; +} + BufferDefinition::BufferDefinition() : mImpl{new BufferDefinition::Impl} { diff --git a/dali-scene3d/public-api/loader/buffer-definition.h b/dali-scene3d/public-api/loader/buffer-definition.h index 8f6952a..808d5a0 100644 --- a/dali-scene3d/public-api/loader/buffer-definition.h +++ b/dali-scene3d/public-api/loader/buffer-definition.h @@ -24,6 +24,7 @@ #include #include #include +#include namespace Dali { @@ -40,6 +41,8 @@ struct DALI_SCENE3D_API BufferDefinition using Vector = std::vector; BufferDefinition(); + BufferDefinition(std::vector& buffer); + ~BufferDefinition(); BufferDefinition(const BufferDefinition& other) = default; diff --git a/dali-scene3d/public-api/loader/model-loader.cpp b/dali-scene3d/public-api/loader/model-loader.cpp index 5ca930f..219f0a9 100644 --- a/dali-scene3d/public-api/loader/model-loader.cpp +++ b/dali-scene3d/public-api/loader/model-loader.cpp @@ -26,6 +26,7 @@ // INTERNAL INCLUDES #include #include +#include #include namespace Dali @@ -38,6 +39,7 @@ namespace { static constexpr std::string_view OBJ_EXTENSION = ".obj"; static constexpr std::string_view GLTF_EXTENSION = ".gltf"; +static constexpr std::string_view GLB_EXTENSION = ".glb"; static constexpr std::string_view DLI_EXTENSION = ".dli"; static constexpr std::string_view METADATA_EXTENSION = "metadata"; } // namespace @@ -118,6 +120,10 @@ void ModelLoader::CreateModelLoader() { mImpl = std::make_shared(); } + else if(extension == GLB_EXTENSION) + { + mImpl = std::make_shared(); + } else { DALI_LOG_ERROR("Not supported model format : %s\n", extension.c_str()); -- 2.7.4