Merge 'remotes/trunk'
[0ad.git] / source / collada / CommonConvert.cpp
blob0833fab8435c76f146ff7b55aee0a44e0c0dd97e
1 /* Copyright (C) 2018 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "CommonConvert.h"
22 #include "StdSkeletons.h"
23 #include "XMLFix.h"
25 #include "FCollada.h"
26 #include "FCDocument/FCDSceneNode.h"
27 #include "FCDocument/FCDSkinController.h"
28 #include "FUtils/FUDaeSyntax.h"
29 #include "FUtils/FUFileManager.h"
31 #include <cassert>
32 #include <algorithm>
34 void require_(int line, bool value, const char* type, const char* message)
36 if (value) return;
37 char linestr[16];
38 sprintf(linestr, "%d", line);
39 throw ColladaException(std::string(type) + " (line " + linestr + "): " + message);
42 /** Error handler for libxml2 */
43 void errorHandler(void* ctx, const char* msg, ...)
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);
52 *((std::string*)ctx) += buffer;
55 FColladaErrorHandler::FColladaErrorHandler(std::string& xmlErrors_)
56 : xmlErrors(xmlErrors_)
58 // Grab all the error output from libxml2, for useful error reporting
59 xmlSetGenericErrorFunc(&xmlErrors, &errorHandler);
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);
66 FColladaErrorHandler::~FColladaErrorHandler()
68 xmlSetGenericErrorFunc(NULL, NULL);
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);
75 void FColladaErrorHandler::OnError(FUError::Level errorLevel, uint32 errorCode, uint32 UNUSED(lineNumber))
77 // Ignore warnings about missing materials, since we ignore materials entirely anyway
78 if (errorCode == FUError::WARNING_INVALID_POLYGON_MAT_SYMBOL)
79 return;
81 const char* errorString = FUError::GetErrorString((FUError::Code) errorCode);
82 if (! errorString)
83 errorString = "Unknown error code";
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);
94 //////////////////////////////////////////////////////////////////////////
96 void FColladaDocument::LoadFromText(const char *text)
98 document.reset(FCollada::NewTopDocument());
100 const char* newText = NULL;
101 size_t newTextSize = 0;
102 FixBrokenXML(text, &newText, &newTextSize);
104 // Log(LOG_INFO, "%s", newText);
105 bool status = FCollada::LoadDocumentFromMemory("unknown.dae", document.get(), (void*)newText, newTextSize);
107 if (newText != text)
108 xmlFree((void*)newText);
110 REQUIRE_SUCCESS(status);
113 void FColladaDocument::ReadExtras(xmlNode* UNUSED(colladaNode))
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.
121 if (! IsEquivalent(colladaNode->name, DAE_COLLADA_ELEMENT))
122 return;
124 extra.reset(new FCDExtra(document.get()));
126 xmlNodeList extraNodes;
127 FUXmlParser::FindChildrenByType(colladaNode, DAE_EXTRA_ELEMENT, extraNodes);
128 for (xmlNodeList::iterator it = extraNodes.begin(); it != extraNodes.end(); ++it)
130 xmlNode* extraNode = (*it);
131 extra->LoadFromXML(extraNode);
136 //////////////////////////////////////////////////////////////////////////
138 CommonConvert::CommonConvert(const char* text, std::string& xmlErrors)
139 : m_Err(xmlErrors)
141 m_Doc.LoadFromText(text);
142 FCDSceneNode* root = m_Doc.GetDocument()->GetVisualSceneRoot();
143 REQUIRE(root != NULL, "has root object");
145 // Find the instance to convert
146 if (! FindSingleInstance(root, m_Instance, m_EntityTransform))
147 throw ColladaException("Couldn't find object to convert");
149 assert(m_Instance);
150 Log(LOG_INFO, "Converting '%s'", m_Instance->GetEntity()->GetName().c_str());
152 m_IsXSI = false;
153 FCDAsset* asset = m_Doc.GetDocument()->GetAsset();
154 if (asset && asset->GetContributorCount() >= 1)
156 std::string tool (asset->GetContributor(0)->GetAuthoringTool());
157 if (tool.find("XSI") != tool.npos)
158 m_IsXSI = true;
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?)
165 CommonConvert::~CommonConvert()
169 //////////////////////////////////////////////////////////////////////////
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);
177 struct FoundInstance
179 FCDEntityInstance* instance;
180 FMMatrix44 transform;
183 static bool IsVisible_XSI(FCDSceneNode* node, bool& visible)
185 // Look for <extra><technique profile="XSI"><SI_Visibility><xsi_param sid="visibility">
187 FCDExtra* extra = node->GetExtra();
188 if (! extra) return false;
190 FCDEType* type = extra->GetDefaultType();
191 if (! type) return false;
193 FCDETechnique* technique = type->FindTechnique("XSI");
194 if (! technique) return false;
196 FCDENode* visibility1 = technique->FindChildNode("SI_Visibility");
197 if (! visibility1) return false;
199 FCDENode* visibility2 = visibility1->FindChildNode("xsi_param");
200 if (! visibility2) return false;
202 if (IsEquivalent(visibility2->GetContent(), "TRUE"))
203 visible = true;
204 else if (IsEquivalent(visibility2->GetContent(), "FALSE"))
205 visible = false;
206 return true;
209 static bool IsVisible(FCDSceneNode* node)
211 bool visible = false;
213 // Try the XSI visibility property
214 if (IsVisible_XSI(node, visible))
215 return visible;
217 // Else fall back to the FCollada-specific setting
218 visible = (node->GetVisibility() != 0.0);
219 return visible;
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.
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
232 static void FindInstances(FCDSceneNode* node, std::vector<FoundInstance>& instances, const FMMatrix44& transform, bool onlyMarked)
234 for (size_t i = 0; i < node->GetChildrenCount(); ++i)
236 FCDSceneNode* child = node->GetChild(i);
237 FindInstances(child, instances, transform * node->ToMatrix(), onlyMarked);
240 for (size_t i = 0; i < node->GetInstanceCount(); ++i)
242 if (onlyMarked)
244 if (node->GetNote() != "export")
245 continue;
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;
253 // Ignore invisible objects, because presumably nobody wanted to export them
254 if (! IsVisible(node))
255 continue;
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());
265 bool FindSingleInstance(FCDSceneNode* node, FCDEntityInstance*& instance, FMMatrix44& transform)
267 std::vector<FoundInstance> instances;
269 FindInstances(node, instances, FMMatrix44_Identity, true);
270 if (instances.size() > 1)
272 Log(LOG_ERROR, "Found too many export-marked objects");
273 return false;
275 if (instances.empty())
277 FindInstances(node, instances, FMMatrix44_Identity, false);
278 if (instances.size() > 1)
280 Log(LOG_ERROR, "Found too many possible objects to convert - try adding the 'export' property to disambiguate one");
281 return false;
283 if (instances.empty())
285 Log(LOG_ERROR, "Didn't find any objects in the scene");
286 return false;
290 assert(instances.size() == 1); // if we got this far
291 instance = instances[0].instance;
292 transform = instances[0].transform;
293 return true;
296 //////////////////////////////////////////////////////////////////////////
298 static bool ReverseSortWeight(const FCDJointWeightPair& a, const FCDJointWeightPair& b)
300 return (a.weight > b.weight);
303 void SkinReduceInfluences(FCDSkinController* skin, size_t maxInfluenceCount, float minimumWeight)
305 // Approximately equivalent to:
306 // skin->ReduceInfluences(maxInfluenceCount, minimumWeight);
307 // except this version merges multiple weights for the same joint
309 for (size_t i = 0; i < skin->GetInfluenceCount(); ++i)
311 FCDSkinControllerVertex& influence = *skin->GetVertexInfluence(i);
313 std::vector<FCDJointWeightPair> newWeights;
314 for (size_t j = 0; j < influence.GetPairCount(); ++j)
316 FCDJointWeightPair* weight = influence.GetPair(j);
318 for (size_t k = 0; k < newWeights.size(); ++k)
320 FCDJointWeightPair& newWeight = newWeights[k];
321 if (weight->jointIndex == newWeight.jointIndex)
323 newWeight.weight += weight->weight;
324 goto MERGED_WEIGHTS;
328 newWeights.push_back(*weight);
329 MERGED_WEIGHTS: ;
332 // Put highest-weighted influences at the front of the list
333 std::sort(newWeights.begin(), newWeights.end(), ReverseSortWeight);
335 // Limit the maximum number of influences
336 if (newWeights.size() > maxInfluenceCount)
337 newWeights.resize(maxInfluenceCount);
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();
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;
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);
359 skin->SetDirtyFlag();
363 void FixSkeletonRoots(FCDControllerInstance& UNUSED(controllerInstance))
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())
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();
378 #endif
381 const Skeleton& FindSkeleton(const FCDControllerInstance& controllerInstance)
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)
387 const Skeleton* skeleton = NULL;
388 const FCDSceneNode* joint = controllerInstance.GetJoint(0);
389 while (joint && (skeleton = Skeleton::FindSkeleton(joint->GetName().c_str())) == NULL)
391 joint = joint->GetParent();
393 REQUIRE(skeleton != NULL, "recognised skeleton structure");
394 return *skeleton;
397 void TransformBones(std::vector<BoneTransform>& bones, const FMMatrix44& scaleTransform, bool yUp)
399 for (size_t i = 0; i < bones.size(); ++i)
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;
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.)
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);
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;
427 if (yUp)
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];
436 else
438 // Convert bone translations from xyz into xzy axes:
439 std::swap(bones[i].translation[1], bones[i].translation[2]);
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];