/* * Copyright (c) 2021 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. * */ // HEADER #include "gltf-scene.h" // EXTERNAL INCLUDES #include namespace { // string contains enum type index encoded matching glTFAttributeType const std::vector GLTF_STR_ATTRIBUTE_TYPE = { "POSITION", "NORMAL", "TEXCOORD_0"}; const std::vector> GLTF_STR_COMPONENT_TYPE = { std::make_pair("VEC2", 2), std::make_pair("VEC3", 3), std::make_pair("VEC4", 4), std::make_pair("SCALAR", 1)}; glTFAttributeType glTFAttributeTypeStrToEnum(const std::string& name) { int index = -1; auto iter = std::find_if(GLTF_STR_ATTRIBUTE_TYPE.begin(), GLTF_STR_ATTRIBUTE_TYPE.end(), [name, &index](const std::string& val) { index++; return val == name; }); if(iter == GLTF_STR_ATTRIBUTE_TYPE.end()) { return glTFAttributeType::UNDEFINED; } return static_cast(index); } uint32_t glTFComponentTypeStrToNum(const std::string& name) { auto iter = std::find_if(GLTF_STR_COMPONENT_TYPE.begin(), GLTF_STR_COMPONENT_TYPE.end(), [name](const std::pair& val) { return val.first == name; }); if(iter == GLTF_STR_COMPONENT_TYPE.end()) { return 0; } return iter->second; } template struct JsonResult { bool success; T result; operator T() const { return static_cast(result); } }; template<> struct JsonResult { bool success; bool result; operator bool() const { return result; } }; template JsonResult JsonFindValue(const picojson::object& object, const std::string& name) { auto iter = find_if(object.begin(), object.end(), [name](decltype(*object.begin())& item) { return item.first == name; }); if(iter == object.end()) { return {false}; } return {true, iter->second}; }; template JsonResult JsonGetValue(const picojson::value& val, const std::string& name, const Converted& onFail) { if(val.contains(name)) { return {true, static_cast(val.get(name).get())}; } return {false, onFail}; } template bool JsonGetValueOut(const picojson::value& val, const std::string& name, Converted& writeOut) { if(val.contains(name)) { writeOut = static_cast(val.get(name).get()); return true; } return false; } /** * Safe json value accessor */ template JsonResult> JsonGetArray(const picojson::value& val, const std::string& name, std::vector valueOnFail = {}) { if(val.contains(name)) { const auto& array = val.get(name).get(); std::vector result{}; for(const auto& item : array) { result.emplace_back(static_cast(item.get())); } return {true, result}; } return {false, valueOnFail}; } } // namespace glTF::glTF(const std::string& filename) { LoadFromFile(filename); ParseJSON(); } void glTF::LoadFromFile(const std::string& filename) { std::string jsonFile(filename); jsonFile += ".gltf"; std::string binFile(filename); binFile += ".bin"; // load binary GLTF_LOG("LoadFromFile: %s", binFile.c_str()); mBuffer = LoadFile(binFile); jsonBuffer = LoadFile(jsonFile); jsonBuffer.push_back('\0'); // It should be null terminated. // Log errors if(mBuffer.empty()) { GLTF_LOG("Error, buffer empty!"); } else { GLTF_LOG("GLTF[BIN]: %s loaded, size = %d", binFile.c_str(), int(mBuffer.size())); } if(jsonBuffer.empty()) { GLTF_LOG("Error, buffer GLTF empty!"); } else { GLTF_LOG("GLTF: %s loaded, size = %d", binFile.c_str(), int(jsonBuffer.size())); } // Abort if errors if(jsonBuffer.empty() || mBuffer.empty()) { return; } // parse json auto err = picojson::parse(jsonNode, std::string(reinterpret_cast(jsonBuffer.data()))); if(!err.empty()) { GLTF_LOG("GLTF: Error parsing %s, error: %s", jsonFile.c_str(), err.c_str()); return; } else { GLTF_LOG("GLTF: %s loaded, size = %d", jsonFile.c_str(), int(jsonBuffer.size())); } } bool glTF::ParseJSON() { if(!jsonNode.is()) { return false; } // Add dummy first node to nodes (scene node) mNodes.emplace_back(); // Sources for textures to be resolved later std::vector textureSources{}; std::vector images{}; for(auto& val : jsonNode.get()) { GLTF_LOG("node: %s", val.first.c_str()); // Parse bufferviews if(val.first == "bufferViews") { auto bufferViews = val.second; for(auto& view : bufferViews.get()) { auto bufferIndex = uint32_t(view.get("buffer").get()); auto byteLength = uint32_t(view.get("byteLength").get()); auto byteOffset = uint32_t(view.get("byteOffset").get()); glTF_BufferView bufferView{}; bufferView.bufferIndex = bufferIndex; bufferView.byteLength = byteLength; bufferView.byteOffset = byteOffset; mBufferViews.emplace_back(bufferView); } } // parse accessors else if(val.first == "accessors") { for(const auto& accessor : val.second.get()) { auto gltfAccessor = glTF_Accessor{}; gltfAccessor.bufferView = uint32_t(accessor.get("bufferView").get()); gltfAccessor.componentType = uint32_t(accessor.get("componentType").get()); gltfAccessor.count = uint32_t(accessor.get("count").get()); gltfAccessor.type = accessor.get("type").get(); gltfAccessor.componentSize = glTFComponentTypeStrToNum(gltfAccessor.type); mAccessors.emplace_back(gltfAccessor); } } // parse meshes else if(val.first == "meshes") { for(const auto& mesh : val.second.get()) { glTF_Mesh gltfMesh{}; gltfMesh.name = mesh.get("name").get(); // get primitives (in this implementation assuming single mesh consists of // one and only one primitive) const auto& primitive = mesh.get("primitives").get()[0]; const auto& attrs = primitive.get("attributes").get(); for(const auto& attr : attrs) { auto type = glTFAttributeTypeStrToEnum(attr.first); auto bvIndex = uint32_t(attr.second.get()); gltfMesh.attributes.emplace_back(std::make_pair(type, bvIndex)); GLTF_LOG("GLTF: ATTR: type: %d, index: %d", int(type), int(bvIndex)); } gltfMesh.indices = uint32_t(primitive.get("indices").get()); gltfMesh.material = uint32_t(primitive.get("material").get()); mMeshes.emplace_back(gltfMesh); } } // parse cameras else if(val.first == "cameras") { glTF_Camera tgifCamera{}; for(const auto& camera : val.second.get()) { tgifCamera.name = camera.get("name").to_str(); tgifCamera.isPerspective = (camera.get("type").to_str() == "perspective"); if(tgifCamera.isPerspective) { const auto& perspective = camera.get("perspective"); tgifCamera.yfov = static_cast(perspective.get("yfov").get()); tgifCamera.zfar = static_cast(perspective.get("zfar").get()); tgifCamera.znear = static_cast(perspective.get("znear").get()); } mCameras.emplace_back(tgifCamera); } } // parse nodes else if(val.first == "nodes") { auto nodeIndex = 1u; for(const auto& node : val.second.get()) { glTF_Node gltfNode{}; gltfNode.name = node.get("name").to_str(); auto rotation = JsonGetArray(node, "rotation", {0.0f, 0.0f, 0.0f, 1.0f}); auto translation = JsonGetArray(node, "translation", {0.0f, 0.0f, 0.0f}); auto scale = JsonGetArray(node, "scale", {1.0f, 1.0f, 1.0f}); std::copy(rotation.result.begin(), rotation.result.end(), gltfNode.rotationQuaternion); std::copy(translation.result.begin(), translation.result.end(), gltfNode.translation); std::copy(scale.result.begin(), scale.result.end(), gltfNode.scale); auto children = JsonGetArray(node, "children"); if(children.success) { gltfNode.children = std::move(children.result); } gltfNode.index = nodeIndex; gltfNode.cameraId = 0xffffffff; gltfNode.meshId = 0xffffffff; auto cameraId = JsonGetValue(node, "camera", 0xffffffff); if(cameraId.success) { gltfNode.cameraId = cameraId.result; } auto meshId = JsonGetValue(node, "mesh", 0xffffffff); if(meshId.success) { gltfNode.meshId = meshId.result; } mNodes.emplace_back(gltfNode); ++nodeIndex; } } // parse scenes, note: only first scene is being parsed else if(val.first == "scenes") { auto& sceneNode = mNodes[0]; const auto& scene = val.second.get()[0]; sceneNode.name = JsonGetValue(scene, "name", std::string()); auto result = JsonGetArray(scene, "nodes"); sceneNode.children = result.result; sceneNode.index = 0; } else if(val.first == "materials") { for(const auto& node : val.second.get()) { // Get pbr material, base color texture glTF_Material material{}; material.doubleSided = JsonGetValue(node, "doubleSided", false).result; material.name = JsonGetValue(node, "name", std::string()).result; if(node.contains("pbrMetallicRoughness")) { const auto& node0 = node.get("pbrMetallicRoughness"); if(node0.contains("baseColorTexture")) { const auto& node1 = node0.get("baseColorTexture"); auto index = uint32_t(node1.get("index").get()); auto texCoord = uint32_t(node1.get("texCoord").get()); material.pbrMetallicRoughness.enabled = true; material.pbrMetallicRoughness.baseTextureColor.index = index; material.pbrMetallicRoughness.baseTextureColor.texCoord = texCoord; } } mMaterials.emplace_back(material); } } else if(val.first == "textures") { for(const auto& item : val.second.get()) { auto source = JsonGetValue(item, "source", 0xffffffff); textureSources.emplace_back(source.result); } } else if(val.first == "images") { for(const auto& item : val.second.get()) { glTF_Texture tex{}; JsonGetValueOut(item, "name", tex.name); JsonGetValueOut(item, "uri", tex.uri); images.emplace_back(tex); } } } // Resolve cross-referencing for(const auto& source : textureSources) { mTextures.emplace_back(images[source]); } return true; } glTF_Buffer glTF::LoadFile(const std::string& filename) { Dali::FileStream fileStream(filename.c_str(), Dali::FileStream::READ | Dali::FileStream::BINARY); FILE* fin = fileStream.GetFile(); std::vector buffer; if(fin) { if(fseek(fin, 0, SEEK_END)) { return {}; } auto size = ftell(fin); if(fseek(fin, 0, SEEK_SET)) { return {}; } buffer.resize(unsigned(size)); auto result = fread(buffer.data(), 1, size_t(size), fin); if(result != size_t(size)) { GLTF_LOG("LoadFile: Result: %d", int(result)); // return empty buffer return {}; } } else { GLTF_LOG("LoadFile: Can't open file: errno = %d", errno); } return buffer; } std::vector glTF::GetMeshes() const { std::vector retval; for(auto& mesh : mMeshes) { retval.emplace_back(&mesh); } return retval; } std::vector glTF::GetCameras() { std::vector cameras; for(const auto& cam : mCameras) { cameras.emplace_back(&cam); } return cameras; } std::vector glTF::GetMeshAttributeBuffer(const glTF_Mesh& mesh, const std::vector& attrTypes) { // find buffer views struct Data { uint32_t accessorIndex{0u}; uint32_t byteStride{0u}; char* srcPtr{nullptr}; }; std::vector data{}; for(const auto& attrType : attrTypes) { (void)std::find_if(mesh.attributes.begin(), mesh.attributes.end(), [&data, &attrType](const std::pair& item) { if(item.first == attrType) { data.emplace_back(); data.back().accessorIndex = item.second; } return false; }); } if(data.empty()) { return {}; } // number of attributes is same for the whole mesh so using very first // accessor glTF_Buffer retval{}; // data is interleaved if(data.size() > 1) { auto attributeCount = mAccessors[data[0].accessorIndex].count; // / mAccessors[data[0].accessorIndex].componentSize; uint32_t attributeStride = 0; // now find buffer view stride for particular accessor for(auto& item : data) { auto& accessor = mAccessors[item.accessorIndex]; // Update byte stride for this buffer view auto& bufferView = mBufferViews[accessor.bufferView]; item.byteStride = bufferView.byteLength / attributeCount; attributeStride += item.byteStride; item.srcPtr = reinterpret_cast(mBuffer.data()) + bufferView.byteOffset; } // now allocate final buffer and interleave data retval.resize(attributeStride * attributeCount); auto* dstPtr = retval.data(); for(auto i = 0u; i < attributeCount; ++i) { for(auto& item : data) { std::copy(item.srcPtr, item.srcPtr + item.byteStride, reinterpret_cast(dstPtr)); dstPtr += item.byteStride; item.srcPtr += item.byteStride; } } } else // copy data directly as single buffer { auto& bufferView = mBufferViews[mAccessors[data[0].accessorIndex].bufferView]; retval.resize(bufferView.byteLength); std::copy(mBuffer.begin() + bufferView.byteOffset, mBuffer.begin() + bufferView.byteOffset + bufferView.byteLength, retval.begin()); } return retval; } const glTF_Mesh* glTF::FindMeshByName(const std::string& name) const { for(const auto& mesh : mMeshes) { if(mesh.name == name) return &mesh; } return nullptr; } uint32_t glTF::GetMeshAttributeCount(const glTF_Mesh* mesh) const { const auto& accessor = mAccessors[mesh->attributes[0].second]; return accessor.count; // / accessor.componentSize; } std::vector glTF::GetMeshIndexBuffer(const glTF_Mesh* mesh) const { // check GL component type const auto& accessor = mAccessors[mesh->indices]; if(accessor.componentType == 0x1403) // GL_UNSIGNED_SHORT { std::vector retval{}; retval.resize(accessor.count); const auto& bufferView = mBufferViews[accessor.bufferView]; const auto* srcPtr = reinterpret_cast(reinterpret_cast(mBuffer.data()) + bufferView.byteOffset); std::copy(srcPtr, srcPtr + accessor.count, retval.data()); return retval; } return {}; } const glTF_Node* glTF::FindNodeByName(const std::string& name) const { auto iter = std::find_if(mNodes.begin(), mNodes.end(), [name](const glTF_Node& node) { return !name.compare(node.name); }); if(iter == mNodes.end()) { return nullptr; } return &*iter; } const std::vector& glTF::GetMaterials() const { return mMaterials; } const std::vector& glTF::GetTextures() const { return mTextures; }