diff --git a/CHANGELOG.md b/CHANGELOG.md index 688f09fc..7689325e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,12 @@ If a copy of the MPL was not distributed with this file, You can obtain one at h --> +## [1.10.3] Skinning Import Bugfix + +### Fixes +* Set all target `MeshNodes` in the `Skin` object when importing skin objects with multiple target nodes from gltf files. + + ## [1.10.2] Ramses Logic Update ### Changes diff --git a/CMakeLists.txt b/CMakeLists.txt index dfdc7d69..29ac1546 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ cmake_minimum_required(VERSION 3.19) SET(CMAKE_CONFIGURATION_TYPES "Debug;RelWithDebInfo") -project(RaCoOS VERSION 1.10.2) +project(RaCoOS VERSION 1.10.3) SET(RACO_RELEASE_DIRECTORY ${CMAKE_BINARY_DIR}/release) diff --git a/components/libApplication/tests/CMakeLists.txt b/components/libApplication/tests/CMakeLists.txt index a4395d72..a66e8400 100644 --- a/components/libApplication/tests/CMakeLists.txt +++ b/components/libApplication/tests/CMakeLists.txt @@ -40,6 +40,7 @@ raco_package_test_resources_process( meshes/InterpolationTest/InterpolationTest.gltf meshes/InterpolationTest/interpolation.bin meshes/InterpolationTest/l.jpg + meshes/SimpleSkin/SimpleSkin-multi-target.gltf meshes/Duck.glb meshes/meshless.gltf meshes/negativeScaleQuad.gltf diff --git a/components/libApplication/tests/RaCoApplication_test.cpp b/components/libApplication/tests/RaCoApplication_test.cpp index a9dedfc6..998fbfaf 100644 --- a/components/libApplication/tests/RaCoApplication_test.cpp +++ b/components/libApplication/tests/RaCoApplication_test.cpp @@ -763,6 +763,27 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphMeshNodesDontReferenceDeselec } } +TEST_F(RaCoApplicationFixture, importglTFScenegraphMultiTargetSkin) { + using namespace raco; + core::MeshDescriptor desc; + desc.absPath = (test_path() / "meshes/SimpleSkin/SimpleSkin-multi-target.gltf").string(); + desc.bakeAllSubmeshes = false; + + auto [scenegraph, dummyCacheEntry] = raco::getMeshSceneGraphWithHandler(commandInterface().meshCache(), desc); + commandInterface().insertAssetScenegraph(scenegraph, desc.absPath, nullptr); + + auto skin = select(project().instances()); + auto node_0 = select(project().instances(), "nodes_0"); + auto node_1 = select(project().instances(), "nodes_1"); + auto node_2 = select(project().instances(), "nodes_2"); + auto node_3 = select(project().instances(), "nodes_3"); + + EXPECT_EQ(skin->targets_->size(), 2); + EXPECT_EQ(skin->targets_->asVector(), std::vector({node_0, node_3})); + EXPECT_EQ(skin->joints_->size(), 2); + EXPECT_EQ(skin->joints_->asVector(), std::vector({node_1, node_2})); +} + TEST_F(RaCoApplicationFixture, LuaScriptRuntimeErrorCausesInformationForAllScripts) { auto* commandInterface = application.activeRaCoProject().commandInterface(); diff --git a/components/libMeshLoader/src/glTFFileLoader.cpp b/components/libMeshLoader/src/glTFFileLoader.cpp index c0d82daa..490918d8 100644 --- a/components/libMeshLoader/src/glTFFileLoader.cpp +++ b/components/libMeshLoader/src/glTFFileLoader.cpp @@ -220,13 +220,15 @@ void glTFFileLoader::importSkins() { const auto& skin = scene_->skins[index]; std::string name = skin.name.empty() ? fmt::format("skin_{}", index) : skin.name; - // Find node by searching through all nodes in scene - auto it = std::find_if(scene_->nodes.begin(), scene_->nodes.end(), [index](const tinygltf::Node& node) { - return index == node.skin; - }); - if (it != scene_->nodes.end()) { - int nodeIndex = it - scene_->nodes.begin(); - sceneGraph_->skins.emplace_back(raco::core::SkinDescription{name, nodeIndex, skin.joints}); + // Find target nodes by searching through all nodes in scene + std::vector targets; + for (int nodeIndex = 0; nodeIndex < scene_->nodes.size(); nodeIndex++) { + if (scene_->nodes[nodeIndex].skin == index) { + targets.emplace_back(nodeIndex); + } + } + if (!targets.empty()) { + sceneGraph_->skins.emplace_back(core::SkinDescription{name, targets, skin.joints}); } } } diff --git a/datamodel/libCore/include/core/MeshCacheInterface.h b/datamodel/libCore/include/core/MeshCacheInterface.h index 1fdd60ff..5cc1302a 100644 --- a/datamodel/libCore/include/core/MeshCacheInterface.h +++ b/datamodel/libCore/include/core/MeshCacheInterface.h @@ -242,7 +242,7 @@ struct MeshAnimation { struct SkinDescription { std::string name; - int meshNodeIndex; + std::vector meshNodeIndices; std::vector jointNodeIndices; }; diff --git a/datamodel/libCore/src/Context.cpp b/datamodel/libCore/src/Context.cpp index f60d098d..edec72dd 100644 --- a/datamodel/libCore/src/Context.cpp +++ b/datamodel/libCore/src/Context.cpp @@ -1064,7 +1064,7 @@ void BaseContext::insertAssetScenegraph(const raco::core::MeshScenegraph& sceneg auto meshWithSameProperties = propertiesToMeshMap.find({false, static_cast(i), relativeFilePath.string()}); if (meshWithSameProperties == propertiesToMeshMap.end()) { LOG_DEBUG(log_system::CONTEXT, "Did not find existing local Mesh with same properties as asset mesh, creating one instead..."); - auto ¤tSubmesh = meshScenegraphMeshes.emplace_back(createObject(raco::user_types::Mesh::typeDescription.typeName, *scenegraph.meshes[i])); + auto& currentSubmesh = meshScenegraphMeshes.emplace_back(createObject(raco::user_types::Mesh::typeDescription.typeName, *scenegraph.meshes[i])); auto currentSubmeshHandle = ValueHandle{currentSubmesh}; set(currentSubmeshHandle.get("bakeMeshes"), false); @@ -1184,10 +1184,9 @@ void BaseContext::insertAssetScenegraph(const raco::core::MeshScenegraph& sceneg } LOG_INFO(log_system::CONTEXT, "Scenegraph structure restored."); - LOG_INFO(log_system::CONTEXT, "Importing animation samplers..."); std::map> sceneChannels; - for (auto animIndex = 0; animIndex < scenegraph.animationSamplers.size(); ++animIndex) { + for (auto animIndex = 0; animIndex < scenegraph.animationSamplers.size(); ++animIndex) { auto& samplers = scenegraph.animationSamplers[animIndex]; for (auto samplerIndex = 0; samplerIndex < samplers.size(); ++samplerIndex) { auto& meshAnimSampler = scenegraph.animationSamplers.at(animIndex)[samplerIndex]; @@ -1208,7 +1207,6 @@ void BaseContext::insertAssetScenegraph(const raco::core::MeshScenegraph& sceneg LOG_DEBUG(log_system::CONTEXT, "Found existing local AnimationChannel '{}' with same properties as asset animation sampler, using this AnimationChannel...", *meshAnimSampler); sceneChannels[animIndex].emplace_back(samplerWithSameProperties->second); } - } } LOG_INFO(log_system::CONTEXT, "Animation samplers imported."); @@ -1247,7 +1245,7 @@ void BaseContext::insertAssetScenegraph(const raco::core::MeshScenegraph& sceneg continue; } - auto &linkEndNode = meshScenegraphNodes[channel.nodeIndex]; + auto& linkEndNode = meshScenegraphNodes[channel.nodeIndex]; ValueHandle linkEndProp; auto& animTargetProp = channel.targetPath; @@ -1287,16 +1285,18 @@ void BaseContext::insertAssetScenegraph(const raco::core::MeshScenegraph& sceneg } std::vector targetMeshNodes; - auto targetMeshNode = meshScenegraphNodes[sceneSkin->meshNodeIndex]; - if (targetMeshNode->isType()) { - targetMeshNodes.emplace_back(targetMeshNode); - } else { - auto submeshRootNode = targetMeshNode->children_->get(0)->asRef()->as(); - for (auto child : submeshRootNode->children_->asVector()) { - if (child->isType()) { - targetMeshNodes.emplace_back(child); - } else { - LOG_ERROR(log_system::CONTEXT, "Target child node is not a MeshNode '{}'", child->objectName()); + for (const auto& targetIndex : sceneSkin->meshNodeIndices) { + auto targetMeshNode = meshScenegraphNodes[targetIndex]; + if (targetMeshNode->isType()) { + targetMeshNodes.emplace_back(targetMeshNode); + } else { + auto submeshRootNode = targetMeshNode->children_->get(0)->asRef()->as(); + for (auto child : submeshRootNode->children_->asVector()) { + if (child->isType()) { + targetMeshNodes.emplace_back(child); + } else { + LOG_ERROR(log_system::CONTEXT, "Target child node is not a MeshNode '{}'", child->objectName()); + } } } } diff --git a/resources/meshes/SimpleSkin/SimpleSkin-multi-target.gltf b/resources/meshes/SimpleSkin/SimpleSkin-multi-target.gltf new file mode 100644 index 00000000..f6d958f4 --- /dev/null +++ b/resources/meshes/SimpleSkin/SimpleSkin-multi-target.gltf @@ -0,0 +1,135 @@ +{ + "scene" : 0, + "scenes" : [ { + "nodes" : [ 0, 1 ] + } ], + + "nodes" : [ { + "skin" : 0, + "mesh" : 0 + }, { + "children" : [ 2 ], + "translation" : [ 0.0, 1.0, 0.0 ] + }, { + "rotation" : [ 0.0, 0.0, 0.0, 1.0 ] + }, { + "skin" : 0, + "mesh" : 0, + "translation" : [1.0, 0.0, 0.0] + } ], + + "meshes" : [ { + "primitives" : [ { + "attributes" : { + "POSITION" : 1, + "JOINTS_0" : 2, + "WEIGHTS_0" : 3 + }, + "indices" : 0 + } ] + } ], + + "skins" : [ { + "inverseBindMatrices" : 4, + "joints" : [ 1, 2 ] + } ], + + "animations" : [ { + "channels" : [ { + "sampler" : 0, + "target" : { + "node" : 2, + "path" : "rotation" + } + } ], + "samplers" : [ { + "input" : 5, + "interpolation" : "LINEAR", + "output" : 6 + } ] + } ], + + "buffers" : [ { + "uri" : "data:application/gltf-buffer;base64,AAABAAMAAAADAAIAAgADAAUAAgAFAAQABAAFAAcABAAHAAYABgAHAAkABgAJAAgAAAAAvwAAAAAAAAAAAAAAPwAAAAAAAAAAAAAAvwAAAD8AAAAAAAAAPwAAAD8AAAAAAAAAvwAAgD8AAAAAAAAAPwAAgD8AAAAAAAAAvwAAwD8AAAAAAAAAPwAAwD8AAAAAAAAAvwAAAEAAAAAAAAAAPwAAAEAAAAAA", + "byteLength" : 168 + }, { + "uri" : "data:application/gltf-buffer;base64,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAQD8AAIA+AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAAAAgD4AAEA/AAAAAAAAAAAAAIA+AABAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAA=", + "byteLength" : 320 + }, { + "uri" : "data:application/gltf-buffer;base64,AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8=", + "byteLength" : 128 + }, { + "uri" : "data:application/gltf-buffer;base64,AAAAAAAAAD8AAIA/AADAPwAAAEAAACBAAABAQAAAYEAAAIBAAACQQAAAoEAAALBAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAPT9ND/0/TQ/AAAAAAAAAAD0/TQ/9P00PwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAPT9NL/0/TQ/AAAAAAAAAAD0/TS/9P00PwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAAAAAAAAAIA/", + "byteLength" : 240 + } ], + + "bufferViews" : [ { + "buffer" : 0, + "byteLength" : 48, + "target" : 34963 + }, { + "buffer" : 0, + "byteOffset" : 48, + "byteLength" : 120, + "target" : 34962 + }, { + "buffer" : 1, + "byteLength" : 320, + "byteStride" : 16 + }, { + "buffer" : 2, + "byteLength" : 128 + }, { + "buffer" : 3, + "byteLength" : 240 + } ], + + "accessors" : [ { + "bufferView" : 0, + "componentType" : 5123, + "count" : 24, + "type" : "SCALAR" + }, { + "bufferView" : 1, + "componentType" : 5126, + "count" : 10, + "type" : "VEC3", + "max" : [ 0.5, 2.0, 0.0 ], + "min" : [ -0.5, 0.0, 0.0 ] + }, { + "bufferView" : 2, + "componentType" : 5123, + "count" : 10, + "type" : "VEC4" + }, { + "bufferView" : 2, + "byteOffset" : 160, + "componentType" : 5126, + "count" : 10, + "type" : "VEC4" + }, { + "bufferView" : 3, + "componentType" : 5126, + "count" : 2, + "type" : "MAT4" + }, { + "bufferView" : 4, + "componentType" : 5126, + "count" : 12, + "type" : "SCALAR", + "max" : [ 5.5 ], + "min" : [ 0.0 ] + }, { + "bufferView" : 4, + "byteOffset" : 48, + "componentType" : 5126, + "count" : 12, + "type" : "VEC4", + "max" : [ 0.0, 0.0, 0.707, 1.0 ], + "min" : [ 0.0, 0.0, -0.707, 0.707 ] + } ], + + "asset" : { + "version" : "2.0" + } +} \ No newline at end of file