[dali_2.3.20] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-scene3d / public-api / loader / bvh-loader.cpp
1 /*
2  * Copyright (c) 2024 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // FILE HEADER
19 #include <dali-scene3d/public-api/loader/bvh-loader.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/devel-api/adaptor-framework/file-stream.h>
23 #include <dali/devel-api/animation/key-frames-devel.h>
24 #include <dali/integration-api/debug.h>
25
26 #include <fstream>
27 #include <iostream>
28 #include <memory>
29 #include <sstream>
30 #include <string_view>
31
32 namespace Dali::Scene3D::Loader
33 {
34 namespace
35 {
36 static constexpr std::string_view TOKEN_OFFSET              = "OFFSET";
37 static constexpr std::string_view TOKEN_CHANNELS            = "CHANNELS";
38 static constexpr std::string_view TOKEN_XPOSITION           = "Xposition";
39 static constexpr std::string_view TOKEN_YPOSITION           = "Yposition";
40 static constexpr std::string_view TOKEN_ZPOSITION           = "Zposition";
41 static constexpr std::string_view TOKEN_XROTATION           = "Xrotation";
42 static constexpr std::string_view TOKEN_YROTATION           = "Yrotation";
43 static constexpr std::string_view TOKEN_ZROTATION           = "Zrotation";
44 static constexpr std::string_view TOKEN_JOINT               = "JOINT";
45 static constexpr std::string_view TOKEN_END_SITE            = "End Site";
46 static constexpr std::string_view TOKEN_FRAMES              = "Frames";
47 static constexpr std::string_view TOKEN_FRAME_TIME          = "Frame Time";
48 static constexpr std::string_view TOKEN_HIERARCHY           = "HIERARCHY";
49 static constexpr std::string_view TOKEN_ROOT                = "ROOT";
50 static constexpr std::string_view TOKEN_MOTION              = "MOTION";
51 static constexpr std::string_view PROPERTY_NAME_POSITION    = "position";
52 static constexpr std::string_view PROPERTY_NAME_ORIENTATION = "orientation";
53 static constexpr std::string_view TOKEN_OPENING_BRACE       = "{";
54 static constexpr std::string_view TOKEN_CLOSING_BRACE       = "}";
55
56 enum class Channel
57 {
58   XPOSITION = 0,
59   YPOSITION,
60   ZPOSITION,
61   XROTATION,
62   YROTATION,
63   ZROTATION
64 };
65
66 struct Frame
67 {
68   std::vector<float> values;
69 };
70
71 struct Joint
72 {
73   std::string                         name;
74   Vector3                             offset;
75   std::vector<Vector3>                translations;
76   std::vector<Quaternion>             rotations;
77   std::vector<Channel>                channels;
78   std::vector<std::shared_ptr<Joint>> children;
79 };
80
81 void trim(std::string& s)
82 {
83   s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
84             return !std::isspace(ch);
85           }));
86   s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
87             return !std::isspace(ch);
88           }).base(),
89           s.end());
90 }
91
92 bool ParseHierarchy(std::istream& file, std::shared_ptr<Joint>& joint)
93 {
94   std::string line;
95   bool        braceExist = false;
96   while(std::getline(file, line))
97   {
98     trim(line);
99     std::istringstream stream(line);
100     std::string        token;
101     std::getline(stream, token, ' ');
102
103     if(token == TOKEN_OFFSET.data())
104     {
105       stream >> joint->offset.x >> joint->offset.y >> joint->offset.z;
106     }
107     else if(token == TOKEN_CHANNELS.data())
108     {
109       uint32_t channelCount = 0;
110       std::getline(stream, token, ' ');
111       channelCount = static_cast<uint32_t>(std::atoi(token.c_str()));
112       for(uint32_t i = 0; i < channelCount; ++i)
113       {
114         std::getline(stream, token, ' ');
115         trim(token);
116         if(token == TOKEN_XPOSITION.data())
117         {
118           joint->channels.push_back(Channel::XPOSITION);
119         }
120         else if(token == TOKEN_YPOSITION.data())
121         {
122           joint->channels.push_back(Channel::YPOSITION);
123         }
124         else if(token == TOKEN_ZPOSITION.data())
125         {
126           joint->channels.push_back(Channel::ZPOSITION);
127         }
128         else if(token == TOKEN_XROTATION.data())
129         {
130           joint->channels.push_back(Channel::XROTATION);
131         }
132         else if(token == TOKEN_YROTATION.data())
133         {
134           joint->channels.push_back(Channel::YROTATION);
135         }
136         else if(token == TOKEN_ZROTATION.data())
137         {
138           joint->channels.push_back(Channel::ZROTATION);
139         }
140       }
141     }
142     else if(token == TOKEN_JOINT.data())
143     {
144       std::shared_ptr<Joint> child(new Joint);
145       joint->children.push_back(child);
146       std::getline(stream, token, ' ');
147       child->name = token;
148
149       if(DALI_UNLIKELY(!ParseHierarchy(file, child)))
150       {
151         return false;
152       }
153     }
154     else if(line == TOKEN_END_SITE.data())
155     {
156       bool braceExistEndSite = false;
157       while(std::getline(file, line))
158       {
159         trim(line);
160         if(line == TOKEN_OPENING_BRACE.data())
161         {
162           if(DALI_UNLIKELY(braceExistEndSite))
163           {
164             DALI_LOG_ERROR("Parsing error : Joint[%s] End Site opening brace not matched\n", joint->name.c_str());
165             return false;
166           }
167           braceExistEndSite = true;
168         }
169         else if(line == TOKEN_CLOSING_BRACE.data())
170         {
171           if(DALI_UNLIKELY(!braceExistEndSite))
172           {
173             DALI_LOG_ERROR("Parsing error : Joint[%s] End Site closing brace not matched\n", joint->name.c_str());
174             return false;
175           }
176           break;
177         }
178       }
179       if(DALI_UNLIKELY(!braceExistEndSite))
180       {
181         DALI_LOG_ERROR("Parsing error : Joint[%s] End Site opening brace not exist\n", joint->name.c_str());
182         return false;
183       }
184     }
185     else if(token == TOKEN_OPENING_BRACE.data())
186     {
187       if(DALI_UNLIKELY(braceExist))
188       {
189         DALI_LOG_ERROR("Parsing error : Joint[%s] opening brace not matched\n", joint->name.c_str());
190         return false;
191       }
192       braceExist = true;
193     }
194     else if(token == TOKEN_CLOSING_BRACE.data())
195     {
196       if(DALI_UNLIKELY(!braceExist))
197       {
198         DALI_LOG_ERROR("Parsing error : Joint[%s] closing brace not matched\n", joint->name.c_str());
199         return false;
200       }
201       break;
202     }
203   }
204   if(DALI_UNLIKELY(!braceExist))
205   {
206     DALI_LOG_ERROR("Parsing error : Joint[%s] opening brace not exist\n", joint->name.c_str());
207     return false;
208   }
209   return true;
210 }
211
212 void MakeList(std::shared_ptr<Joint>& joint, std::vector<std::shared_ptr<Joint>>& jointList)
213 {
214   jointList.push_back(joint);
215   for(uint32_t i = 0; i < joint->children.size(); ++i)
216   {
217     MakeList(joint->children[i], jointList);
218   }
219 }
220
221 bool ParseMotion(std::istream& file, std::shared_ptr<Joint>& hierarchy, uint32_t& frameCount, float& frameTime)
222 {
223   std::vector<std::shared_ptr<Joint>> jointList;
224   MakeList(hierarchy, jointList);
225
226   bool        frameCountLoaded = false;
227   bool        frameTimeLoaded  = false;
228   std::string line;
229   while((!frameCountLoaded || !frameTimeLoaded) && std::getline(file, line))
230   {
231     trim(line);
232     std::istringstream stream(line);
233     std::string        token;
234     std::getline(stream, token, ':');
235     trim(token);
236     if(token == TOKEN_FRAMES.data())
237     {
238       stream >> frameCount;
239       frameCountLoaded = true;
240     }
241     else if(token == TOKEN_FRAME_TIME.data())
242     {
243       stream >> frameTime;
244       frameTimeLoaded = true;
245     }
246   }
247
248   if(DALI_UNLIKELY(!frameCountLoaded))
249   {
250     DALI_LOG_ERROR("Parsing error : Frames not exist!\n");
251     return false;
252   }
253   if(DALI_UNLIKELY(!frameTimeLoaded))
254   {
255     DALI_LOG_ERROR("Parsing error : Frame Time not exist!\n");
256     return false;
257   }
258
259   uint32_t loadedFrameCount = 0u;
260
261   while(std::getline(file, line))
262   {
263     trim(line);
264     if(DALI_UNLIKELY(line.empty()))
265     {
266       continue;
267     }
268     std::istringstream stream(line);
269     if(DALI_UNLIKELY(++loadedFrameCount > frameCount))
270     {
271       // Parse failed. Just skip decoding, and get the number of line for debug.
272       continue;
273     }
274
275     for(auto&& joint : jointList)
276     {
277       Vector3    translation;
278       Quaternion rotation[3];
279       for(uint32_t i = 0; i < joint->channels.size(); ++i)
280       {
281         if(joint->channels[i] == Channel::XPOSITION)
282         {
283           stream >> translation.x;
284         }
285         else if(joint->channels[i] == Channel::YPOSITION)
286         {
287           stream >> translation.y;
288         }
289         else if(joint->channels[i] == Channel::ZPOSITION)
290         {
291           stream >> translation.z;
292         }
293         else if(joint->channels[i] == Channel::XROTATION)
294         {
295           float radian;
296           stream >> radian;
297           rotation[0] = Quaternion(Radian(Degree(radian)), Vector3::XAXIS);
298         }
299         else if(joint->channels[i] == Channel::YROTATION)
300         {
301           float radian;
302           stream >> radian;
303           rotation[1] = Quaternion(Radian(Degree(radian)), Vector3::YAXIS);
304         }
305         else if(joint->channels[i] == Channel::ZROTATION)
306         {
307           float radian;
308           stream >> radian;
309           rotation[2] = Quaternion(Radian(Degree(radian)), Vector3::ZAXIS);
310         }
311       }
312       joint->translations.push_back(translation);
313       joint->rotations.push_back(rotation[2] * rotation[0] * rotation[1]);
314     }
315   }
316
317   if(DALI_UNLIKELY(loadedFrameCount != frameCount))
318   {
319     DALI_LOG_ERROR("Parsing error : Motion frame count not matched! expect : %u, loaded : %u\n", frameCount, loadedFrameCount);
320     return false;
321   }
322
323   return true;
324 }
325
326 bool ParseBvh(std::istream& file, uint32_t& frameCount, float& frameTime, std::shared_ptr<Joint>& rootJoint)
327 {
328   std::string line;
329   bool        parseHierarchy = false;
330   bool        parseMotion    = false;
331   while(std::getline(file, line))
332   {
333     trim(line);
334     std::istringstream stream(line);
335     std::string        token;
336     std::getline(stream, token, ' ');
337     if(token == TOKEN_HIERARCHY.data())
338     {
339       std::string line;
340       while(std::getline(file, line))
341       {
342         trim(line);
343         std::istringstream stream(line);
344         std::string        token;
345         std::getline(stream, token, ' ');
346         if(token == TOKEN_ROOT.data())
347         {
348           std::getline(stream, token, ' ');
349           rootJoint->name = token;
350           parseHierarchy  = ParseHierarchy(file, rootJoint);
351           break;
352         }
353       }
354     }
355     if(token == TOKEN_MOTION.data())
356     {
357       parseMotion = ParseMotion(file, rootJoint, frameCount, frameTime);
358     }
359   }
360   return parseHierarchy && parseMotion;
361 }
362
363 AnimationDefinition GenerateAnimation(const std::string& animationName, std::shared_ptr<Joint>& hierarchy, uint32_t frameCount, float frameTime, bool useRootTranslationOnly, const Vector3& scale)
364 {
365   AnimationDefinition animationDefinition;
366
367   animationDefinition.SetName(animationName);
368   animationDefinition.SetDuration(frameTime * (frameCount - 1));
369   float keyFrameInterval = (frameCount > 1u) ? 1.0f / static_cast<float>(frameCount - 1u) : Dali::Math::MACHINE_EPSILON_10;
370
371   std::vector<std::shared_ptr<Joint>> jointList;
372   MakeList(hierarchy, jointList);
373
374   if(!jointList.empty())
375   {
376     uint32_t animationSize = jointList.size();
377     animationSize          = (useRootTranslationOnly) ? (animationSize + 1u) : (animationSize * 2u);
378     animationDefinition.ReserveSize(animationSize);
379     uint32_t animationIndex = 0u;
380     for(uint32_t i = 0; i < jointList.size(); ++i)
381     {
382       AnimatedProperty translationProperty;
383       if(!useRootTranslationOnly || i == 0)
384       {
385         translationProperty.mTimePeriod   = Dali::TimePeriod(animationDefinition.GetDuration());
386         translationProperty.mNodeName     = jointList[i]->name;
387         translationProperty.mPropertyName = PROPERTY_NAME_POSITION.data();
388       }
389
390       AnimatedProperty rotationProperty;
391       rotationProperty.mTimePeriod   = Dali::TimePeriod(animationDefinition.GetDuration());
392       rotationProperty.mNodeName     = jointList[i]->name;
393       rotationProperty.mPropertyName = PROPERTY_NAME_ORIENTATION.data();
394
395       translationProperty.mKeyFrames = Dali::KeyFrames::New();
396       rotationProperty.mKeyFrames    = Dali::KeyFrames::New();
397       for(uint32_t j = 0; j < frameCount; ++j)
398       {
399         if(!useRootTranslationOnly || i == 0)
400         {
401           translationProperty.mKeyFrames.Add(static_cast<float>(j) * keyFrameInterval, (jointList[i]->translations[j] * scale));
402         }
403         rotationProperty.mKeyFrames.Add(static_cast<float>(j) * keyFrameInterval, jointList[i]->rotations[j]);
404       }
405       if(!useRootTranslationOnly || i == 0)
406       {
407         // Optimize keyframes, for heuristic!
408         DevelKeyFrames::OptimizeKeyFramesLinear(translationProperty.mKeyFrames);
409         animationDefinition.SetProperty(animationIndex++, std::move(translationProperty));
410       }
411       // Optimize keyframes, for heuristic!
412       DevelKeyFrames::OptimizeKeyFramesLinear(rotationProperty.mKeyFrames);
413       animationDefinition.SetProperty(animationIndex++, std::move(rotationProperty));
414     }
415   }
416
417   return animationDefinition;
418 }
419
420 AnimationDefinition LoadBvhInternal(std::istream& stream, const std::string& animationName, bool useRootTranslationOnly, const Vector3& scale)
421 {
422   uint32_t               frameCount = 0;
423   float                  frameTime  = 0.0f;
424   std::shared_ptr<Joint> rootJoint(new Joint);
425
426   if(!ParseBvh(stream, frameCount, frameTime, rootJoint))
427   {
428     AnimationDefinition animationDefinition;
429     return animationDefinition;
430   }
431
432   return GenerateAnimation(animationName, rootJoint, frameCount, frameTime, useRootTranslationOnly, scale);
433 }
434 } // namespace
435
436 AnimationDefinition LoadBvh(const std::string& path, const std::string& animationName, bool useRootTranslationOnly, const Vector3& scale)
437 {
438   Dali::FileStream fileStream(path);
439   std::iostream&   stream = fileStream.GetStream();
440
441   if(stream.fail())
442   {
443     DALI_LOG_ERROR("Fail to load bvh file : %s\n", path.c_str());
444     AnimationDefinition animationDefinition;
445     return animationDefinition;
446   }
447
448   return LoadBvhInternal(stream, animationName, useRootTranslationOnly, scale);
449 }
450
451 AnimationDefinition LoadBvhFromBuffer(const uint8_t* rawBuffer, int rawBufferLength, const std::string& animationName, bool useRootTranslationOnly, const Vector3& scale)
452 {
453   if(rawBuffer == nullptr || rawBufferLength == 0)
454   {
455     DALI_LOG_ERROR("Fail to load bvh buffer : buffer is empty!\n");
456     AnimationDefinition animationDefinition;
457     return animationDefinition;
458   }
459
460   Dali::FileStream fileStream(const_cast<uint8_t*>(rawBuffer), static_cast<size_t>(static_cast<uint32_t>(rawBufferLength)));
461   std::iostream&   stream = fileStream.GetStream();
462
463   if(stream.fail())
464   {
465     DALI_LOG_ERROR("Fail to load bvh buffer : buffer length : %d\n", rawBufferLength);
466     AnimationDefinition animationDefinition;
467     return animationDefinition;
468   }
469
470   return LoadBvhInternal(stream, animationName, useRootTranslationOnly, scale);
471 }
472 } // namespace Dali::Scene3D::Loader