2 * Copyright (c) 2023 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-scene3d/public-api/loader/bvh-loader.h>
22 #include <dali/devel-api/adaptor-framework/file-stream.h>
23 #include <dali/integration-api/debug.h>
29 #include <string_view>
31 namespace Dali::Scene3D::Loader
35 static constexpr std::string_view TOKEN_OFFSET = "OFFSET";
36 static constexpr std::string_view TOKEN_CHANNELS = "CHANNELS";
37 static constexpr std::string_view TOKEN_XPOSITION = "Xposition";
38 static constexpr std::string_view TOKEN_YPOSITION = "Yposition";
39 static constexpr std::string_view TOKEN_ZPOSITION = "Zposition";
40 static constexpr std::string_view TOKEN_XROTATION = "Xrotation";
41 static constexpr std::string_view TOKEN_YROTATION = "Yrotation";
42 static constexpr std::string_view TOKEN_ZROTATION = "Zrotation";
43 static constexpr std::string_view TOKEN_JOINT = "JOINT";
44 static constexpr std::string_view TOKEN_END_SITE = "End Site";
45 static constexpr std::string_view TOKEN_FRAMES = "Frames";
46 static constexpr std::string_view TOKEN_FRAME_TIME = "Frame Time";
47 static constexpr std::string_view TOKEN_HIERARCHY = "HIERARCHY";
48 static constexpr std::string_view TOKEN_ROOT = "ROOT";
49 static constexpr std::string_view TOKEN_MOTION = "MOTION";
50 static constexpr std::string_view PROPERTY_NAME_POSITION = "position";
51 static constexpr std::string_view PROPERTY_NAME_ORIENTATION = "orientation";
52 static constexpr std::string_view TOKEN_OPENING_BRACE = "{";
53 static constexpr std::string_view TOKEN_CLOSING_BRACE = "}";
67 std::vector<float> values;
74 std::vector<Vector3> translations;
75 std::vector<Quaternion> rotations;
76 std::vector<Channel> channels;
77 std::vector<std::shared_ptr<Joint>> children;
80 void trim(std::string& s)
82 s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
83 return !std::isspace(ch);
85 s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
86 return !std::isspace(ch);
91 bool ParseHierarchy(std::istream& file, std::shared_ptr<Joint>& joint)
94 bool braceExist = false;
95 while(std::getline(file, line))
98 std::istringstream stream(line);
100 std::getline(stream, token, ' ');
102 if(token == TOKEN_OFFSET.data())
104 stream >> joint->offset.x >> joint->offset.y >> joint->offset.z;
106 else if(token == TOKEN_CHANNELS.data())
108 uint32_t channelCount = 0;
109 std::getline(stream, token, ' ');
110 channelCount = static_cast<uint32_t>(std::atoi(token.c_str()));
111 for(uint32_t i = 0; i < channelCount; ++i)
113 std::getline(stream, token, ' ');
115 if(token == TOKEN_XPOSITION.data())
117 joint->channels.push_back(Channel::XPOSITION);
119 else if(token == TOKEN_YPOSITION.data())
121 joint->channels.push_back(Channel::YPOSITION);
123 else if(token == TOKEN_ZPOSITION.data())
125 joint->channels.push_back(Channel::ZPOSITION);
127 else if(token == TOKEN_XROTATION.data())
129 joint->channels.push_back(Channel::XROTATION);
131 else if(token == TOKEN_YROTATION.data())
133 joint->channels.push_back(Channel::YROTATION);
135 else if(token == TOKEN_ZROTATION.data())
137 joint->channels.push_back(Channel::ZROTATION);
141 else if(token == TOKEN_JOINT.data())
143 std::shared_ptr<Joint> child(new Joint);
144 joint->children.push_back(child);
145 std::getline(stream, token, ' ');
148 if(DALI_UNLIKELY(!ParseHierarchy(file, child)))
153 else if(line == TOKEN_END_SITE.data())
155 bool braceExistEndSite = false;
156 while(std::getline(file, line))
159 if(line == TOKEN_OPENING_BRACE.data())
161 if(DALI_UNLIKELY(braceExistEndSite))
163 DALI_LOG_ERROR("Parsing error : Joint[%s] End Site opening brace not matched\n", joint->name.c_str());
166 braceExistEndSite = true;
168 else if(line == TOKEN_CLOSING_BRACE.data())
170 if(DALI_UNLIKELY(!braceExistEndSite))
172 DALI_LOG_ERROR("Parsing error : Joint[%s] End Site closing brace not matched\n", joint->name.c_str());
178 if(DALI_UNLIKELY(!braceExistEndSite))
180 DALI_LOG_ERROR("Parsing error : Joint[%s] End Site opening brace not exist\n", joint->name.c_str());
184 else if(token == TOKEN_OPENING_BRACE.data())
186 if(DALI_UNLIKELY(braceExist))
188 DALI_LOG_ERROR("Parsing error : Joint[%s] opening brace not matched\n", joint->name.c_str());
193 else if(token == TOKEN_CLOSING_BRACE.data())
195 if(DALI_UNLIKELY(!braceExist))
197 DALI_LOG_ERROR("Parsing error : Joint[%s] closing brace not matched\n", joint->name.c_str());
203 if(DALI_UNLIKELY(!braceExist))
205 DALI_LOG_ERROR("Parsing error : Joint[%s] opening brace not exist\n", joint->name.c_str());
211 void MakeList(std::shared_ptr<Joint>& joint, std::vector<std::shared_ptr<Joint>>& jointList)
213 jointList.push_back(joint);
214 for(uint32_t i = 0; i < joint->children.size(); ++i)
216 MakeList(joint->children[i], jointList);
220 bool ParseMotion(std::istream& file, std::shared_ptr<Joint>& hierarchy, uint32_t& frameCount, float& frameTime)
222 std::vector<std::shared_ptr<Joint>> jointList;
223 MakeList(hierarchy, jointList);
225 bool frameCountLoaded = false;
226 bool frameTimeLoaded = false;
228 while((!frameCountLoaded || !frameTimeLoaded) && std::getline(file, line))
231 std::istringstream stream(line);
233 std::getline(stream, token, ':');
235 if(token == TOKEN_FRAMES.data())
237 stream >> frameCount;
238 frameCountLoaded = true;
240 else if(token == TOKEN_FRAME_TIME.data())
243 frameTimeLoaded = true;
247 if(DALI_UNLIKELY(!frameCountLoaded))
249 DALI_LOG_ERROR("Parsing error : Frames not exist!\n");
252 if(DALI_UNLIKELY(!frameTimeLoaded))
254 DALI_LOG_ERROR("Parsing error : Frame Time not exist!\n");
258 uint32_t loadedFrameCount = 0u;
260 while(std::getline(file, line))
263 if(DALI_UNLIKELY(line.empty()))
267 std::istringstream stream(line);
268 if(DALI_UNLIKELY(++loadedFrameCount > frameCount))
270 // Parse failed. Just skip decoding, and get the number of line for debug.
274 for(auto&& joint : jointList)
277 Quaternion rotation[3];
278 for(uint32_t i = 0; i < joint->channels.size(); ++i)
280 if(joint->channels[i] == Channel::XPOSITION)
282 stream >> translation.x;
284 else if(joint->channels[i] == Channel::YPOSITION)
286 stream >> translation.y;
288 else if(joint->channels[i] == Channel::ZPOSITION)
290 stream >> translation.z;
292 else if(joint->channels[i] == Channel::XROTATION)
296 rotation[0] = Quaternion(Radian(Degree(radian)), Vector3::XAXIS);
298 else if(joint->channels[i] == Channel::YROTATION)
302 rotation[1] = Quaternion(Radian(Degree(radian)), Vector3::YAXIS);
304 else if(joint->channels[i] == Channel::ZROTATION)
308 rotation[2] = Quaternion(Radian(Degree(radian)), Vector3::ZAXIS);
311 joint->translations.push_back(translation);
312 joint->rotations.push_back(rotation[2] * rotation[0] * rotation[1]);
316 if(DALI_UNLIKELY(loadedFrameCount != frameCount))
318 DALI_LOG_ERROR("Parsing error : Motion frame count not matched! expect : %u, loaded : %u\n", frameCount, loadedFrameCount);
325 bool ParseBvh(std::istream& file, uint32_t& frameCount, float& frameTime, std::shared_ptr<Joint>& rootJoint)
328 bool parseHierarchy = false;
329 bool parseMotion = false;
330 while(std::getline(file, line))
333 std::istringstream stream(line);
335 std::getline(stream, token, ' ');
336 if(token == TOKEN_HIERARCHY.data())
339 while(std::getline(file, line))
342 std::istringstream stream(line);
344 std::getline(stream, token, ' ');
345 if(token == TOKEN_ROOT.data())
347 std::getline(stream, token, ' ');
348 rootJoint->name = token;
349 parseHierarchy = ParseHierarchy(file, rootJoint);
354 if(token == TOKEN_MOTION.data())
356 parseMotion = ParseMotion(file, rootJoint, frameCount, frameTime);
359 return parseHierarchy && parseMotion;
362 AnimationDefinition GenerateAnimation(const std::string& animationName, std::shared_ptr<Joint>& hierarchy, uint32_t frameCount, float frameTime, const Vector3& scale)
364 AnimationDefinition animationDefinition;
366 animationDefinition.SetName(animationName);
367 animationDefinition.SetDuration(frameTime * (frameCount - 1));
368 float keyFrameInterval = (frameCount > 1u) ? 1.0f / static_cast<float>(frameCount - 1u) : Dali::Math::MACHINE_EPSILON_10;
370 std::vector<std::shared_ptr<Joint>> jointList;
371 MakeList(hierarchy, jointList);
373 if(!jointList.empty())
375 animationDefinition.ReserveSize(jointList.size() * 2u); // translation and rotation
376 for(uint32_t i = 0; i < jointList.size(); ++i)
378 AnimatedProperty translationProperty;
379 translationProperty.mTimePeriod = Dali::TimePeriod(animationDefinition.GetDuration());
380 translationProperty.mNodeName = jointList[i]->name;
381 translationProperty.mPropertyName = PROPERTY_NAME_POSITION.data();
383 AnimatedProperty rotationProperty;
384 rotationProperty.mTimePeriod = Dali::TimePeriod(animationDefinition.GetDuration());
385 rotationProperty.mNodeName = jointList[i]->name;
386 rotationProperty.mPropertyName = PROPERTY_NAME_ORIENTATION.data();
388 translationProperty.mKeyFrames = Dali::KeyFrames::New();
389 rotationProperty.mKeyFrames = Dali::KeyFrames::New();
390 for(uint32_t j = 0; j < frameCount; ++j)
392 translationProperty.mKeyFrames.Add(static_cast<float>(j) * keyFrameInterval, (jointList[i]->translations[j] * scale));
393 rotationProperty.mKeyFrames.Add(static_cast<float>(j) * keyFrameInterval, jointList[i]->rotations[j]);
395 animationDefinition.SetProperty(i * 2u, std::move(translationProperty));
396 animationDefinition.SetProperty(i * 2u + 1, std::move(rotationProperty));
400 return animationDefinition;
403 AnimationDefinition LoadBvhInternal(std::istream& stream, const std::string& animationName, const Vector3& scale)
405 uint32_t frameCount = 0;
406 float frameTime = 0.0f;
407 std::shared_ptr<Joint> rootJoint(new Joint);
409 if(!ParseBvh(stream, frameCount, frameTime, rootJoint))
411 AnimationDefinition animationDefinition;
412 return animationDefinition;
415 return GenerateAnimation(animationName, rootJoint, frameCount, frameTime, scale);
419 AnimationDefinition LoadBvh(const std::string& path, const std::string& animationName, const Vector3& scale)
421 Dali::FileStream fileStream(path);
422 std::iostream& stream = fileStream.GetStream();
426 DALI_LOG_ERROR("Fail to load bvh file : %s\n", path.c_str());
427 AnimationDefinition animationDefinition;
428 return animationDefinition;
431 return LoadBvhInternal(stream, animationName, scale);
434 AnimationDefinition LoadBvhFromBuffer(const uint8_t* rawBuffer, int rawBufferLength, const std::string& animationName, const Vector3& scale)
436 if(rawBuffer == nullptr || rawBufferLength == 0)
438 DALI_LOG_ERROR("Fail to load bvh buffer : buffer is empty!\n");
439 AnimationDefinition animationDefinition;
440 return animationDefinition;
443 Dali::FileStream fileStream(const_cast<uint8_t*>(rawBuffer), static_cast<size_t>(static_cast<uint32_t>(rawBufferLength)));
444 std::iostream& stream = fileStream.GetStream();
448 DALI_LOG_ERROR("Fail to load bvh buffer : buffer length : %d\n", rawBufferLength);
449 AnimationDefinition animationDefinition;
450 return animationDefinition;
453 return LoadBvhInternal(stream, animationName, scale);
455 } // namespace Dali::Scene3D::Loader