Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
CommonConvert.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2011 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 "CommonConvert.h"
21 
22 #include "StdSkeletons.h"
23 #include "XMLFix.h"
24 
25 #include "FCollada.h"
26 #include "FCDocument/FCDSceneNode.h"
27 #include "FCDocument/FCDSkinController.h"
28 #include "FUtils/FUDaeSyntax.h"
29 #include "FUtils/FUFileManager.h"
30 
31 #include <cassert>
32 #include <algorithm>
33 
34 void require_(int line, bool value, const char* type, const char* message)
35 {
36  if (value) return;
37  char linestr[16];
38  sprintf(linestr, "%d", line);
39  throw ColladaException(std::string(type) + " (line " + linestr + "): " + message);
40 }
41 
42 /** Error handler for libxml2 */
43 void errorHandler(void* ctx, const char* msg, ...)
44 {
45  char buffer[1024];
46  va_list ap;
47  va_start(ap, msg);
48  vsnprintf(buffer, sizeof(buffer), msg, ap);
49  buffer[sizeof(buffer)-1] = '\0';
50  va_end(ap);
51 
52  *((std::string*)ctx) += buffer;
53 }
54 
56 : xmlErrors(xmlErrors_)
57 {
58  // Grab all the error output from libxml2, for useful error reporting
59  xmlSetGenericErrorFunc(&xmlErrors, &errorHandler);
60 
61  FUError::AddErrorCallback(FUError::DEBUG_LEVEL, this, &FColladaErrorHandler::OnError);
62  FUError::AddErrorCallback(FUError::WARNING_LEVEL, this, &FColladaErrorHandler::OnError);
63  FUError::AddErrorCallback(FUError::ERROR_LEVEL, this, &FColladaErrorHandler::OnError);
64 }
65 
67 {
68  xmlSetGenericErrorFunc(NULL, NULL);
69 
70  FUError::RemoveErrorCallback(FUError::DEBUG_LEVEL, this, &FColladaErrorHandler::OnError);
71  FUError::RemoveErrorCallback(FUError::WARNING_LEVEL, this, &FColladaErrorHandler::OnError);
72  FUError::RemoveErrorCallback(FUError::ERROR_LEVEL, this, &FColladaErrorHandler::OnError);
73 }
74 
75 void FColladaErrorHandler::OnError(FUError::Level errorLevel, uint32 errorCode, uint32 UNUSED(lineNumber))
76 {
77  // Ignore warnings about missing materials, since we ignore materials entirely anyway
78  if (errorCode == FUError::WARNING_INVALID_POLYGON_MAT_SYMBOL)
79  return;
80 
81  const char* errorString = FUError::GetErrorString((FUError::Code) errorCode);
82  if (! errorString)
83  errorString = "Unknown error code";
84 
85  if (errorLevel == FUError::DEBUG_LEVEL)
86  Log(LOG_INFO, "FCollada %d: %s", errorCode, errorString);
87  else if (errorLevel == FUError::WARNING_LEVEL)
88  Log(LOG_WARNING, "FCollada %d: %s", errorCode, errorString);
89  else
90  throw ColladaException(errorString);
91 }
92 
93 
94 //////////////////////////////////////////////////////////////////////////
95 
96 void FColladaDocument::LoadFromText(const char *text)
97 {
98  document.reset(FCollada::NewTopDocument());
99 
100  const char* newText = NULL;
101  size_t newTextSize = 0;
102  FixBrokenXML(text, &newText, &newTextSize);
103 
104  // Log(LOG_INFO, "%s", newText);
105  bool status = FCollada::LoadDocumentFromMemory("unknown.dae", document.get(), (void*)newText, newTextSize);
106 
107  if (newText != text)
108  xmlFree((void*)newText);
109 
110  REQUIRE_SUCCESS(status);
111 }
112 
113 void FColladaDocument::ReadExtras(xmlNode* UNUSED(colladaNode))
114 {
115  // TODO: This was needed to recognise and load XSI models.
116  // XSI support should be reintroduced some time, but this function
117  // may not be necessary since FCollada might now provide access to the
118  // 'extra' data via a proper API.
119 
120  /*
121  if (! IsEquivalent(colladaNode->name, DAE_COLLADA_ELEMENT))
122  return;
123 
124  extra.reset(new FCDExtra(document.get()));
125 
126  xmlNodeList extraNodes;
127  FUXmlParser::FindChildrenByType(colladaNode, DAE_EXTRA_ELEMENT, extraNodes);
128  for (xmlNodeList::iterator it = extraNodes.begin(); it != extraNodes.end(); ++it)
129  {
130  xmlNode* extraNode = (*it);
131  extra->LoadFromXML(extraNode);
132  }
133  */
134 }
135 
136 //////////////////////////////////////////////////////////////////////////
137 
138 CommonConvert::CommonConvert(const char* text, std::string& xmlErrors)
139 : m_Err(xmlErrors)
140 {
141  m_Doc.LoadFromText(text);
142  FCDSceneNode* root = m_Doc.GetDocument()->GetVisualSceneRoot();
143  REQUIRE(root != NULL, "has root object");
144 
145  // Find the instance to convert
147  throw ColladaException("Couldn't find object to convert");
148 
149  assert(m_Instance);
150  Log(LOG_INFO, "Converting '%s'", m_Instance->GetEntity()->GetName().c_str());
151 
152  m_IsXSI = false;
153  FCDAsset* asset = m_Doc.GetDocument()->GetAsset();
154  if (asset && asset->GetContributorCount() >= 1)
155  {
156  std::string tool (asset->GetContributor(0)->GetAuthoringTool());
157  if (tool.find("XSI") != tool.npos)
158  m_IsXSI = true;
159  }
160 
161  FMVector3 upAxis = m_Doc.GetDocument()->GetAsset()->GetUpAxis();
162  m_YUp = (upAxis.y != 0); // assume either Y_UP or Z_UP (TODO: does anyone ever do X_UP?)
163 }
164 
166 {
167 }
168 
169 //////////////////////////////////////////////////////////////////////////
170 
171 // HACK: The originals don't get exported properly from FCollada (3.02, DLL), so define
172 // them here instead of fixing it correctly.
173 const FMVector3 FMVector3_XAxis(1.0f, 0.0f, 0.0f);
174 static float identity[] = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
175 FMMatrix44 FMMatrix44_Identity(identity);
176 
178 {
179  FCDEntityInstance* instance;
180  FMMatrix44 transform;
181 };
182 
183 static bool IsVisible_XSI(FCDSceneNode* node, bool& visible)
184 {
185  // Look for <extra><technique profile="XSI"><SI_Visibility><xsi_param sid="visibility">
186 
187  FCDExtra* extra = node->GetExtra();
188  if (! extra) return false;
189 
190  FCDEType* type = extra->GetDefaultType();
191  if (! type) return false;
192 
193  FCDETechnique* technique = type->FindTechnique("XSI");
194  if (! technique) return false;
195 
196  FCDENode* visibility1 = technique->FindChildNode("SI_Visibility");
197  if (! visibility1) return false;
198 
199  FCDENode* visibility2 = visibility1->FindChildNode("xsi_param");
200  if (! visibility2) return false;
201 
202  if (IsEquivalent(visibility2->GetContent(), "TRUE"))
203  visible = true;
204  else if (IsEquivalent(visibility2->GetContent(), "FALSE"))
205  visible = false;
206  return true;
207 }
208 
209 static bool IsVisible(FCDSceneNode* node)
210 {
211  bool visible = false;
212 
213  // Try the XSI visibility property
214  if (IsVisible_XSI(node, visible))
215  return visible;
216 
217  // Else fall back to the FCollada-specific setting
218  visible = (node->GetVisibility() != 0.0);
219  return visible;
220 }
221 
222 /**
223  * Recursively finds all entities under the current node. If onlyMarked is
224  * set, only matches entities where the user-defined property was set to
225  * "export" in the modelling program.
226  *
227  * @param node root of subtree to search
228  * @param instances output - appends matching entities
229  * @param transform transform matrix of current subtree
230  * @param onlyMarked only match entities with "export" property
231  */
232 static void FindInstances(FCDSceneNode* node, std::vector<FoundInstance>& instances, const FMMatrix44& transform, bool onlyMarked)
233 {
234  for (size_t i = 0; i < node->GetChildrenCount(); ++i)
235  {
236  FCDSceneNode* child = node->GetChild(i);
237  FindInstances(child, instances, transform * node->ToMatrix(), onlyMarked);
238  }
239 
240  for (size_t i = 0; i < node->GetInstanceCount(); ++i)
241  {
242  if (onlyMarked)
243  {
244  if (node->GetNote() != "export")
245  continue;
246  }
247 
248  // Only accept instances of appropriate types, and not e.g. lights
249  FCDEntity::Type type = node->GetInstance(i)->GetEntityType();
250  if (! (type == FCDEntity::GEOMETRY || type == FCDEntity::CONTROLLER))
251  continue;
252 
253  // Ignore invisible objects, because presumably nobody wanted to export them
254  if (! IsVisible(node))
255  continue;
256 
257  FoundInstance f;
258  f.transform = transform * node->ToMatrix();
259  f.instance = node->GetInstance(i);
260  instances.push_back(f);
261  Log(LOG_INFO, "Found convertible object '%s'", node->GetName().c_str());
262  }
263 }
264 
265 bool FindSingleInstance(FCDSceneNode* node, FCDEntityInstance*& instance, FMMatrix44& transform)
266 {
267  std::vector<FoundInstance> instances;
268 
269  FindInstances(node, instances, FMMatrix44_Identity, true);
270  if (instances.size() > 1)
271  {
272  Log(LOG_ERROR, "Found too many export-marked objects");
273  return false;
274  }
275  if (instances.empty())
276  {
277  FindInstances(node, instances, FMMatrix44_Identity, false);
278  if (instances.size() > 1)
279  {
280  Log(LOG_ERROR, "Found too many possible objects to convert - try adding the 'export' property to disambiguate one");
281  return false;
282  }
283  if (instances.empty())
284  {
285  Log(LOG_ERROR, "Didn't find any objects in the scene");
286  return false;
287  }
288  }
289 
290  assert(instances.size() == 1); // if we got this far
291  instance = instances[0].instance;
292  transform = instances[0].transform;
293  return true;
294 }
295 
296 //////////////////////////////////////////////////////////////////////////
297 
298 static bool ReverseSortWeight(const FCDJointWeightPair& a, const FCDJointWeightPair& b)
299 {
300  return (a.weight > b.weight);
301 }
302 
303 void SkinReduceInfluences(FCDSkinController* skin, size_t maxInfluenceCount, float minimumWeight)
304 {
305  // Approximately equivalent to:
306  // skin->ReduceInfluences(maxInfluenceCount, minimumWeight);
307  // except this version merges multiple weights for the same joint
308 
309  for (size_t i = 0; i < skin->GetInfluenceCount(); ++i)
310  {
311  FCDSkinControllerVertex& influence = *skin->GetVertexInfluence(i);
312 
313  std::vector<FCDJointWeightPair> newWeights;
314  for (size_t i = 0; i < influence.GetPairCount(); ++i)
315  {
316  FCDJointWeightPair* weight = influence.GetPair(i);
317 
318  for (size_t j = 0; j < newWeights.size(); ++j)
319  {
320  FCDJointWeightPair& newWeight = newWeights[j];
321  if (weight->jointIndex == newWeight.jointIndex)
322  {
323  newWeight.weight += weight->weight;
324  goto MERGED_WEIGHTS;
325  }
326  }
327 
328  newWeights.push_back(*weight);
329 MERGED_WEIGHTS: ;
330  }
331 
332  // Put highest-weighted influences at the front of the list
333  std::sort(newWeights.begin(), newWeights.end(), ReverseSortWeight);
334 
335  // Limit the maximum number of influences
336  if (newWeights.size() > maxInfluenceCount)
337  newWeights.resize(maxInfluenceCount);
338 
339  // Enforce the minimum weight per influence
340  // (This is done here rather than in the earlier loop, because several
341  // small weights for the same bone might add up to a value above the
342  // threshold)
343  while (!newWeights.empty() && newWeights.back().weight < minimumWeight)
344  newWeights.pop_back();
345 
346  // Renormalise, so sum(weights)=1
347  float totalWeight = 0;
348  for (std::vector<FCDJointWeightPair>::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW)
349  totalWeight += itNW->weight;
350  for (std::vector<FCDJointWeightPair>::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW)
351  itNW->weight /= totalWeight;
352 
353  // Copy new weights into the skin
354  influence.SetPairCount(0);
355  for (std::vector<FCDJointWeightPair>::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW)
356  influence.AddPair(itNW->jointIndex, itNW->weight);
357  }
358 
359  skin->SetDirtyFlag();
360 }
361 
362 
363 void FixSkeletonRoots(FCDControllerInstance& UNUSED(controllerInstance))
364 {
365  // TODO: Need to reintroduce XSI support at some point
366 #if 0
367  // HACK: The XSI exporter doesn't do a <skeleton> and FCollada doesn't
368  // seem to know where else to look, so just guess that it's somewhere
369  // under Scene_Root
370  if (controllerInstance.GetSkeletonRoots().empty())
371  {
372  // HACK (evil): SetSkeletonRoot is declared but not defined, and there's
373  // no other proper way to modify the skeleton-roots list, so cheat horribly
374  FUUriList& uriList = const_cast<FUUriList&>(controllerInstance.GetSkeletonRoots());
375  uriList.push_back(FUUri("Scene_Root"));
376  controllerInstance.LinkImport();
377  }
378 #endif
379 }
380 
381 const Skeleton& FindSkeleton(const FCDControllerInstance& controllerInstance)
382 {
383  // I can't see any proper way to determine the real root of the skeleton,
384  // so just choose an arbitrary bone and search upwards until we find a
385  // recognised ancestor (or until we fall off the top of the tree)
386 
387  const Skeleton* skeleton = NULL;
388  const FCDSceneNode* joint = controllerInstance.GetJoint(0);
389  while (joint && (skeleton = Skeleton::FindSkeleton(joint->GetName().c_str())) == NULL)
390  {
391  joint = joint->GetParent();
392  }
393  REQUIRE(skeleton != NULL, "recognised skeleton structure");
394  return *skeleton;
395 }
396 
397 void TransformBones(std::vector<BoneTransform>& bones, const FMMatrix44& scaleTransform, bool yUp)
398 {
399  for (size_t i = 0; i < bones.size(); ++i)
400  {
401  // Apply the desired transformation to the bone coordinates
402  FMVector3 trans(bones[i].translation, 0);
403  trans = scaleTransform.TransformCoordinate(trans);
404  bones[i].translation[0] = trans.x;
405  bones[i].translation[1] = trans.y;
406  bones[i].translation[2] = trans.z;
407 
408  // DON'T apply the transformation to orientation, because I can't get
409  // that kind of thing to work in practice (interacting nicely between
410  // the models and animations), so this function assumes the transform
411  // just does scaling, so there's no need to rotate anything. (But I think
412  // this code would work for rotation, though not very efficiently.)
413  /*
414  FMMatrix44 m = FMQuaternion(bones[i].orientation[0], bones[i].orientation[1], bones[i].orientation[2], bones[i].orientation[3]).ToMatrix();
415  m *= scaleTransform;
416  HMatrix matrix;
417  memcpy(matrix, m.Transposed().m, sizeof(matrix));
418  AffineParts parts;
419  decomp_affine(matrix, &parts);
420 
421  bones[i].orientation[0] = parts.q.x;
422  bones[i].orientation[1] = parts.q.y;
423  bones[i].orientation[2] = parts.q.z;
424  bones[i].orientation[3] = parts.q.w;
425  */
426 
427  if (yUp)
428  {
429  // TODO: this is all just guesses which seem to work for data
430  // exported from XSI, rather than having been properly thought
431  // through
432  bones[i].translation[2] = -bones[i].translation[2];
433  bones[i].orientation[2] = -bones[i].orientation[2];
434  bones[i].orientation[3] = -bones[i].orientation[3];
435  }
436  else
437  {
438  // Convert bone translations from xyz into xzy axes:
439  std::swap(bones[i].translation[1], bones[i].translation[2]);
440 
441  // To convert the quaternions: imagine you're using the axis/angle
442  // representation, then swap the y,z basis vectors and change the
443  // direction of rotation by negating the angle ( => negating sin(angle)
444  // => negating x,y,z => changing (x,y,z,w) to (-x,-z,-y,w)
445  // but then (-x,-z,-y,w) == (x,z,y,-w) so do that instead)
446  std::swap(bones[i].orientation[1], bones[i].orientation[2]);
447  bones[i].orientation[3] = -bones[i].orientation[3];
448  }
449  }
450 }
FMMatrix44 m_EntityTransform
void require_(int line, bool value, const char *type, const char *message)
static const Skeleton * FindSkeleton(const std::string &rootBoneName)
Tries to find a skeleton that matches the given root bone name.
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
#define REQUIRE(value, message)
Throws a ColladaException unless the value is true.
void ReadExtras(xmlNode *colladaNode)
static bool IsVisible(FCDSceneNode *node)
static bool IsVisible_XSI(FCDSceneNode *node, bool &visible)
FCDEntityInstance * m_Instance
FCDocument * GetDocument() const
Returns the FCDocument that was loaded.
Definition: CommonConvert.h:82
void SkinReduceInfluences(FCDSkinController *skin, size_t maxInfluenceCount, float minimumWeight)
Like FCDSkinController::ReduceInfluences but works correctly.
static void swap(UniqueRange &p1, UniqueRange &p2)
Definition: unique_range.h:176
CommonConvert(const char *text, std::string &xmlErrors)
#define LOG_ERROR
bool FindSingleInstance(FCDSceneNode *node, FCDEntityInstance *&instance, FMMatrix44 &transform)
Tries to find a single suitable entity instance in the scene.
FMMatrix44 FMMatrix44_Identity
std::auto_ptr< FCDocument > document
Definition: CommonConvert.h:89
void FixSkeletonRoots(FCDControllerInstance &controllerInstance)
Fixes some occasional problems with the skeleton root definitions in a controller.
static float identity[]
const Skeleton & FindSkeleton(const FCDControllerInstance &controllerInstance)
Finds the skeleton definition which best matches the given controller.
static void FindInstances(FCDSceneNode *node, std::vector< FoundInstance > &instances, const FMMatrix44 &transform, bool onlyMarked)
Recursively finds all entities under the current node.
#define LOG_INFO
FMMatrix44 transform
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...
FColladaErrorHandler(std::string &xmlErrors)
void OnError(FUError::Level errorLevel, uint32 errorCode, uint32 lineNumber)
const char * GetErrorString(PSRETURN code)
Definition: Errors.cpp:458
std::string & xmlErrors
Definition: CommonConvert.h:62
void LoadFromText(const char *text)
Loads the document from the given XML string.
void FixBrokenXML(const char *text, const char **out, size_t *outSize)
Fixes some errors in COLLADA XML files that would otherwise prevent FCollada from loading them succes...
Definition: XMLFix.cpp:164
#define REQUIRE_SUCCESS(status)
Throws a ColladaException unless the status is successful.
void errorHandler(void *ctx, const char *msg,...)
Error handler for libxml2.
FCDEntityInstance * instance
static bool ReverseSortWeight(const FCDJointWeightPair &a, const FCDJointWeightPair &b)
FColladaDocument m_Doc
#define LOG_WARNING
void Log(int severity, const char *msg,...)
Definition: DLL.cpp:50
const FMVector3 FMVector3_XAxis(1.0f, 0.0f, 0.0f)