From 92d2a0c0fa1524c19e6299449de821895cb7ac39 Mon Sep 17 00:00:00 2001 From: seungho Date: Wed, 13 Jul 2022 11:28:17 +0900 Subject: [PATCH] Add bvh loader in scene-loader Change-Id: I51b859d6c7ad828c25e89d4ea3627bf3f56c6699 Signed-off-by: seungho --- automated-tests/resources/test.bvh | 20 ++ automated-tests/src/dali-scene3d/CMakeLists.txt | 1 + .../src/dali-scene3d/utc-Dali-BvhLoader.cpp | 103 ++++++ dali-scene3d/public-api/file.list | 1 + dali-scene3d/public-api/loader/bvh-loader.cpp | 345 +++++++++++++++++++++ dali-scene3d/public-api/loader/bvh-loader.h | 45 +++ 6 files changed, 515 insertions(+) create mode 100644 automated-tests/resources/test.bvh create mode 100644 automated-tests/src/dali-scene3d/utc-Dali-BvhLoader.cpp create mode 100644 dali-scene3d/public-api/loader/bvh-loader.cpp create mode 100644 dali-scene3d/public-api/loader/bvh-loader.h diff --git a/automated-tests/resources/test.bvh b/automated-tests/resources/test.bvh new file mode 100644 index 0000000..28bd0da --- /dev/null +++ b/automated-tests/resources/test.bvh @@ -0,0 +1,20 @@ +HIERARCHY +ROOT root +{ + OFFSET 0.0 -0.948831 1.32574 + CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + JOINT first + { + OFFSET -0.0 4.130377 -0.008512 + CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + End Site + { + OFFSET 0 0 0 + } + } +} +MOTION +Frames: 2 +Frame Time: 0.3 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 10.0 0.0 0.0 0.0 0.0 10.0 0.0 0.0 90.0 0.0 0.0 \ No newline at end of file diff --git a/automated-tests/src/dali-scene3d/CMakeLists.txt b/automated-tests/src/dali-scene3d/CMakeLists.txt index 030aa8b..d86c377 100755 --- a/automated-tests/src/dali-scene3d/CMakeLists.txt +++ b/automated-tests/src/dali-scene3d/CMakeLists.txt @@ -10,6 +10,7 @@ SET(TC_SOURCES utc-Dali-AlphaFunctionHelper.cpp utc-Dali-AnimationDefinition.cpp utc-Dali-AnimatedProperty.cpp + utc-Dali-BvhLoader.cpp utc-Dali-CameraParameters.cpp utc-Dali-CubeLoader.cpp utc-Dali-CubeMapLoader.cpp diff --git a/automated-tests/src/dali-scene3d/utc-Dali-BvhLoader.cpp b/automated-tests/src/dali-scene3d/utc-Dali-BvhLoader.cpp new file mode 100644 index 0000000..91fdb19 --- /dev/null +++ b/automated-tests/src/dali-scene3d/utc-Dali-BvhLoader.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022 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. + * + */ + +#include +#include + +using namespace Dali; +using namespace Dali::Scene3D::Loader; + +int UtcDaliLoadBvh(void) +{ + TestApplication application; + + AnimationDefinition animDef = LoadBvh(TEST_RESOURCE_DIR "/test.bvh", "testBvh"); + + DALI_TEST_EQUAL(animDef.mName, "testBvh"); + DALI_TEST_EQUAL(animDef.mDuration, 0.3f); + + DALI_TEST_EQUAL(animDef.mProperties[0].mNodeName, "root"); + DALI_TEST_EQUAL(animDef.mProperties[0].mPropertyName, "position"); + DALI_TEST_EQUAL(animDef.mProperties[0].mKeyFrames.GetType(), Property::Type::VECTOR3); + DALI_TEST_EQUAL(animDef.mProperties[0].mTimePeriod.durationSeconds, 0.3f); + + DALI_TEST_EQUAL(animDef.mProperties[1].mNodeName, "root"); + DALI_TEST_EQUAL(animDef.mProperties[1].mPropertyName, "orientation"); + DALI_TEST_EQUAL(animDef.mProperties[1].mKeyFrames.GetType(), Property::Type::ROTATION); + DALI_TEST_EQUAL(animDef.mProperties[1].mTimePeriod.durationSeconds, 0.3f); + + DALI_TEST_EQUAL(animDef.mProperties[2].mNodeName, "first"); + DALI_TEST_EQUAL(animDef.mProperties[2].mPropertyName, "position"); + DALI_TEST_EQUAL(animDef.mProperties[2].mKeyFrames.GetType(), Property::Type::VECTOR3); + DALI_TEST_EQUAL(animDef.mProperties[2].mTimePeriod.durationSeconds, 0.3f); + + DALI_TEST_EQUAL(animDef.mProperties[3].mNodeName, "first"); + DALI_TEST_EQUAL(animDef.mProperties[3].mPropertyName, "orientation"); + DALI_TEST_EQUAL(animDef.mProperties[3].mKeyFrames.GetType(), Property::Type::ROTATION); + DALI_TEST_EQUAL(animDef.mProperties[3].mTimePeriod.durationSeconds, 0.3f); + + Actor root = Actor::New(); + root.SetProperty(Actor::Property::NAME, "root"); + + Actor first = Actor::New(); + first.SetProperty(Actor::Property::NAME, "first"); + root.Add(first); + + auto getActor = [&root](const std::string& name) { + return root.FindChildByName(name); + }; + + Animation animation = animDef.ReAnimate(getActor); + DALI_TEST_EQUAL(animation.GetDuration(), animDef.mDuration); + + application.GetScene().Add(root); + + application.SendNotification(); + application.Render(20); + + DALI_TEST_EQUALS(Vector2(0, 0), root.GetProperty(Actor::Property::POSITION), TEST_LOCATION); + DALI_TEST_EQUALS(Vector2(0, 0), first.GetProperty(Actor::Property::POSITION), TEST_LOCATION); + Vector3 rootWorldPositionBefore = root.GetProperty(Actor::Property::WORLD_POSITION); + Vector3 firstWorldPositionBefore = first.GetProperty(Actor::Property::WORLD_POSITION); + + animation.Play(); + + application.SendNotification(); + application.Render(1000); + + DALI_TEST_EQUALS(Vector2(0, 10), root.GetProperty(Actor::Property::POSITION), TEST_LOCATION); + DALI_TEST_EQUALS(Vector2(10, 0), first.GetProperty(Actor::Property::POSITION), TEST_LOCATION); + + Vector3 rootWorldPositionAfter = root.GetProperty(Actor::Property::WORLD_POSITION); + Vector3 firstWorldPositionAfter = first.GetProperty(Actor::Property::WORLD_POSITION); + + DALI_TEST_EQUALS(Vector3(0, 10, 0), rootWorldPositionAfter - rootWorldPositionBefore, TEST_LOCATION); + DALI_TEST_EQUALS(Vector3(10, 10, 0), firstWorldPositionAfter - firstWorldPositionBefore, TEST_LOCATION); + + END_TEST; +} + + + +int UtcDaliLoadBvhFailed(void) +{ + TestApplication application; + + AnimationDefinition animDef = LoadBvh("/nothing.bvh", "testBvh"); + DALI_TEST_EQUALS(0u, animDef.mProperties.size(), TEST_LOCATION); + END_TEST; +} diff --git a/dali-scene3d/public-api/file.list b/dali-scene3d/public-api/file.list index 59ce588..23d4702 100644 --- a/dali-scene3d/public-api/file.list +++ b/dali-scene3d/public-api/file.list @@ -6,6 +6,7 @@ set(scene3d_src_files ${scene3d_src_files} ${scene3d_public_api_dir}/loader/animated-property.cpp ${scene3d_public_api_dir}/loader/animation-definition.cpp ${scene3d_public_api_dir}/loader/blend-shape-details.cpp + ${scene3d_public_api_dir}/loader/bvh-loader.cpp ${scene3d_public_api_dir}/loader/camera-parameters.cpp ${scene3d_public_api_dir}/loader/cube-data.cpp ${scene3d_public_api_dir}/loader/cube-loader.cpp diff --git a/dali-scene3d/public-api/loader/bvh-loader.cpp b/dali-scene3d/public-api/loader/bvh-loader.cpp new file mode 100644 index 0000000..fc6815d --- /dev/null +++ b/dali-scene3d/public-api/loader/bvh-loader.cpp @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2022 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 +#include +#include +#include + +namespace Dali +{ +namespace Scene3D +{ +namespace Loader +{ +namespace +{ +static constexpr std::string_view TOKEN_OFFSET = "OFFSET"; +static constexpr std::string_view TOKEN_CHANNELS = "CHANNELS"; +static constexpr std::string_view TOKEN_XPOSITION = "Xposition"; +static constexpr std::string_view TOKEN_YPOSITION = "Yposition"; +static constexpr std::string_view TOKEN_ZPOSITION = "Zposition"; +static constexpr std::string_view TOKEN_XROTATION = "Xrotation"; +static constexpr std::string_view TOKEN_YROTATION = "Yrotation"; +static constexpr std::string_view TOKEN_ZROTATION = "Zrotation"; +static constexpr std::string_view TOKEN_JOINT = "JOINT"; +static constexpr std::string_view TOKEN_END_SITE = "End Site"; +static constexpr std::string_view TOKEN_FRAMES = "Frames"; +static constexpr std::string_view TOKEN_FRAME_TIME = "Frame Time"; +static constexpr std::string_view TOKEN_HIERARCHY = "HIERARCHY"; +static constexpr std::string_view TOKEN_ROOT = "ROOT"; +static constexpr std::string_view TOKEN_MOTION = "MOTION"; +static constexpr std::string_view PROPERTY_NAME_POSITION = "position"; +static constexpr std::string_view PROPERTY_NAME_ORIENTATION = "orientation"; +static constexpr std::string_view TOKEN_CLOSING_BRACE = "}"; + +enum class Channel +{ + XPOSITION = 0, + YPOSITION, + ZPOSITION, + XROTATION, + YROTATION, + ZROTATION +}; + +struct Frame +{ + std::vector values; +}; + +struct Joint +{ + std::string name; + Vector3 offset; + std::vector translations; + std::vector rotations; + std::vector channels; + std::vector> children; +}; + +void trim(std::string& s) +{ + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { + return !std::isspace(ch); + })); + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), + s.end()); +} + +void ParseHierarchy(std::ifstream& file, std::shared_ptr& joint) +{ + std::string line; + while(std::getline(file, line)) + { + trim(line); + std::istringstream stream(line); + std::string token; + std::getline(stream, token, ' '); + + if(token == TOKEN_OFFSET.data()) + { + stream >> joint->offset.x >> joint->offset.y >> joint->offset.z; + } + else if(token == TOKEN_CHANNELS.data()) + { + uint32_t channelCount = 0; + std::getline(stream, token, ' '); + channelCount = static_cast(std::atoi(token.c_str())); + for(uint32_t i = 0; i < channelCount; ++i) + { + std::getline(stream, token, ' '); + trim(token); + if(token == TOKEN_XPOSITION.data()) + { + joint->channels.push_back(Channel::XPOSITION); + } + else if(token == TOKEN_YPOSITION.data()) + { + joint->channels.push_back(Channel::YPOSITION); + } + else if(token == TOKEN_ZPOSITION.data()) + { + joint->channels.push_back(Channel::ZPOSITION); + } + else if(token == TOKEN_XROTATION.data()) + { + joint->channels.push_back(Channel::XROTATION); + } + else if(token == TOKEN_YROTATION.data()) + { + joint->channels.push_back(Channel::YROTATION); + } + else if(token == TOKEN_ZROTATION.data()) + { + joint->channels.push_back(Channel::ZROTATION); + } + } + } + else if(token == TOKEN_JOINT.data()) + { + std::shared_ptr child(new Joint); + joint->children.push_back(child); + std::getline(stream, token, ' '); + child->name = token; + ParseHierarchy(file, child); + } + else if(line == TOKEN_END_SITE.data()) + { + while(std::getline(file, line)) + { + trim(line); + if(line == TOKEN_CLOSING_BRACE.data()) + { + break; + } + } + } + else if(token == TOKEN_CLOSING_BRACE.data()) + { + break; + } + } +} + +void MakeList(std::shared_ptr& joint, std::vector>& jointList) +{ + jointList.push_back(joint); + for(uint32_t i = 0; i < joint->children.size(); ++i) + { + MakeList(joint->children[i], jointList); + } +} + +void ParseMotion(std::ifstream& file, std::shared_ptr& hierarchy, uint32_t& frameCount, float& frameTime) +{ + std::vector> jointList; + MakeList(hierarchy, jointList); + + bool frameCountLoaded = false; + bool frameTimeLoaded = false; + std::string line; + while((!frameCountLoaded || !frameTimeLoaded) && std::getline(file, line)) + { + trim(line); + std::istringstream stream(line); + std::string token; + std::getline(stream, token, ':'); + trim(token); + if(token == TOKEN_FRAMES.data()) + { + stream >> frameCount; + frameCountLoaded = true; + } + else if(token == TOKEN_FRAME_TIME.data()) + { + stream >> frameTime; + frameTimeLoaded = true; + } + } + + while(getline(file, line)) + { + trim(line); + std::istringstream stream(line); + for(auto&& joint : jointList) + { + Vector3 translation; + Quaternion rotation[3]; + for(uint32_t i = 0; i < joint->channels.size(); ++i) + { + if(joint->channels[i] == Channel::XPOSITION) + { + stream >> translation.x; + } + else if(joint->channels[i] == Channel::YPOSITION) + { + stream >> translation.y; + } + else if(joint->channels[i] == Channel::ZPOSITION) + { + stream >> translation.z; + } + else if(joint->channels[i] == Channel::XROTATION) + { + float radian; + stream >> radian; + rotation[0] = Quaternion(Radian(Degree(radian)), Vector3::XAXIS); + } + else if(joint->channels[i] == Channel::YROTATION) + { + float radian; + stream >> radian; + rotation[1] = Quaternion(Radian(Degree(radian)), Vector3::YAXIS); + } + else if(joint->channels[i] == Channel::ZROTATION) + { + float radian; + stream >> radian; + rotation[2] = Quaternion(Radian(Degree(radian)), Vector3::ZAXIS); + } + } + joint->translations.push_back(translation); + joint->rotations.push_back(rotation[2] * rotation[0] * rotation[1]); + } + } +} + +bool ParseBvh(const std::string& path, uint32_t& frameCount, float& frameTime, std::shared_ptr& rootJoint) +{ + std::ifstream file(path.data()); + if(!file.is_open()) + { + return false; + } + + std::string line; + while(getline(file, line)) + { + trim(line); + std::istringstream stream(line); + std::string token; + std::getline(stream, token, ' '); + if(token == TOKEN_HIERARCHY.data()) + { + std::string line; + while(getline(file, line)) + { + trim(line); + std::istringstream stream(line); + std::string token; + std::getline(stream, token, ' '); + if(token == TOKEN_ROOT.data()) + { + std::getline(stream, token, ' '); + rootJoint->name = token; + ParseHierarchy(file, rootJoint); + break; + } + } + } + if(token == TOKEN_MOTION.data()) + { + ParseMotion(file, rootJoint, frameCount, frameTime); + } + } + return true; +} + +AnimationDefinition GenerateAnimation(const std::string& animationName, std::shared_ptr& hierarchy, uint32_t frameCount, float frameTime, const Vector3& scale) +{ + AnimationDefinition animationDefinition; + + animationDefinition.mName = animationName; + animationDefinition.mDuration = frameTime * (frameCount - 1); + float keyFrameInterval = (frameCount > 1u) ? 1.0f / static_cast(frameCount - 1u) : Dali::Math::MACHINE_EPSILON_10; + + std::vector> jointList; + MakeList(hierarchy, jointList); + + if(!jointList.empty()) + { + animationDefinition.mProperties.resize(jointList.size() * 2u); // translation and rotation + for(uint32_t i = 0; i < jointList.size(); ++i) + { + AnimatedProperty& translationProperty = animationDefinition.mProperties[i * 2u]; + translationProperty.mTimePeriod = Dali::TimePeriod(animationDefinition.mDuration); + translationProperty.mNodeName = jointList[i]->name; + translationProperty.mPropertyName = PROPERTY_NAME_POSITION.data(); + + AnimatedProperty& rotationProperty = animationDefinition.mProperties[i * 2u + 1]; + rotationProperty.mTimePeriod = Dali::TimePeriod(animationDefinition.mDuration); + rotationProperty.mNodeName = jointList[i]->name; + rotationProperty.mPropertyName = PROPERTY_NAME_ORIENTATION.data(); + + translationProperty.mKeyFrames = Dali::KeyFrames::New(); + rotationProperty.mKeyFrames = Dali::KeyFrames::New(); + for(uint32_t j = 0; j < frameCount; ++j) + { + translationProperty.mKeyFrames.Add(static_cast(j) * keyFrameInterval, (jointList[i]->translations[j] / scale)); + rotationProperty.mKeyFrames.Add(static_cast(j) * keyFrameInterval, jointList[i]->rotations[j]); + } + } + } + + return animationDefinition; +} +} // namespace + +AnimationDefinition LoadBvh(const std::string& path, const std::string& animationName, const Vector3& scale) +{ + uint32_t frameCount = 0; + float frameTime = 0.0f; + std::shared_ptr rootJoint(new Joint); + if(!ParseBvh(path, frameCount, frameTime, rootJoint)) + { + AnimationDefinition animationDefinition; + return animationDefinition; + } + + return GenerateAnimation(animationName, rootJoint, frameCount, frameTime, scale); +} +} // namespace Loader +} // namespace Scene3D +} // namespace Dali \ No newline at end of file diff --git a/dali-scene3d/public-api/loader/bvh-loader.h b/dali-scene3d/public-api/loader/bvh-loader.h new file mode 100644 index 0000000..2c3dbc7 --- /dev/null +++ b/dali-scene3d/public-api/loader/bvh-loader.h @@ -0,0 +1,45 @@ +#ifndef DALI_SCENE3D_LOADER_BVH_LOADER_H +#define DALI_SCENE3D_LOADER_BVH_LOADER_H + +/* + * Copyright (c) 2022 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 + +namespace Dali +{ +namespace Scene3D +{ +namespace Loader +{ + +/** + * @brief Loads motion capture data from bvh file format. + * + * @param[in] path The file path. + * @param[in] animationName Name of the motion capture animation + * @param[in] scale The scale factor to set on the position property manually. + * @return AnimationDefinition that includes joint animation information. + */ +DALI_SCENE3D_API AnimationDefinition LoadBvh(const std::string& path, const std::string& animationName, const Vector3& scale = Vector3::ONE); + +} // namespace Loader +} // namespace Scene3D +} // namespace Dali + +#endif // DALI_SCENE3D_LOADER_BVH_LOADER_H \ No newline at end of file -- 2.7.4