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"
26 #include "FCDocument/FCDSceneNode.h"
27 #include "FCDocument/FCDSkinController.h"
28 #include "FUtils/FUDaeSyntax.h"
29 #include "FUtils/FUFileManager.h"
34 void require_(int line
, bool value
, const char* type
, const char* message
)
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
, ...)
48 vsnprintf(buffer
, sizeof(buffer
), msg
, ap
);
49 buffer
[sizeof(buffer
)-1] = '\0';
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
)
81 const char* errorString
= FUError::GetErrorString((FUError::Code
) errorCode
);
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
);
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
);
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))
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
)
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");
150 Log(LOG_INFO
, "Converting '%s'", m_Instance
->GetEntity()->GetName().c_str());
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
)
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
);
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"))
204 else if (IsEquivalent(visibility2
->GetContent(), "FALSE"))
209 static bool IsVisible(FCDSceneNode
* node
)
211 bool visible
= false;
213 // Try the XSI visibility property
214 if (IsVisible_XSI(node
, visible
))
217 // Else fall back to the FCollada-specific setting
218 visible
= (node
->GetVisibility() != 0.0);
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
)
244 if (node
->GetNote() != "export")
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
))
253 // Ignore invisible objects, because presumably nobody wanted to export them
254 if (! IsVisible(node
))
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");
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");
283 if (instances
.empty())
285 Log(LOG_ERROR
, "Didn't find any objects in the scene");
290 assert(instances
.size() == 1); // if we got this far
291 instance
= instances
[0].instance
;
292 transform
= instances
[0].transform
;
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
;
328 newWeights
.push_back(*weight
);
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
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
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
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();
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");
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();
417 memcpy(matrix, m.Transposed().m, sizeof(matrix));
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;
429 // TODO: this is all just guesses which seem to work for data
430 // exported from XSI, rather than having been properly thought
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];
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];