Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
PSAConvert.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2013 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
20 #include "PSAConvert.h"
21 #include "CommonConvert.h"
22 
23 #include "FCollada.h"
24 #include "FCDocument/FCDocument.h"
25 #include "FCDocument/FCDocumentTools.h"
26 #include "FCDocument/FCDAnimated.h"
27 #include "FCDocument/FCDAnimationCurve.h"
28 #include "FCDocument/FCDAnimationKey.h"
29 #include "FCDocument/FCDController.h"
30 #include "FCDocument/FCDControllerInstance.h"
31 #include "FCDocument/FCDExtra.h"
32 #include "FCDocument/FCDGeometry.h"
33 #include "FCDocument/FCDGeometryMesh.h"
34 #include "FCDocument/FCDGeometryPolygons.h"
35 #include "FCDocument/FCDGeometrySource.h"
36 #include "FCDocument/FCDSceneNode.h"
37 
38 #include "StdSkeletons.h"
39 #include "Decompose.h"
40 #include "Maths.h"
41 #include "GeomReindex.h"
42 
43 #include <cassert>
44 #include <vector>
45 #include <limits>
46 #include <iterator>
47 
49 {
50 public:
51  /**
52  * Converts a COLLADA XML document into the PSA animation format.
53  *
54  * @param input XML document to parse
55  * @param output callback for writing the PSA data; called lots of times
56  * with small strings
57  * @param xmlErrors output - errors reported by the XML parser
58  * @throws ColladaException on failure
59  */
60  static void ColladaToPSA(const char* input, OutputCB& output, std::string& xmlErrors)
61  {
62  CommonConvert converter(input, xmlErrors);
63 
64  if (converter.GetInstance().GetType() == FCDEntityInstance::CONTROLLER)
65  {
66  FCDControllerInstance& controllerInstance = static_cast<FCDControllerInstance&>(converter.GetInstance());
67 
68  FixSkeletonRoots(controllerInstance);
69 
70  assert(converter.GetInstance().GetEntity()->GetType() == FCDEntity::CONTROLLER); // assume this is always true?
71  FCDController* controller = static_cast<FCDController*>(converter.GetInstance().GetEntity());
72 
73  FCDSkinController* skin = controller->GetSkinController();
74  REQUIRE(skin != NULL, "is skin controller");
75 
76  const Skeleton& skeleton = FindSkeleton(controllerInstance);
77 
78  float frameLength = 1.f / 30.f; // currently we always want to create PMDs at fixed 30fps
79 
80  // Find the extents of the animation:
81 
82  float timeStart = 0, timeEnd = 0;
83  GetAnimationRange(converter.GetDocument(), skeleton, controllerInstance, timeStart, timeEnd);
84  // To catch broken animations / skeletons.xml:
85  REQUIRE(timeEnd > timeStart, "animation end frame must come after start frame");
86 
87  // Count frames; don't include the last keyframe
88  size_t frameCount = (size_t)((timeEnd - timeStart) / frameLength - 0.5f);
89  REQUIRE(frameCount > 0, "animation must have frames");
90  // (TODO: sort out the timing/looping problems)
91 
92  size_t boneCount = skeleton.GetBoneCount();
93 
94  std::vector<BoneTransform> boneTransforms;
95 
96  for (size_t frame = 0; frame < frameCount; ++frame)
97  {
98  float time = timeStart + frameLength * frame;
99 
100  BoneTransform boneDefault = { { 0, 0, 0 }, { 0, 0, 0, 1 } };
101  std::vector<BoneTransform> frameBoneTransforms (boneCount, boneDefault);
102 
103  // Move the model into the new animated pose
104  // (We can't tell exactly which nodes should be animated, so
105  // just update the entire world recursively)
106  EvaluateAnimations(converter.GetRoot(), time);
107 
108  // Convert the pose into the form require by the game
109  for (size_t i = 0; i < controllerInstance.GetJointCount(); ++i)
110  {
111  FCDSceneNode* joint = controllerInstance.GetJoint(i);
112 
113  int boneId = skeleton.GetRealBoneID(joint->GetName().c_str());
114  if (boneId < 0)
115  continue; // not a recognised bone - ignore it, same as before
116 
117  FMMatrix44 worldTransform = joint->CalculateWorldTransform();
118 
119  HMatrix matrix;
120  memcpy(matrix, worldTransform.Transposed().m, sizeof(matrix));
121 
122  AffineParts parts;
123  decomp_affine(matrix, &parts);
124 
125  BoneTransform b = {
126  { parts.t.x, parts.t.y, parts.t.z },
127  { parts.q.x, parts.q.y, parts.q.z, parts.q.w }
128  };
129 
130  frameBoneTransforms[boneId] = b;
131  }
132 
133  // Push frameBoneTransforms onto the back of boneTransforms
134  copy(frameBoneTransforms.begin(), frameBoneTransforms.end(),
135  std::inserter(boneTransforms, boneTransforms.end()));
136  }
137 
138  // Convert into game's coordinate space
139  TransformVertices(boneTransforms, skin->GetBindShapeTransform(), converter.IsYUp(), converter.IsXSI());
140 
141  // Write out the file
142  WritePSA(output, frameCount, boneCount, boneTransforms);
143  }
144  else
145  {
146  throw ColladaException("Unrecognised object type");
147  }
148  }
149 
150  /**
151  * Writes the animation data in the PSA format.
152  */
153  static void WritePSA(OutputCB& output, size_t frameCount, size_t boneCount, const std::vector<BoneTransform>& boneTransforms)
154  {
155  output("PSSA", 4); // magic number
156  write(output, (uint32)1); // version number
157  write(output, (uint32)(
158  4 + 0 + // name
159  4 + // frameLength
160  4 + 4 + // numBones, numFrames
161  7*4*boneCount*frameCount // boneStates
162  )); // data size
163 
164  // Name
165  write(output, (uint32)0);
166 
167  // Frame length
168  write(output, 1000.f/30.f);
169 
170  write(output, (uint32)boneCount);
171  write(output, (uint32)frameCount);
172 
173  for (size_t i = 0; i < boneCount*frameCount; ++i)
174  {
175  output((char*)&boneTransforms[i], 7*4);
176  }
177  }
178 
179  static void TransformVertices(std::vector<BoneTransform>& bones,
180  const FMMatrix44& transform, bool yUp, bool isXSI)
181  {
182  // HACK: we want to handle scaling in XSI because that makes it easy
183  // for artists to adjust the models to the right size. But this way
184  // doesn't work in Max, and I can't see how to make it do so, so this
185  // is only applied to models from XSI.
186  if (isXSI)
187  {
188  TransformBones(bones, DecomposeToScaleMatrix(transform), yUp);
189  }
190  else
191  {
192  TransformBones(bones, FMMatrix44_Identity, yUp);
193  }
194  }
195 
196  static void GetAnimationRange(const FColladaDocument& doc, const Skeleton& skeleton,
197  const FCDControllerInstance& controllerInstance,
198  float& timeStart, float& timeEnd)
199  {
200  // FCollada tools export <extra> info in the scene to specify the start
201  // and end times.
202  // If that isn't available, we have to search for the earliest and latest
203  // keyframes on any of the bones.
204  if (doc.GetDocument()->HasStartTime() && doc.GetDocument()->HasEndTime())
205  {
206  timeStart = doc.GetDocument()->GetStartTime();
207  timeEnd = doc.GetDocument()->GetEndTime();
208  return;
209  }
210 
211  // XSI exports relevant information in
212  // <extra><technique profile="XSI"><SI_Scene><xsi_param sid="start">
213  // (and 'end' and 'frameRate') so use those
214  if (GetAnimationRange_XSI(doc, timeStart, timeEnd))
215  return;
216 
217  timeStart = std::numeric_limits<float>::max();
218  timeEnd = -std::numeric_limits<float>::max();
219  for (size_t i = 0; i < controllerInstance.GetJointCount(); ++i)
220  {
221  const FCDSceneNode* joint = controllerInstance.GetJoint(i);
222  REQUIRE(joint != NULL, "joint exists");
223 
224  int boneId = skeleton.GetBoneID(joint->GetName().c_str());
225  if (boneId < 0)
226  {
227  // unrecognised joint - it's probably just a prop point
228  // or something, so ignore it
229  continue;
230  }
231 
232  // Skip unanimated joints
233  if (joint->GetTransformCount() == 0)
234  continue;
235 
236  for (size_t j = 0; j < joint->GetTransformCount(); ++j)
237  {
238  const FCDTransform* transform = joint->GetTransform(j);
239 
240  if (! transform->IsAnimated())
241  continue;
242 
243  // Iterate over all curves to find the earliest and latest keys
244  const FCDAnimated* anim = transform->GetAnimated();
245  const FCDAnimationCurveListList& curvesList = anim->GetCurves();
246  for (size_t j = 0; j < curvesList.size(); ++j)
247  {
248  const FCDAnimationCurveTrackList& curves = curvesList[j];
249  for (size_t k = 0; k < curves.size(); ++k)
250  {
251  const FCDAnimationCurve* curve = curves[k];
252  timeStart = std::min(timeStart, curve->GetKeys()[0]->input);
253  timeEnd = std::max(timeEnd, curve->GetKeys()[curve->GetKeyCount()-1]->input);
254  }
255  }
256  }
257  }
258  }
259 
260  static bool GetAnimationRange_XSI(const FColladaDocument& doc, float& timeStart, float& timeEnd)
261  {
262  FCDExtra* extra = doc.GetExtra();
263  if (! extra) return false;
264 
265  FCDEType* type = extra->GetDefaultType();
266  if (! type) return false;
267 
268  FCDETechnique* technique = type->FindTechnique("XSI");
269  if (! technique) return false;
270 
271  FCDENode* scene = technique->FindChildNode("SI_Scene");
272  if (! scene) return false;
273 
274  float start = FLT_MAX, end = -FLT_MAX, framerate = 0.f;
275 
276  FCDENodeList paramNodes;
277  scene->FindChildrenNodes("xsi_param", paramNodes);
278  for (FCDENodeList::iterator it = paramNodes.begin(); it != paramNodes.end(); ++it)
279  {
280  if ((*it)->ReadAttribute("sid") == "start")
281  start = FUStringConversion::ToFloat((*it)->GetContent());
282  else if ((*it)->ReadAttribute("sid") == "end")
283  end = FUStringConversion::ToFloat((*it)->GetContent());
284  else if ((*it)->ReadAttribute("sid") == "frameRate")
285  framerate = FUStringConversion::ToFloat((*it)->GetContent());
286  }
287 
288  if (framerate != 0.f && start != FLT_MAX && end != -FLT_MAX)
289  {
290  timeStart = start / framerate;
291  timeEnd = end / framerate;
292  return true;
293  }
294 
295  return false;
296  }
297 
298  static void EvaluateAnimations(FCDSceneNode& node, float time)
299  {
300  for (size_t i = 0; i < node.GetTransformCount(); ++i)
301  {
302  FCDTransform* transform = node.GetTransform(i);
303  FCDAnimated* anim = transform->GetAnimated();
304  if (anim)
305  anim->Evaluate(time);
306  }
307 
308  for (size_t i = 0; i < node.GetChildrenCount(); ++i)
309  EvaluateAnimations(*node.GetChild(i), time);
310  }
311 
312 };
313 
314 
315 // The above stuff is just in a class since I don't like having to bother
316 // with forward declarations of functions - but provide the plain function
317 // interface here:
318 
319 void ColladaToPSA(const char* input, OutputCB& output, std::string& xmlErrors)
320 {
321  PSAConvert::ColladaToPSA(input, output, xmlErrors);
322 }
float x
Definition: Decompose.h:21
Standard document loader.
Definition: CommonConvert.h:71
#define REQUIRE(value, message)
Throws a ColladaException unless the value is true.
float z
Definition: Decompose.h:21
tuple output
Definition: tests.py:116
HVect t
Definition: Decompose.h:26
Bone pose data.
FCDocument * GetDocument() const
Returns the FCDocument that was loaded.
Definition: CommonConvert.h:82
float HMatrix[4][4]
Definition: Decompose.h:24
float y
Definition: Decompose.h:21
FCDExtra * GetExtra() const
Returns the &lt;extra&gt; data from the &lt;COLLADA&gt; element.
Definition: CommonConvert.h:85
FMMatrix44 FMMatrix44_Identity
static void GetAnimationRange(const FColladaDocument &doc, const Skeleton &skeleton, const FCDControllerInstance &controllerInstance, float &timeStart, float &timeEnd)
Definition: PSAConvert.cpp:196
void FixSkeletonRoots(FCDControllerInstance &controllerInstance)
Fixes some occasional problems with the skeleton root definitions in a controller.
const Skeleton & FindSkeleton(const FCDControllerInstance &controllerInstance)
Finds the skeleton definition which best matches the given controller.
FCDSceneNode & GetRoot()
FMMatrix44 DecomposeToScaleMatrix(const FMMatrix44 &m)
Definition: Maths.cpp:35
static void ColladaToPSA(const char *input, OutputCB &output, std::string &xmlErrors)
Converts a COLLADA XML document into the PSA animation format.
Definition: PSAConvert.cpp:60
const FColladaDocument & GetDocument() const
void TransformBones(std::vector< BoneTransform > &bones, const FMMatrix44 &scaleTransform, bool yUp)
Performs the standard transformations on bones, applying a scale matrix and moving them into the game...
int GetBoneCount() const
Returns the number of bones in the standard-skeleton which this current skeleton is mapped onto...
int GetRealBoneID(const std::string &name) const
Similar to GetBoneID, but only returns a value for the most important (first) nonstandard-skeleton bo...
FCDEntityInstance & GetInstance()
float w
Definition: Decompose.h:21
void decomp_affine(HMatrix A, AffineParts *parts)
Definition: Decompose.cpp:486
Wrapper for code shared between the PMD and PSA converters.
Definition: CommonConvert.h:97
static void WritePSA(OutputCB &output, size_t frameCount, size_t boneCount, const std::vector< BoneTransform > &boneTransforms)
Writes the animation data in the PSA format.
Definition: PSAConvert.cpp:153
void write(OutputCB &output, const T &data)
Outputs a structure, using sizeof to get the size.
static void EvaluateAnimations(FCDSceneNode &node, float time)
Definition: PSAConvert.cpp:298
bool IsXSI() const
tuple input
Definition: tests.py:115
static bool GetAnimationRange_XSI(const FColladaDocument &doc, float &timeStart, float &timeEnd)
Definition: PSAConvert.cpp:260
void ColladaToPSA(const char *input, OutputCB &output, std::string &xmlErrors)
Definition: PSAConvert.cpp:319
bool IsYUp() const
static void TransformVertices(std::vector< BoneTransform > &bones, const FMMatrix44 &transform, bool yUp, bool isXSI)
Definition: PSAConvert.cpp:179
int GetBoneID(const std::string &name) const
Returns the ID number of the standard-skeleton bone, which corresponds to the named nonstandard-skele...