Merge "Load bvh and facial animations from buffer" into 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_CLOSING_BRACE       = "}";
53
54 enum class Channel
55 {
56   XPOSITION = 0,
57   YPOSITION,
58   ZPOSITION,
59   XROTATION,
60   YROTATION,
61   ZROTATION
62 };
63
64 struct Frame
65 {
66   std::vector<float> values;
67 };
68
69 struct Joint
70 {
71   std::string                         name;
72   Vector3                             offset;
73   std::vector<Vector3>                translations;
74   std::vector<Quaternion>             rotations;
75   std::vector<Channel>                channels;
76   std::vector<std::shared_ptr<Joint>> children;
77 };
78
79 void trim(std::string& s)
80 {
81   s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
82             return !std::isspace(ch);
83           }));
84   s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
85             return !std::isspace(ch);
86           }).base(),
87           s.end());
88 }
89
90 void ParseHierarchy(std::istream& file, std::shared_ptr<Joint>& joint)
91 {
92   std::string line;
93   while(std::getline(file, line))
94   {
95     trim(line);
96     std::istringstream stream(line);
97     std::string        token;
98     std::getline(stream, token, ' ');
99
100     if(token == TOKEN_OFFSET.data())
101     {
102       stream >> joint->offset.x >> joint->offset.y >> joint->offset.z;
103     }
104     else if(token == TOKEN_CHANNELS.data())
105     {
106       uint32_t channelCount = 0;
107       std::getline(stream, token, ' ');
108       channelCount = static_cast<uint32_t>(std::atoi(token.c_str()));
109       for(uint32_t i = 0; i < channelCount; ++i)
110       {
111         std::getline(stream, token, ' ');
112         trim(token);
113         if(token == TOKEN_XPOSITION.data())
114         {
115           joint->channels.push_back(Channel::XPOSITION);
116         }
117         else if(token == TOKEN_YPOSITION.data())
118         {
119           joint->channels.push_back(Channel::YPOSITION);
120         }
121         else if(token == TOKEN_ZPOSITION.data())
122         {
123           joint->channels.push_back(Channel::ZPOSITION);
124         }
125         else if(token == TOKEN_XROTATION.data())
126         {
127           joint->channels.push_back(Channel::XROTATION);
128         }
129         else if(token == TOKEN_YROTATION.data())
130         {
131           joint->channels.push_back(Channel::YROTATION);
132         }
133         else if(token == TOKEN_ZROTATION.data())
134         {
135           joint->channels.push_back(Channel::ZROTATION);
136         }
137       }
138     }
139     else if(token == TOKEN_JOINT.data())
140     {
141       std::shared_ptr<Joint> child(new Joint);
142       joint->children.push_back(child);
143       std::getline(stream, token, ' ');
144       child->name = token;
145       ParseHierarchy(file, child);
146     }
147     else if(line == TOKEN_END_SITE.data())
148     {
149       while(std::getline(file, line))
150       {
151         trim(line);
152         if(line == TOKEN_CLOSING_BRACE.data())
153         {
154           break;
155         }
156       }
157     }
158     else if(token == TOKEN_CLOSING_BRACE.data())
159     {
160       break;
161     }
162   }
163 }
164
165 void MakeList(std::shared_ptr<Joint>& joint, std::vector<std::shared_ptr<Joint>>& jointList)
166 {
167   jointList.push_back(joint);
168   for(uint32_t i = 0; i < joint->children.size(); ++i)
169   {
170     MakeList(joint->children[i], jointList);
171   }
172 }
173
174 void ParseMotion(std::istream& file, std::shared_ptr<Joint>& hierarchy, uint32_t& frameCount, float& frameTime)
175 {
176   std::vector<std::shared_ptr<Joint>> jointList;
177   MakeList(hierarchy, jointList);
178
179   bool        frameCountLoaded = false;
180   bool        frameTimeLoaded  = false;
181   std::string line;
182   while((!frameCountLoaded || !frameTimeLoaded) && std::getline(file, line))
183   {
184     trim(line);
185     std::istringstream stream(line);
186     std::string        token;
187     std::getline(stream, token, ':');
188     trim(token);
189     if(token == TOKEN_FRAMES.data())
190     {
191       stream >> frameCount;
192       frameCountLoaded = true;
193     }
194     else if(token == TOKEN_FRAME_TIME.data())
195     {
196       stream >> frameTime;
197       frameTimeLoaded = true;
198     }
199   }
200
201   while(std::getline(file, line))
202   {
203     trim(line);
204     std::istringstream stream(line);
205     for(auto&& joint : jointList)
206     {
207       Vector3    translation;
208       Quaternion rotation[3];
209       for(uint32_t i = 0; i < joint->channels.size(); ++i)
210       {
211         if(joint->channels[i] == Channel::XPOSITION)
212         {
213           stream >> translation.x;
214         }
215         else if(joint->channels[i] == Channel::YPOSITION)
216         {
217           stream >> translation.y;
218         }
219         else if(joint->channels[i] == Channel::ZPOSITION)
220         {
221           stream >> translation.z;
222         }
223         else if(joint->channels[i] == Channel::XROTATION)
224         {
225           float radian;
226           stream >> radian;
227           rotation[0] = Quaternion(Radian(Degree(radian)), Vector3::XAXIS);
228         }
229         else if(joint->channels[i] == Channel::YROTATION)
230         {
231           float radian;
232           stream >> radian;
233           rotation[1] = Quaternion(Radian(Degree(radian)), Vector3::YAXIS);
234         }
235         else if(joint->channels[i] == Channel::ZROTATION)
236         {
237           float radian;
238           stream >> radian;
239           rotation[2] = Quaternion(Radian(Degree(radian)), Vector3::ZAXIS);
240         }
241       }
242       joint->translations.push_back(translation);
243       joint->rotations.push_back(rotation[2] * rotation[0] * rotation[1]);
244     }
245   }
246 }
247
248 bool ParseBvh(std::istream& file, uint32_t& frameCount, float& frameTime, std::shared_ptr<Joint>& rootJoint)
249 {
250   std::string line;
251   while(std::getline(file, line))
252   {
253     trim(line);
254     std::istringstream stream(line);
255     std::string        token;
256     std::getline(stream, token, ' ');
257     if(token == TOKEN_HIERARCHY.data())
258     {
259       std::string line;
260       while(std::getline(file, line))
261       {
262         trim(line);
263         std::istringstream stream(line);
264         std::string        token;
265         std::getline(stream, token, ' ');
266         if(token == TOKEN_ROOT.data())
267         {
268           std::getline(stream, token, ' ');
269           rootJoint->name = token;
270           ParseHierarchy(file, rootJoint);
271           break;
272         }
273       }
274     }
275     if(token == TOKEN_MOTION.data())
276     {
277       ParseMotion(file, rootJoint, frameCount, frameTime);
278     }
279   }
280   return true;
281 }
282
283 AnimationDefinition GenerateAnimation(const std::string& animationName, std::shared_ptr<Joint>& hierarchy, uint32_t frameCount, float frameTime, const Vector3& scale)
284 {
285   AnimationDefinition animationDefinition;
286
287   animationDefinition.SetName(animationName);
288   animationDefinition.SetDuration(frameTime * (frameCount - 1));
289   float keyFrameInterval = (frameCount > 1u) ? 1.0f / static_cast<float>(frameCount - 1u) : Dali::Math::MACHINE_EPSILON_10;
290
291   std::vector<std::shared_ptr<Joint>> jointList;
292   MakeList(hierarchy, jointList);
293
294   if(!jointList.empty())
295   {
296     animationDefinition.ReserveSize(jointList.size() * 2u); // translation and rotation
297     for(uint32_t i = 0; i < jointList.size(); ++i)
298     {
299       AnimatedProperty translationProperty;
300       translationProperty.mTimePeriod   = Dali::TimePeriod(animationDefinition.GetDuration());
301       translationProperty.mNodeName     = jointList[i]->name;
302       translationProperty.mPropertyName = PROPERTY_NAME_POSITION.data();
303
304       AnimatedProperty rotationProperty;
305       rotationProperty.mTimePeriod   = Dali::TimePeriod(animationDefinition.GetDuration());
306       rotationProperty.mNodeName     = jointList[i]->name;
307       rotationProperty.mPropertyName = PROPERTY_NAME_ORIENTATION.data();
308
309       translationProperty.mKeyFrames = Dali::KeyFrames::New();
310       rotationProperty.mKeyFrames    = Dali::KeyFrames::New();
311       for(uint32_t j = 0; j < frameCount; ++j)
312       {
313         translationProperty.mKeyFrames.Add(static_cast<float>(j) * keyFrameInterval, (jointList[i]->translations[j] * scale));
314         rotationProperty.mKeyFrames.Add(static_cast<float>(j) * keyFrameInterval, jointList[i]->rotations[j]);
315       }
316       animationDefinition.SetProperty(i * 2u, std::move(translationProperty));
317       animationDefinition.SetProperty(i * 2u + 1, std::move(rotationProperty));
318     }
319   }
320
321   return animationDefinition;
322 }
323
324 AnimationDefinition LoadBvhInternal(std::istream& stream, const std::string& animationName, const Vector3& scale)
325 {
326   uint32_t               frameCount = 0;
327   float                  frameTime  = 0.0f;
328   std::shared_ptr<Joint> rootJoint(new Joint);
329
330   if(!ParseBvh(stream, frameCount, frameTime, rootJoint))
331   {
332     AnimationDefinition animationDefinition;
333     return animationDefinition;
334   }
335
336   return GenerateAnimation(animationName, rootJoint, frameCount, frameTime, scale);
337 }
338 } // namespace
339
340 AnimationDefinition LoadBvh(const std::string& path, const std::string& animationName, const Vector3& scale)
341 {
342   Dali::FileStream fileStream(path);
343   std::iostream&   stream = fileStream.GetStream();
344
345   if(stream.fail())
346   {
347     DALI_LOG_ERROR("Fail to load bvh file : %s\n", path.c_str());
348     AnimationDefinition animationDefinition;
349     return animationDefinition;
350   }
351
352   return LoadBvhInternal(stream, animationName, scale);
353 }
354
355 AnimationDefinition LoadBvhFromBuffer(const uint8_t* rawBuffer, int rawBufferLength, const std::string& animationName, const Vector3& scale)
356 {
357   if(rawBuffer == nullptr || rawBufferLength == 0)
358   {
359     DALI_LOG_ERROR("Fail to load bvh buffer : buffer is empty!\n");
360     AnimationDefinition animationDefinition;
361     return animationDefinition;
362   }
363
364   Dali::FileStream fileStream(const_cast<uint8_t*>(rawBuffer), static_cast<size_t>(static_cast<uint32_t>(rawBufferLength)));
365   std::iostream&   stream = fileStream.GetStream();
366
367   if(stream.fail())
368   {
369     DALI_LOG_ERROR("Fail to load bvh buffer : buffer length : %d\n", rawBufferLength);
370     AnimationDefinition animationDefinition;
371     return animationDefinition;
372   }
373
374   return LoadBvhInternal(stream, animationName, scale);
375 }
376 } // namespace Dali::Scene3D::Loader