LCOV - code coverage report
Current view: top level - source/collada - CommonConvert.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 103 183 56.3 %
Date: 2023-01-19 00:18:29 Functions: 15 21 71.4 %

          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 : }

Generated by: LCOV version 1.13