Line data Source code
1 : /* Copyright (C) 2018 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 32 : void require_(int line, bool value, const char* type, const char* message)
35 : {
36 64 : if (value) return;
37 : char linestr[16];
38 0 : sprintf(linestr, "%d", line);
39 0 : throw ColladaException(std::string(type) + " (line " + linestr + "): " + message);
40 : }
41 :
42 : /** Error handler for libxml2 */
43 18 : void errorHandler(void* ctx, const char* msg, ...)
44 : {
45 : char buffer[1024];
46 : va_list ap;
47 18 : va_start(ap, msg);
48 18 : vsnprintf(buffer, sizeof(buffer), msg, ap);
49 18 : buffer[sizeof(buffer)-1] = '\0';
50 18 : va_end(ap);
51 :
52 18 : *((std::string*)ctx) += buffer;
53 18 : }
54 :
55 5 : FColladaErrorHandler::FColladaErrorHandler(std::string& xmlErrors_)
56 5 : : xmlErrors(xmlErrors_)
57 : {
58 : // Grab all the error output from libxml2, for useful error reporting
59 5 : xmlSetGenericErrorFunc(&xmlErrors, &errorHandler);
60 :
61 5 : FUError::AddErrorCallback(FUError::DEBUG_LEVEL, this, &FColladaErrorHandler::OnError);
62 5 : FUError::AddErrorCallback(FUError::WARNING_LEVEL, this, &FColladaErrorHandler::OnError);
63 5 : FUError::AddErrorCallback(FUError::ERROR_LEVEL, this, &FColladaErrorHandler::OnError);
64 5 : }
65 :
66 10 : FColladaErrorHandler::~FColladaErrorHandler()
67 : {
68 5 : xmlSetGenericErrorFunc(NULL, NULL);
69 :
70 5 : FUError::RemoveErrorCallback(FUError::DEBUG_LEVEL, this, &FColladaErrorHandler::OnError);
71 5 : FUError::RemoveErrorCallback(FUError::WARNING_LEVEL, this, &FColladaErrorHandler::OnError);
72 5 : FUError::RemoveErrorCallback(FUError::ERROR_LEVEL, this, &FColladaErrorHandler::OnError);
73 5 : }
74 :
75 5 : 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 5 : if (errorCode == FUError::WARNING_INVALID_POLYGON_MAT_SYMBOL)
79 0 : return;
80 :
81 5 : const char* errorString = FUError::GetErrorString((FUError::Code) errorCode);
82 5 : if (! errorString)
83 0 : errorString = "Unknown error code";
84 :
85 5 : if (errorLevel == FUError::DEBUG_LEVEL)
86 4 : Log(LOG_INFO, "FCollada %d: %s", errorCode, errorString);
87 1 : else if (errorLevel == FUError::WARNING_LEVEL)
88 0 : Log(LOG_WARNING, "FCollada %d: %s", errorCode, errorString);
89 : else
90 1 : throw ColladaException(errorString);
91 : }
92 :
93 :
94 : //////////////////////////////////////////////////////////////////////////
95 :
96 5 : void FColladaDocument::LoadFromText(const char *text)
97 : {
98 5 : document.reset(FCollada::NewTopDocument());
99 :
100 5 : const char* newText = NULL;
101 5 : size_t newTextSize = 0;
102 5 : FixBrokenXML(text, &newText, &newTextSize);
103 :
104 : // Log(LOG_INFO, "%s", newText);
105 5 : bool status = FCollada::LoadDocumentFromMemory("unknown.dae", document.get(), (void*)newText, newTextSize);
106 :
107 4 : if (newText != text)
108 1 : xmlFree((void*)newText);
109 :
110 4 : REQUIRE_SUCCESS(status);
111 4 : }
112 :
113 0 : 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 0 : }
135 :
136 : //////////////////////////////////////////////////////////////////////////
137 :
138 5 : CommonConvert::CommonConvert(const char* text, std::string& xmlErrors)
139 6 : : m_Err(xmlErrors)
140 : {
141 5 : m_Doc.LoadFromText(text);
142 4 : FCDSceneNode* root = m_Doc.GetDocument()->GetVisualSceneRoot();
143 4 : REQUIRE(root != NULL, "has root object");
144 :
145 : // Find the instance to convert
146 4 : if (! FindSingleInstance(root, m_Instance, m_EntityTransform))
147 0 : throw ColladaException("Couldn't find object to convert");
148 :
149 4 : assert(m_Instance);
150 4 : Log(LOG_INFO, "Converting '%s'", m_Instance->GetEntity()->GetName().c_str());
151 :
152 4 : m_IsXSI = false;
153 4 : FCDAsset* asset = m_Doc.GetDocument()->GetAsset();
154 4 : if (asset && asset->GetContributorCount() >= 1)
155 : {
156 8 : std::string tool (asset->GetContributor(0)->GetAuthoringTool());
157 4 : if (tool.find("XSI") != tool.npos)
158 0 : m_IsXSI = true;
159 : }
160 :
161 4 : FMVector3 upAxis = m_Doc.GetDocument()->GetAsset()->GetUpAxis();
162 4 : m_YUp = (upAxis.y != 0); // assume either Y_UP or Z_UP (TODO: does anyone ever do X_UP?)
163 4 : }
164 :
165 4 : CommonConvert::~CommonConvert()
166 : {
167 4 : }
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 1 : 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 1 : FMMatrix44 FMMatrix44_Identity(identity);
176 :
177 4 : struct FoundInstance
178 : {
179 : FCDEntityInstance* instance;
180 : FMMatrix44 transform;
181 : };
182 :
183 4 : static bool IsVisible_XSI(FCDSceneNode* node, bool& visible)
184 : {
185 : // Look for <extra><technique profile="XSI"><SI_Visibility><xsi_param sid="visibility">
186 :
187 4 : FCDExtra* extra = node->GetExtra();
188 4 : if (! extra) return false;
189 :
190 4 : FCDEType* type = extra->GetDefaultType();
191 4 : if (! type) return false;
192 :
193 4 : FCDETechnique* technique = type->FindTechnique("XSI");
194 4 : if (! technique) return false;
195 :
196 0 : FCDENode* visibility1 = technique->FindChildNode("SI_Visibility");
197 0 : if (! visibility1) return false;
198 :
199 0 : FCDENode* visibility2 = visibility1->FindChildNode("xsi_param");
200 0 : if (! visibility2) return false;
201 :
202 0 : if (IsEquivalent(visibility2->GetContent(), "TRUE"))
203 0 : visible = true;
204 0 : else if (IsEquivalent(visibility2->GetContent(), "FALSE"))
205 0 : visible = false;
206 0 : return true;
207 : }
208 :
209 4 : static bool IsVisible(FCDSceneNode* node)
210 : {
211 4 : bool visible = false;
212 :
213 : // Try the XSI visibility property
214 4 : if (IsVisible_XSI(node, visible))
215 0 : return visible;
216 :
217 : // Else fall back to the FCollada-specific setting
218 4 : visible = (node->GetVisibility() != 0.0);
219 4 : 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 18 : static void FindInstances(FCDSceneNode* node, std::vector<FoundInstance>& instances, const FMMatrix44& transform, bool onlyMarked)
233 : {
234 28 : for (size_t i = 0; i < node->GetChildrenCount(); ++i)
235 : {
236 10 : FCDSceneNode* child = node->GetChild(i);
237 10 : FindInstances(child, instances, transform * node->ToMatrix(), onlyMarked);
238 : }
239 :
240 26 : for (size_t i = 0; i < node->GetInstanceCount(); ++i)
241 : {
242 8 : if (onlyMarked)
243 : {
244 4 : if (node->GetNote() != "export")
245 8 : continue;
246 : }
247 :
248 : // Only accept instances of appropriate types, and not e.g. lights
249 4 : FCDEntity::Type type = node->GetInstance(i)->GetEntityType();
250 4 : if (! (type == FCDEntity::GEOMETRY || type == FCDEntity::CONTROLLER))
251 0 : continue;
252 :
253 : // Ignore invisible objects, because presumably nobody wanted to export them
254 4 : if (! IsVisible(node))
255 0 : continue;
256 :
257 4 : FoundInstance f;
258 4 : f.transform = transform * node->ToMatrix();
259 4 : f.instance = node->GetInstance(i);
260 4 : instances.push_back(f);
261 4 : Log(LOG_INFO, "Found convertible object '%s'", node->GetName().c_str());
262 : }
263 18 : }
264 :
265 4 : bool FindSingleInstance(FCDSceneNode* node, FCDEntityInstance*& instance, FMMatrix44& transform)
266 : {
267 8 : std::vector<FoundInstance> instances;
268 :
269 4 : FindInstances(node, instances, FMMatrix44_Identity, true);
270 4 : if (instances.size() > 1)
271 : {
272 0 : Log(LOG_ERROR, "Found too many export-marked objects");
273 0 : return false;
274 : }
275 4 : if (instances.empty())
276 : {
277 4 : FindInstances(node, instances, FMMatrix44_Identity, false);
278 4 : if (instances.size() > 1)
279 : {
280 0 : Log(LOG_ERROR, "Found too many possible objects to convert - try adding the 'export' property to disambiguate one");
281 0 : return false;
282 : }
283 4 : if (instances.empty())
284 : {
285 0 : Log(LOG_ERROR, "Didn't find any objects in the scene");
286 0 : return false;
287 : }
288 : }
289 :
290 4 : assert(instances.size() == 1); // if we got this far
291 4 : instance = instances[0].instance;
292 4 : transform = instances[0].transform;
293 4 : return true;
294 : }
295 :
296 : //////////////////////////////////////////////////////////////////////////
297 :
298 0 : static bool ReverseSortWeight(const FCDJointWeightPair& a, const FCDJointWeightPair& b)
299 : {
300 0 : return (a.weight > b.weight);
301 : }
302 :
303 0 : 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 0 : for (size_t i = 0; i < skin->GetInfluenceCount(); ++i)
310 : {
311 0 : FCDSkinControllerVertex& influence = *skin->GetVertexInfluence(i);
312 :
313 0 : std::vector<FCDJointWeightPair> newWeights;
314 0 : for (size_t j = 0; j < influence.GetPairCount(); ++j)
315 : {
316 0 : FCDJointWeightPair* weight = influence.GetPair(j);
317 :
318 0 : for (size_t k = 0; k < newWeights.size(); ++k)
319 : {
320 0 : FCDJointWeightPair& newWeight = newWeights[k];
321 0 : if (weight->jointIndex == newWeight.jointIndex)
322 : {
323 0 : newWeight.weight += weight->weight;
324 0 : goto MERGED_WEIGHTS;
325 : }
326 : }
327 :
328 0 : newWeights.push_back(*weight);
329 0 : MERGED_WEIGHTS: ;
330 : }
331 :
332 : // Put highest-weighted influences at the front of the list
333 0 : std::sort(newWeights.begin(), newWeights.end(), ReverseSortWeight);
334 :
335 : // Limit the maximum number of influences
336 0 : if (newWeights.size() > maxInfluenceCount)
337 0 : 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 0 : while (!newWeights.empty() && newWeights.back().weight < minimumWeight)
344 0 : newWeights.pop_back();
345 :
346 : // Renormalise, so sum(weights)=1
347 0 : float totalWeight = 0;
348 0 : for (std::vector<FCDJointWeightPair>::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW)
349 0 : totalWeight += itNW->weight;
350 0 : for (std::vector<FCDJointWeightPair>::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW)
351 0 : itNW->weight /= totalWeight;
352 :
353 : // Copy new weights into the skin
354 0 : influence.SetPairCount(0);
355 0 : for (std::vector<FCDJointWeightPair>::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW)
356 0 : influence.AddPair(itNW->jointIndex, itNW->weight);
357 : }
358 :
359 0 : skin->SetDirtyFlag();
360 0 : }
361 :
362 :
363 0 : 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 0 : }
380 :
381 0 : 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 0 : const Skeleton* skeleton = NULL;
388 0 : const FCDSceneNode* joint = controllerInstance.GetJoint(0);
389 0 : while (joint && (skeleton = Skeleton::FindSkeleton(joint->GetName().c_str())) == NULL)
390 : {
391 0 : joint = joint->GetParent();
392 : }
393 0 : REQUIRE(skeleton != NULL, "recognised skeleton structure");
394 0 : return *skeleton;
395 : }
396 :
397 0 : void TransformBones(std::vector<BoneTransform>& bones, const FMMatrix44& scaleTransform, bool yUp)
398 : {
399 0 : for (size_t i = 0; i < bones.size(); ++i)
400 : {
401 : // Apply the desired transformation to the bone coordinates
402 0 : FMVector3 trans(bones[i].translation, 0);
403 0 : trans = scaleTransform.TransformCoordinate(trans);
404 0 : bones[i].translation[0] = trans.x;
405 0 : bones[i].translation[1] = trans.y;
406 0 : 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 0 : 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 0 : bones[i].translation[2] = -bones[i].translation[2];
433 0 : bones[i].orientation[2] = -bones[i].orientation[2];
434 0 : bones[i].orientation[3] = -bones[i].orientation[3];
435 : }
436 : else
437 : {
438 : // Convert bone translations from xyz into xzy axes:
439 0 : 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 0 : std::swap(bones[i].orientation[1], bones[i].orientation[2]);
447 0 : bones[i].orientation[3] = -bones[i].orientation[3];
448 : }
449 : }
450 3 : }
|