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