LCOV - code coverage report
Current view: top level - source/collada - PSAConvert.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 0 125 0.0 %
Date: 2023-01-19 00:18:29 Functions: 0 7 0.0 %

          Line data    Source code
       1             : /* Copyright (C) 2021 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 "PSAConvert.h"
      21             : #include "CommonConvert.h"
      22             : 
      23             : #include "FCollada.h"
      24             : #include "FCDocument/FCDocument.h"
      25             : #include "FCDocument/FCDocumentTools.h"
      26             : #include "FCDocument/FCDAnimated.h"
      27             : #include "FCDocument/FCDAnimationCurve.h"
      28             : #include "FCDocument/FCDAnimationKey.h"
      29             : #include "FCDocument/FCDController.h"
      30             : #include "FCDocument/FCDControllerInstance.h"
      31             : #include "FCDocument/FCDExtra.h"
      32             : #include "FCDocument/FCDGeometry.h"
      33             : #include "FCDocument/FCDGeometryMesh.h"
      34             : #include "FCDocument/FCDGeometryPolygons.h"
      35             : #include "FCDocument/FCDGeometrySource.h"
      36             : #include "FCDocument/FCDSceneNode.h"
      37             : 
      38             : #include "StdSkeletons.h"
      39             : #include "Decompose.h"
      40             : #include "Maths.h"
      41             : #include "GeomReindex.h"
      42             : 
      43             : #include <cassert>
      44             : #include <vector>
      45             : #include <limits>
      46             : #include <iterator>
      47             : #include <algorithm>
      48             : 
      49             : class PSAConvert
      50             : {
      51             : public:
      52             :     /**
      53             :      * Converts a COLLADA XML document into the PSA animation format.
      54             :      *
      55             :      * @param input XML document to parse
      56             :      * @param output callback for writing the PSA data; called lots of times
      57             :      *               with small strings
      58             :      * @param xmlErrors output - errors reported by the XML parser
      59             :      * @throws ColladaException on failure
      60             :      */
      61           0 :     static void ColladaToPSA(const char* input, OutputCB& output, std::string& xmlErrors)
      62             :     {
      63           0 :         CommonConvert converter(input, xmlErrors);
      64             : 
      65           0 :         if (converter.GetInstance().GetType() == FCDEntityInstance::CONTROLLER)
      66             :         {
      67           0 :             FCDControllerInstance& controllerInstance = static_cast<FCDControllerInstance&>(converter.GetInstance());
      68             : 
      69           0 :             FixSkeletonRoots(controllerInstance);
      70             : 
      71           0 :             assert(converter.GetInstance().GetEntity()->GetType() == FCDEntity::CONTROLLER); // assume this is always true?
      72           0 :             FCDController* controller = static_cast<FCDController*>(converter.GetInstance().GetEntity());
      73             : 
      74           0 :             FCDSkinController* skin = controller->GetSkinController();
      75           0 :             REQUIRE(skin != NULL, "is skin controller");
      76             : 
      77           0 :             const Skeleton& skeleton = FindSkeleton(controllerInstance);
      78             : 
      79           0 :             float frameLength = 1.f / 30.f; // currently we always want to create PMDs at fixed 30fps
      80             : 
      81             :             // Find the extents of the animation:
      82             : 
      83           0 :             float timeStart = 0, timeEnd = 0;
      84           0 :             GetAnimationRange(converter.GetDocument(), skeleton, controllerInstance, timeStart, timeEnd);
      85             :             // To catch broken animations / skeletons.xml:
      86           0 :             REQUIRE(timeEnd > timeStart, "animation end frame must come after start frame");
      87             : 
      88             :             // Count frames; don't include the last keyframe
      89           0 :             size_t frameCount = (size_t)((timeEnd - timeStart) / frameLength - 0.5f);
      90           0 :             REQUIRE(frameCount > 0, "animation must have frames");
      91             :             // (TODO: sort out the timing/looping problems)
      92             : 
      93           0 :             const size_t boneCount = skeleton.GetBoneCount();
      94           0 :             if (boneCount > 64)
      95           0 :                 Log(LOG_ERROR, "Skeleton has too many bones %zu/64", boneCount);
      96             : 
      97           0 :             std::vector<BoneTransform> boneTransforms;
      98             : 
      99           0 :             for (size_t frame = 0; frame < frameCount; ++frame)
     100             :             {
     101           0 :                 float time = timeStart + frameLength * frame;
     102             : 
     103           0 :                 BoneTransform boneDefault  = { { 0, 0, 0 }, { 0, 0, 0, 1 } };
     104           0 :                 std::vector<BoneTransform> frameBoneTransforms(boneCount, boneDefault);
     105             : 
     106             :                 // Move the model into the new animated pose
     107             :                 // (We can't tell exactly which nodes should be animated, so
     108             :                 // just update the entire world recursively)
     109           0 :                 EvaluateAnimations(converter.GetRoot(), time);
     110             : 
     111             :                 // Convert the pose into the form require by the game
     112           0 :                 for (size_t i = 0; i < controllerInstance.GetJointCount(); ++i)
     113             :                 {
     114           0 :                     FCDSceneNode* joint = controllerInstance.GetJoint(i);
     115             : 
     116           0 :                     int boneId = skeleton.GetRealBoneID(joint->GetName().c_str());
     117           0 :                     if (boneId < 0)
     118           0 :                         continue; // not a recognised bone - ignore it, same as before
     119             : 
     120           0 :                     FMMatrix44 worldTransform = joint->CalculateWorldTransform();
     121             : 
     122             :                     HMatrix matrix;
     123           0 :                     memcpy(matrix, worldTransform.Transposed().m, sizeof(matrix));
     124             : 
     125             :                     AffineParts parts;
     126           0 :                     decomp_affine(matrix, &parts);
     127             : 
     128             :                     BoneTransform b = {
     129           0 :                         { parts.t.x, parts.t.y, parts.t.z },
     130           0 :                         { parts.q.x, parts.q.y, parts.q.z, parts.q.w }
     131           0 :                     };
     132             : 
     133           0 :                     frameBoneTransforms[boneId] = b;
     134             :                 }
     135             : 
     136             :                 // Push frameBoneTransforms onto the back of boneTransforms
     137           0 :                 copy(frameBoneTransforms.begin(), frameBoneTransforms.end(),
     138           0 :                     std::inserter(boneTransforms, boneTransforms.end()));
     139             :             }
     140             : 
     141             :             // Convert into game's coordinate space
     142           0 :             TransformVertices(boneTransforms, skin->GetBindShapeTransform(), converter.IsYUp(), converter.IsXSI());
     143             : 
     144             :             // Write out the file
     145           0 :             WritePSA(output, frameCount, boneCount, boneTransforms);
     146             :         }
     147             :         else
     148             :         {
     149           0 :             throw ColladaException("Unrecognised object type");
     150             :         }
     151           0 :     }
     152             : 
     153             :     /**
     154             :      * Writes the animation data in the PSA format.
     155             :      */
     156           0 :     static void WritePSA(OutputCB& output, size_t frameCount, size_t boneCount, const std::vector<BoneTransform>& boneTransforms)
     157             :     {
     158           0 :         output("PSSA", 4);  // magic number
     159           0 :         write(output, (uint32)1); // version number
     160           0 :         write(output, (uint32)(
     161             :             4 + 0 + // name
     162             :             4 + // frameLength
     163           0 :             4 + 4 + // numBones, numFrames
     164             :             7*4*boneCount*frameCount // boneStates
     165             :             )); // data size
     166             : 
     167             :         // Name
     168           0 :         write(output, (uint32)0);
     169             : 
     170             :         // Frame length
     171           0 :         write(output, 1000.f/30.f);
     172             : 
     173           0 :         write(output, (uint32)boneCount);
     174           0 :         write(output, (uint32)frameCount);
     175             : 
     176           0 :         for (size_t i = 0; i < boneCount*frameCount; ++i)
     177             :         {
     178           0 :             output((char*)&boneTransforms[i], 7*4);
     179             :         }
     180           0 :     }
     181             : 
     182           0 :     static void TransformVertices(std::vector<BoneTransform>& bones,
     183             :         const FMMatrix44& transform, bool yUp, bool isXSI)
     184             :     {
     185             :         // HACK: we want to handle scaling in XSI because that makes it easy
     186             :         // for artists to adjust the models to the right size. But this way
     187             :         // doesn't work in Max, and I can't see how to make it do so, so this
     188             :         // is only applied to models from XSI.
     189           0 :         if (isXSI)
     190             :         {
     191           0 :             TransformBones(bones, DecomposeToScaleMatrix(transform), yUp);
     192             :         }
     193             :         else
     194             :         {
     195           0 :             TransformBones(bones, FMMatrix44_Identity, yUp);
     196             :         }
     197           0 :     }
     198             : 
     199           0 :     static void GetAnimationRange(const FColladaDocument& doc, const Skeleton& skeleton,
     200             :         const FCDControllerInstance& controllerInstance,
     201             :         float& timeStart, float& timeEnd)
     202             :     {
     203             :         // FCollada tools export <extra> info in the scene to specify the start
     204             :         // and end times.
     205             :         // If that isn't available, we have to search for the earliest and latest
     206             :         // keyframes on any of the bones.
     207           0 :         if (doc.GetDocument()->HasStartTime() && doc.GetDocument()->HasEndTime())
     208             :         {
     209           0 :             timeStart = doc.GetDocument()->GetStartTime();
     210           0 :             timeEnd = doc.GetDocument()->GetEndTime();
     211           0 :             return;
     212             :         }
     213             : 
     214             :         // XSI exports relevant information in
     215             :         // <extra><technique profile="XSI"><SI_Scene><xsi_param sid="start">
     216             :         // (and 'end' and 'frameRate') so use those
     217           0 :         if (GetAnimationRange_XSI(doc, timeStart, timeEnd))
     218           0 :             return;
     219             : 
     220           0 :         timeStart = std::numeric_limits<float>::max();
     221           0 :         timeEnd = -std::numeric_limits<float>::max();
     222           0 :         for (size_t i = 0; i < controllerInstance.GetJointCount(); ++i)
     223             :         {
     224           0 :             const FCDSceneNode* joint = controllerInstance.GetJoint(i);
     225           0 :             REQUIRE(joint != NULL, "joint exists");
     226             : 
     227           0 :             int boneId = skeleton.GetBoneID(joint->GetName().c_str());
     228           0 :             if (boneId < 0)
     229             :             {
     230             :                 // unrecognised joint - it's probably just a prop point
     231             :                 // or something, so ignore it
     232           0 :                 continue;
     233             :             }
     234             : 
     235             :             // Skip unanimated joints
     236           0 :             if (joint->GetTransformCount() == 0)
     237           0 :                 continue;
     238             : 
     239           0 :             for (size_t j = 0; j < joint->GetTransformCount(); ++j)
     240             :             {
     241           0 :                 const FCDTransform* transform = joint->GetTransform(j);
     242             : 
     243           0 :                 if (! transform->IsAnimated())
     244           0 :                     continue;
     245             : 
     246             :                 // Iterate over all curves to find the earliest and latest keys
     247           0 :                 const FCDAnimated* anim = transform->GetAnimated();
     248           0 :                 const FCDAnimationCurveListList& curvesList = anim->GetCurves();
     249           0 :                 for (size_t k = 0; k < curvesList.size(); ++k)
     250             :                 {
     251           0 :                     const FCDAnimationCurveTrackList& curves = curvesList[k];
     252           0 :                     for (size_t l = 0; l < curves.size(); ++l)
     253             :                     {
     254           0 :                         const FCDAnimationCurve* curve = curves[l];
     255           0 :                         timeStart = std::min(timeStart, curve->GetKeys()[0]->input);
     256           0 :                         timeEnd = std::max(timeEnd, curve->GetKeys()[curve->GetKeyCount()-1]->input);
     257             :                     }
     258             :                 }
     259             :             }
     260             :         }
     261             :     }
     262             : 
     263           0 :     static bool GetAnimationRange_XSI(const FColladaDocument& doc, float& timeStart, float& timeEnd)
     264             :     {
     265           0 :         FCDExtra* extra = doc.GetExtra();
     266           0 :         if (! extra) return false;
     267             : 
     268           0 :         FCDEType* type = extra->GetDefaultType();
     269           0 :         if (! type) return false;
     270             : 
     271           0 :         FCDETechnique* technique = type->FindTechnique("XSI");
     272           0 :         if (! technique) return false;
     273             : 
     274           0 :         FCDENode* scene = technique->FindChildNode("SI_Scene");
     275           0 :         if (! scene) return false;
     276             : 
     277           0 :         float start = FLT_MAX, end = -FLT_MAX, framerate = 0.f;
     278             : 
     279           0 :         FCDENodeList paramNodes;
     280           0 :         scene->FindChildrenNodes("xsi_param", paramNodes);
     281           0 :         for (FCDENodeList::iterator it = paramNodes.begin(); it != paramNodes.end(); ++it)
     282             :         {
     283           0 :             if ((*it)->ReadAttribute("sid") == "start")
     284           0 :                 start = FUStringConversion::ToFloat((*it)->GetContent());
     285           0 :             else if ((*it)->ReadAttribute("sid") == "end")
     286           0 :                 end = FUStringConversion::ToFloat((*it)->GetContent());
     287           0 :             else if ((*it)->ReadAttribute("sid") == "frameRate")
     288           0 :                 framerate = FUStringConversion::ToFloat((*it)->GetContent());
     289             :         }
     290             : 
     291           0 :         if (framerate != 0.f && start != FLT_MAX && end != -FLT_MAX)
     292             :         {
     293           0 :             timeStart = start / framerate;
     294           0 :             timeEnd = end / framerate;
     295           0 :             return true;
     296             :         }
     297             : 
     298           0 :         return false;
     299             :     }
     300             : 
     301           0 :     static void EvaluateAnimations(FCDSceneNode& node, float time)
     302             :     {
     303           0 :         for (size_t i = 0; i < node.GetTransformCount(); ++i)
     304             :         {
     305           0 :             FCDTransform* transform = node.GetTransform(i);
     306           0 :             FCDAnimated* anim = transform->GetAnimated();
     307           0 :             if (anim)
     308           0 :                 anim->Evaluate(time);
     309             :         }
     310             : 
     311           0 :         for (size_t i = 0; i < node.GetChildrenCount(); ++i)
     312           0 :             EvaluateAnimations(*node.GetChild(i), time);
     313           0 :     }
     314             : 
     315             : };
     316             : 
     317             : 
     318             : // The above stuff is just in a class since I don't like having to bother
     319             : // with forward declarations of functions - but provide the plain function
     320             : // interface here:
     321             : 
     322           0 : void ColladaToPSA(const char* input, OutputCB& output, std::string& xmlErrors)
     323             : {
     324           0 :     PSAConvert::ColladaToPSA(input, output, xmlErrors);
     325           0 : }

Generated by: LCOV version 1.13