Morph animation         How to actually get morph animations to work in the engine

Original Author: Joost van Dongen ("Oogst" in the forums)

Introduction

Ogre features both Morph animation and Pose animation. The difference between these two techniques is explained in the manual. However, what is not explained there, is how to actually get morph animations to work in the engine. This article provides explanation of this.

Code in the engine

Morph animation works like skeletal animation from the in-engine point of view. If there is a morph animation in the mesh, then you can get it to animate like this:

Morph AnimationState usage

//get the animation
 Ogre::AnimationState* state = entity->getAnimationState("morph");
 state->setEnabled(true);
 
 //do this every frame to play the animation
 float time = 0.1f;
 state->addTime(time);

An example shader



You can let OGRE do software blending of the morph animation, but you can also do it in hardware. The shader will only get morph data from the engine if its AnimationState is enabled, so the code above is needed.

Morph.material

vertex_program Cg_Morph_VP cg
 {
     source Morph.cg
     entry_point Morph_VP
     profiles vs_1_1
     
     includes_morph_animation true
 
     default_params
     {
         param_named_auto worldViewProjMatrix worldviewproj_matrix
          param_named_auto animationPhase animation_parametric
     }
 }
 
 material Morph
 {
     technique
     {
         pass
         {
             vertex_program_ref Cg_Morph_VP
             {
             }
         }
     }
 }

Morph.cg



Note that the second position is put in the first free texture coordinate.

void Morph_VP
     (
         float3 position1    : POSITION,
         float2 uv        : TEXCOORD0,
         float3 position2    : TEXCOORD1,
 
         out float4 oPosition : POSITION,
 
         uniform float4x4 worldViewProjMatrix,
         uniform float animationPhase
     )
 {
     float3 blendedPosition = lerp(position1, position2, animationPhase);
     oPosition = mul(worldViewProjMatrix, float4(blendedPosition, 1));
 }

Creating meshes with morph animation



Unfortunately, some exporters do not support morph animation, so it is difficult to get meshes with morph animation enabled into the engine. If your exporter does not support morph animation, then you can edit the XML files by hand to add it to your mesh. You can also use the little tool below to do this automatically, or create the morph animation procedurally in code. An example of creating morph animation in code can be found in the file PlayPen.cpp, which is in the Ogre CVS.

The XML format of meshes with morph animation

This is an example of a random mesh that has morph animation in it:

<mesh>
    <submeshes>
        <submesh material="SomeMaterial" usesharedvertices="false" use32bitindexes="false" operationtype="triangle_list">
            <faces count="3">
                <face v1="0" v2="1" v3="2" />
                <face v1="3" v2="2" v3="1" />
                <face v1="0" v2="3" v3="1" />
            </faces>
            <geometry vertexcount="4">
                <vertexbuffer positions="true">
                    <vertex>
                        <position x="0" y="1.71556e-006" z="28.7823" />
                    </vertex>
                    <vertex>
                        <position x="-15.8672" y="-16.2362" z="9.67751e-007" />
                    </vertex>
                    <vertex>
                        <position x="15.8672" y="-16.2362" z="9.67751e-007" />
                    </vertex>
                    <vertex>
                        <position x="0" y="1.71556e-006" z="28.7823" />
                    </vertex>
                </vertexbuffer>
                <vertexbuffer normals="true">
                    <vertex>
                        <normal x="0" y="-0.870978" z="0.491321" />
                    </vertex>
                    <vertex>
                        <normal x="0" y="-0.870978" z="0.491321" />
                    </vertex>
                    <vertex>
                        <normal x="0" y="-0.870978" z="0.491321" />
                    </vertex>
                    <vertex>
                        <normal x="0.875741" y="0" z="0.482781" />
                    </vertex>
                </vertexbuffer>
                <vertexbuffer texture_coord_dimensions_0="2" texture_coords="1">
                    <vertex>
                        <texcoord u="0.5" v="0" />
                    </vertex>
                    <vertex>
                        <texcoord u="0" v="1" />
                    </vertex>
                    <vertex>
                        <texcoord u="1" v="1" />
                    </vertex>
                    <vertex>
                        <texcoord u="0.5" v="0" />
                    </vertex>
                </vertexbuffer>
            </geometry>
        </submesh>
    </submeshes>
    <animations>
        <animation name="morph" length="1">
            <tracks>
                <track target="submesh" index="0" type="morph">
                    <keyframes>
                        <keyframe time="0">
                            <position x="0" y="1.71556e-006" z="28.7823" />
                            <position x="-15.8672" y="-16.2362" z="9.67751e-007" />
                            <position x="15.8672" y="-16.2362" z="9.67751e-007" />
                            <position x="0" y="1.71556e-006" z="28.7823" />
                        </keyframe>
                        <keyframe time="1">
                            <position x="0" y="1.71556e-006" z="100" />
                            <position x="-15.8672" y="-16.2362" z="100" />
                            <position x="15.8672" y="-16.2362" z="100" />
                            <position x="0" y="1.71556e-006" z="100" />
                        </keyframe>
                    </keyframes>
                </track>
            </tracks>
        </animation>
    </animations>
 </mesh>

A little tool that merges two meshes into one morph animated mesh



Creating XML files by hand is a lot of work, so here is the source code of a little tool that will take two .MESH-files and automatically merges them into one .MESH-file with vertex animation. It animations from file 1 to file 2. Note that this tool is very simple and does not check whether morphing between the meshes is actually possible. This tool can be used to export the same mesh twice in different morph situations and then create a morphing version of them. This little tool uses the OgreXMLConverter, so you need to make sure that OgreXmlConverter.exe and OgreMain.dll are in the same folder as this tool. To use it, you can for example call it from the command line using this line: MorphMeshMaker.exe Ground1.mesh Ground2.mesh. The call to OgreXmlConverter.exe will have to be changed to its equivalent on other platforms to make it work on for example Mac OS X.

#include <iostream>
 #include <fstream>
 #include <sstream>
 #include <string>
 #include <vector>
 
 
 typedef std::vector<std::string> textArray;
 
 
 textArray readFile(const std::string& fileName);
 void writeFile(const std::string& fileName, const textArray& content);
 
 void system(std::string command);
 
 std::string convertToXML(std::string fileName);
 void convertToMesh(std::string fileName);
 
 textArray getPositionLines(const textArray& file);
 
 textArray addMorphAnimation(const textArray& original,
                             const textArray& positions1,
                             const textArray& positions2);
 
 void main(int argumentCount, const char** arguments);
 
 
 textArray readFile(const std::string& fileName)
 {
     std::cout << "\n  Reading this file: " + fileName + "\n";
 
     textArray strings;
     std::ifstream file(fileName.c_str());
     if (!file.is_open())
     {
         std::cout << "  ERROR: file reading failed for file: ", fileName;
         return strings;
     }
     while (!file.eof())
     {
         std::string temp;
         getline(file, temp);
         strings.push_back(temp);
     }
     file.close();
     return strings;
 }
 
 
 void writeFile(const std::string& fileName, const textArray& content)
 {
     std::cout << "\n  Writing this file: " + fileName + "\n";
 
     std::ofstream file(fileName.c_str());
     if (!file.is_open())
     {
         std::cout << "  ERROR: file writing failed for file: ", fileName;
     }
     for (unsigned int i = 0; i < content.size(); i++)
     {
         file.write(content[i].c_str(), std::streamsize(content[i].length()));
         if (i != content.size() - 1)
         {
             file.put('\n');
         }
     }
     file.close();
 }
 
 
 void system(std::string command)
 {
     system(command.c_str());
 }
 
 
 std::string convertToXML(std::string fileName)
 {
     std::cout << "\n  Converting this file from mesh to xml: " + fileName + "\n";
     system("OgreXmlConverter.exe " + fileName);
     return fileName + ".xml";
 }
 
 
 void convertToMesh(std::string fileName)
 {
     std::cout << "\n  Converting this file from xml to mesh: " + fileName + "\n";
     system("OgreXmlConverter.exe " + fileName);
 }
 
 
 textArray getPositionLines(const textArray& file)
 {
     textArray result;
 
     size_t size = file.size();
     for (size_t i = 0; i < size; ++i)
     {
         std::stringstream line(file[i]);
         std::string identifier;
         line >> identifier;
         if (identifier == "<position")
         {
             result.push_back(file[i]);
         }
     }
 
     return result;
 }
 
 
 textArray addMorphAnimation(const textArray& original,
                  const textArray& positions1,
                  const textArray& positions2)
 { 
     std::cout << "\n  Combining files into version with morph animation\n";
 
     textArray result;
 
     size_t size = original.size();
     for (size_t i = 0; i < size; ++i)
     {
         result.push_back(original[i]);
     
         std::stringstream line(original[i]);
         std::string identifier;
         line >> identifier;
         if (identifier == "</submeshes>")
         {
             result.push_back("\t<animations>");
             result.push_back("\t\t<animation name=\"morph\" length=\"1\">");
             result.push_back("\t\t\t<tracks>");
             result.push_back("\t\t\t\t<track target=\"submesh\" index=\"0\" type=\"morph\">");
             result.push_back("\t\t\t\t\t<keyframes>");
             result.push_back("\t\t\t\t\t\t<keyframe time=\"0\">");
 
             std::cout << "\n  Adding positions from file 1 to the morphing version\n";
 
             size_t size1 = positions1.size();
             for (size_t i1 = 0; i1 < size1; ++i1)
             {
                 result.push_back("\t" + positions1[i1]);
             }
 
             result.push_back("\t\t\t\t\t\t</keyframe>");
             result.push_back("\t\t\t\t\t\t<keyframe time=\"1\">");
 
             std::cout << "\n  Adding positions from file 2 to the morphing version\n";
 
             size_t size2 = positions2.size();
             for (size_t i2 = 0; i2 < size2; ++i2)
             {
                 result.push_back("\t" + positions2[i2]);
             }
 
             result.push_back("\t\t\t\t\t\t</keyframe>");
             result.push_back("\t\t\t\t\t</keyframes>");
             result.push_back("\t\t\t\t</track>");
             result.push_back("\t\t\t</tracks>");
             result.push_back("\t\t</animation>");
             result.push_back("\t</animations>");
         }
     }
 
     return result;
 } 
 
 
 void main(int argumentCount, const char** arguments)
 { 
     if (argumentCount < 3)
     {
         std::cout << "\n  You need to pass two .mesh files as arguments to make this program work.";
     }
     else
     {
         //get file names
         std::string fileName1Mesh(arguments[1]);
         std::string fileName2Mesh(arguments[2]);
 
         //convert .mesh files to .xml
         std::string fileName1XML = convertToXML(fileName1Mesh);
         std::string fileName2XML = convertToXML(fileName2Mesh);
 
         //read the .xml files
         textArray file1 = readFile(fileName1XML);
         textArray file2 = readFile(fileName2XML);
 
         //get all the position information from the files
         textArray file1Positions = getPositionLines(file1);
         textArray file2Positions = getPositionLines(file2);
 
         //add morph animation to file1
         textArray morphed = addMorphAnimation(file1, file1Positions, file2Positions);
 
         //write the result to a .xml file
         std::string outputFileName = "Output.mesh.xml";
         writeFile(outputFileName, morphed);
 
         //convert the result to a .mesh file
         convertToMesh(outputFileName);
     }
 
     std::cout << "\n  Finished. Press any key to close this window.\n";
 
     std::string empty;
     getline(std::cin, empty);
 }