754 lines
28 KiB
C++
Executable File
754 lines
28 KiB
C++
Executable File
#pragma warning ( disable: 4786 )
|
|
#include <windows.h>
|
|
#include <stack>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <set>
|
|
#include <cmath>
|
|
#include <cassert>
|
|
|
|
#include "DTSShape.h"
|
|
#include "DTSBrushMesh.h"
|
|
#include "DTSMilkshapeMesh.h"
|
|
#include "DTSMilkshapeShape.h"
|
|
|
|
#include "msLib.h"
|
|
|
|
namespace DTS
|
|
{
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Interpolation methods used to compute tween frames - phdana
|
|
|
|
void lerpTranslation(Point *out, Point &a, Point&b, float alpha)
|
|
{
|
|
out->x(a.x() + (b.x()-a.x()) * alpha);
|
|
out->y(a.y() + (b.y()-a.y()) * alpha);
|
|
out->z(a.z() + (b.z()-a.z()) * alpha);
|
|
}
|
|
|
|
void lerpTranslation(Point *out, Point &a, Point &b, int outFrame, int aFrame, int bFrame)
|
|
{
|
|
float alpha = (float)(outFrame - aFrame) / (float)(bFrame - aFrame);
|
|
|
|
lerpTranslation(out,a,b,alpha);
|
|
}
|
|
|
|
void lerpRotation(Quaternion *out, Quaternion &q1, Quaternion &q2, float alpha)
|
|
{
|
|
//-----------------------------------
|
|
// Calculate the cosine of the angle:
|
|
|
|
double cosOmega = q1.x() * q2.x() + q1.y() * q2.y() + q1.z() * q2.z() + q1.w() * q2.w();
|
|
|
|
//-----------------------------------
|
|
// adjust signs if necessary:
|
|
|
|
float sign2;
|
|
if ( cosOmega < 0.0 )
|
|
{
|
|
cosOmega = -cosOmega;
|
|
sign2 = -1.0f;
|
|
}
|
|
else
|
|
sign2 = 1.0f;
|
|
|
|
//-----------------------------------
|
|
// calculate interpolating coeffs:
|
|
|
|
double scale1, scale2;
|
|
if ( (1.0 - cosOmega) > 0.00001 )
|
|
{
|
|
// standard case
|
|
double omega = acos(cosOmega);
|
|
double sinOmega = sin(omega);
|
|
scale1 = sin((1.0 - alpha) * omega) / sinOmega;
|
|
scale2 = sign2 * sin(alpha * omega) / sinOmega;
|
|
}
|
|
else
|
|
{
|
|
// if quats are very close, just do linear interpolation
|
|
scale1 = 1.0 - alpha;
|
|
scale2 = sign2 * alpha;
|
|
}
|
|
|
|
|
|
//-----------------------------------
|
|
// actually do the interpolation:
|
|
|
|
out->x( float(scale1 * q1.x() + scale2 * q2.x()));
|
|
out->y( float(scale1 * q1.y() + scale2 * q2.y()));
|
|
out->z( float(scale1 * q1.z() + scale2 * q2.z()));
|
|
out->w( float(scale1 * q1.w() + scale2 * q2.w()));
|
|
}
|
|
|
|
void lerpRotation(Quaternion *out, Quaternion &a, Quaternion &b,
|
|
int outFrame, int aFrame, int bFrame)
|
|
{
|
|
float alpha = (float)(outFrame - aFrame) / (float)(bFrame - aFrame);
|
|
|
|
lerpRotation(out,a,b,alpha);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
int extractMeshFlags(char * name)
|
|
{
|
|
// Extract comma seperated flags embeded in the name. Anything after
|
|
// the ":" in the name is assumed to be flag arguments and stripped
|
|
// off the name.
|
|
int flags = 0;
|
|
char *ptr = strstr(name,":");
|
|
if (ptr)
|
|
{
|
|
*ptr = 0;
|
|
for (char *tok = strtok(ptr+1,","); tok != 0; tok = strtok(0,","))
|
|
{
|
|
// Strip lead/trailing white space
|
|
for (; isspace(*tok); tok++) ;
|
|
for (char* itr = tok + strlen(tok)-1; itr > tok && isspace(*itr); itr--)
|
|
*itr = 0;
|
|
|
|
//
|
|
if (!stricmp(tok,"billboard"))
|
|
flags |= Mesh::Billboard;
|
|
else
|
|
if (!stricmp(tok,"billboardz"))
|
|
flags |= Mesh::Billboard | Mesh::BillboardZ;
|
|
else
|
|
if (!stricmp(tok,"enormals"))
|
|
flags |= Mesh::EncodedNormals;
|
|
}
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
void extractMaterialFlags(char * name, int& flags)
|
|
{
|
|
// Extract comma seperated flags embeded in the name. Anything after
|
|
// the ":" in the name is assumed to be flag arguments and stripped
|
|
// off the name.
|
|
char *ptr = strstr(name,":");
|
|
if (ptr)
|
|
{
|
|
*ptr = 0;
|
|
for (char *tok = strtok(ptr+1,","); tok != 0; tok = strtok(0,","))
|
|
{
|
|
// Strip lead/trailing white space
|
|
for (; isspace(*tok); tok++) ;
|
|
for (char* itr = tok + strlen(tok)-1; itr > tok && isspace(*itr); itr--)
|
|
*itr = 0;
|
|
|
|
if (!stricmp(tok,"add"))
|
|
flags |= Material::Additive;
|
|
else
|
|
if (!stricmp(tok,"sub"))
|
|
flags |= Material::Subtractive;
|
|
else
|
|
if (!stricmp(tok,"illum"))
|
|
flags |= Material::SelfIlluminating;
|
|
else
|
|
if (!stricmp(tok,"nomip"))
|
|
flags |= Material::NoMipMap;
|
|
else
|
|
if (!stricmp(tok,"mipzero"))
|
|
flags |= Material::MipMapZeroBorder;
|
|
}
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool isBoneAnimated(msBone *bone, int start, int end)
|
|
{
|
|
// Returns true if the bone contains any key frames
|
|
// within the given time range.
|
|
int numPKeys = msBone_GetPositionKeyCount(bone);
|
|
for (int j = 0 ; j < numPKeys ; j++) {
|
|
msPositionKey * msKey = msBone_GetPositionKeyAt (bone, j);
|
|
// MS is one based, start/end 0 based..
|
|
if (msKey->fTime > start && msKey->fTime <= end)
|
|
return true;
|
|
}
|
|
int numRKeys = msBone_GetRotationKeyCount(bone);
|
|
for (int i = 0 ; i < numRKeys ; i++)
|
|
{
|
|
msRotationKey * msKey = msBone_GetRotationKeyAt (bone, i);
|
|
// MS is one based, start/end 0 based..
|
|
if (msKey->fTime > start && msKey->fTime <= end)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Imports a Milkshape Model
|
|
// -----------------------------------------------------------------------
|
|
|
|
MilkshapeShape::ImportConfig::ImportConfig ()
|
|
{
|
|
withMaterials = true ;
|
|
collisionType = C_None ;
|
|
collisionComplexity = 0.2f ;
|
|
collisionVisible = false ;
|
|
animation = true ;
|
|
reset();
|
|
}
|
|
|
|
void MilkshapeShape::ImportConfig::reset()
|
|
{
|
|
// Reset values that must be specified using opt: or seq:
|
|
scaleFactor = 0.1f ;
|
|
minimumSize = 0 ;
|
|
animationFPS = 15 ;
|
|
animationCyclic = false;
|
|
sequence.resize(0);
|
|
}
|
|
|
|
|
|
MilkshapeShape::MilkshapeShape (msModel * model, MilkshapeShape::ImportConfig config)
|
|
{
|
|
int i, j ;
|
|
char buffer[512] ;
|
|
|
|
int numBones = msModel_GetBoneCount(model) ;
|
|
int numMeshes = msModel_GetMeshCount(model) ;
|
|
int numMaterials = msModel_GetMaterialCount(model) ;
|
|
|
|
// --------------------------------------------------------
|
|
// Materials (optional)
|
|
// --------------------------------------------------------
|
|
|
|
if (config.withMaterials)
|
|
{
|
|
for (i = 0 ; i < numMaterials ; i++)
|
|
{
|
|
msMaterial * msMat = msModel_GetMaterialAt(model, i) ;
|
|
char textureName[MS_MAX_PATH+1] ;
|
|
|
|
// Check for "reserved" names first. Reserved names
|
|
// are special materials used to encode exporter options...
|
|
// a total hack, but what can you do?
|
|
msMaterial_GetName (msMat, textureName, MS_MAX_PATH);
|
|
if (!strncmp(textureName,"seq:",4) || !strncmp(textureName,"opt:",4))
|
|
continue;
|
|
|
|
// If no texture, use the material name
|
|
msMaterial_GetDiffuseTexture (msMat, textureName, MS_MAX_PATH) ;
|
|
if (!textureName[0])
|
|
{
|
|
msMaterial_GetName (msMat, textureName, MS_MAX_PATH) ;
|
|
|
|
// If no name, create one
|
|
if (!textureName[0])
|
|
strcpy (textureName, "Unnamed") ;
|
|
}
|
|
|
|
// Strip texture file extension
|
|
char * ptr = textureName + strlen(textureName) ;
|
|
while (ptr > textureName && *ptr != '.' && *ptr != '\\') ptr-- ;
|
|
if (*ptr == '.') *ptr = '\0' ;
|
|
|
|
// Strip texture file path
|
|
ptr = textureName + strlen(textureName) ;
|
|
while (ptr > textureName && *ptr != '\\') ptr-- ;
|
|
if (*ptr == '\\') memmove (textureName, ptr+1, strlen(ptr)+1) ;
|
|
|
|
// Create the material
|
|
Material mat ;
|
|
mat.name = textureName ;
|
|
mat.flags = Material::SWrap | Material::TWrap ;
|
|
mat.reflectance = materials.size() ;
|
|
mat.bump = -1 ;
|
|
mat.detail = -1 ;
|
|
mat.detailScale = 1.0f ;
|
|
|
|
// take the strength of the enviro mapping from the shininess
|
|
float shininess = msMaterial_GetShininess(msMat) / 128.0f;
|
|
if (shininess > 0.0f)
|
|
{
|
|
mat.reflection = shininess;
|
|
}
|
|
else
|
|
{
|
|
mat.flags |= Material::NeverEnvMap;
|
|
mat.reflection = 0.0f;
|
|
}
|
|
|
|
// look for tags in material name.. the tags are short because the max
|
|
// length of a material name is only 32 chars
|
|
char materialName[MS_MAX_PATH+1];
|
|
msMaterial_GetName (msMat, materialName, MS_MAX_PATH);
|
|
extractMaterialFlags(materialName, mat.flags);
|
|
|
|
|
|
// now check for transparency < 1.0
|
|
if (msMaterial_GetTransparency (msMat) < 1.0f)
|
|
mat.flags |= Material::Translucent;
|
|
|
|
|
|
|
|
materials.push_back(mat) ;
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Nodes & Bones
|
|
// --------------------------------------------------------
|
|
|
|
Node n ;
|
|
|
|
// Loop through first and import all the bones. I assume here
|
|
// that the parent joints always exist before their children.
|
|
// The Torque engine certainly expects nodes to be organized
|
|
// that way. Other parts of the code assume that Milkshape
|
|
// Joint index = Torque node index
|
|
assert(!nodes.size());
|
|
for (int bone_num = 0 ; bone_num < numBones ; bone_num++)
|
|
{
|
|
char bone_name[256];
|
|
char parent_name[256];
|
|
msBone * bone = msModel_GetBoneAt (model, bone_num) ;
|
|
msBone_GetName (bone, bone_name, 256) ;
|
|
msBone_GetParentName (bone, parent_name, 256);
|
|
|
|
// Create a node for this bone
|
|
n.parent = msModel_FindBoneByName (model, parent_name);
|
|
n.name = addName (bone_name) ;
|
|
nodes.push_back (n) ;
|
|
|
|
// Create the default position and rotation for the node
|
|
msVec3 pos ;
|
|
msVec3 rot ;
|
|
msBone_GetPosition (bone, pos) ;
|
|
msBone_GetRotation (bone, rot) ;
|
|
nodeDefTranslations.push_back(MilkshapePoint(pos) * config.scaleFactor) ;
|
|
nodeDefRotations.push_back(MilkshapeQuaternion(rot)) ;
|
|
}
|
|
|
|
// Add a default "root" node for shapes. All meshes need to
|
|
// be assigned to a node and the Root will be used to catch
|
|
// any mesh not assigned to a bone.
|
|
n.name = addName ("Root") ;
|
|
n.parent = -1;
|
|
nodes.push_back(n) ;
|
|
|
|
// Node indexes map one to one with the default rotation and
|
|
// tranlation arrays: push identity transform for the Root.
|
|
nodeDefRotations.push_back(Quaternion(0,0,0,1)) ;
|
|
nodeDefTranslations.push_back(Point(0,0,0)) ;
|
|
|
|
|
|
// --------------------------------------------------------
|
|
// Mesh objects.
|
|
// --------------------------------------------------------
|
|
|
|
Object o ;
|
|
|
|
for (i = 0 ; i < numMeshes ; i++)
|
|
{
|
|
msMesh * mesh = msModel_GetMeshAt(model, i) ;
|
|
msMesh_GetName (mesh, buffer, 510) ;
|
|
int flags = extractMeshFlags(buffer);
|
|
|
|
// Changed 01/16/2004 by Sven Knie
|
|
// Ignore meshes reserved for collision
|
|
if (!strnicmp(buffer,"Collision",9))
|
|
continue;
|
|
|
|
// added 01/16/2004 by Sven Knie
|
|
// Ignore meshes reserved for LoS collision
|
|
if (!strnicmp(buffer,"LoSCollision", 11))
|
|
continue;
|
|
|
|
// Get object struct ready. Objects are entities that
|
|
// represent renderable items. Objects can have more
|
|
// than one mesh to represent different detail levels.
|
|
o.name = addName(buffer) ;
|
|
o.numMeshes = 1 ;
|
|
o.firstMesh = meshes.size() ;
|
|
o.node = nodes.size() - 1;
|
|
|
|
// Process the raw data.
|
|
meshes.push_back(MilkshapeMesh(mesh, o.node, config.scaleFactor, config.withMaterials)) ;
|
|
Mesh& m = meshes.back();
|
|
m.setFlag(flags);
|
|
|
|
// Rigid meshes can be attached to a single node, in which
|
|
// case we need to transform the vertices into the node's
|
|
// local space.
|
|
if (m.getType() == Mesh::T_Standard)
|
|
{
|
|
o.node = m.getNodeIndex(0);
|
|
if (o.node != -1) {
|
|
// Transform the mesh into node space. The mesh vertices
|
|
// must all be relative to the bone their attached to.
|
|
Quaternion world_rot;
|
|
Point world_trans;
|
|
getNodeWorldPosRot(o.node,world_trans, world_rot);
|
|
m.translate(-world_trans);
|
|
m.rotate(world_rot.inverse());
|
|
}
|
|
}
|
|
|
|
// Skin meshes need transform information to be able to
|
|
// transform vertices into bone space (should fix the Torque
|
|
// engine to calculate these at load time).
|
|
if (m.getType() == Mesh::T_Skin)
|
|
{
|
|
for (int n = 0; n < m.getNodeIndexCount(); n++)
|
|
{
|
|
Quaternion world_rot;
|
|
Point world_trans;
|
|
getNodeWorldPosRot(m.getNodeIndex(n),world_trans, world_rot);
|
|
m.setNodeTransform(n,world_trans,world_rot);
|
|
}
|
|
}
|
|
|
|
objects.push_back(o) ;
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Detail levels
|
|
// Not fully supported, we'll just export all our current
|
|
// stuff as single detail level.
|
|
// --------------------------------------------------------
|
|
|
|
DetailLevel dl ;
|
|
dl.name = addName("Detail-1") ;
|
|
dl.size = 0 ; // Projected size of detail level
|
|
dl.objectDetail = 0 ;
|
|
dl.polyCount = 0 ;
|
|
dl.subshape = 0 ;
|
|
dl.maxError = -1 ;
|
|
dl.avgError = -1 ;
|
|
|
|
std::vector<Mesh>::iterator mesh_ptr ;
|
|
for (mesh_ptr = meshes.begin() ; mesh_ptr != meshes.end() ; mesh_ptr++)
|
|
dl.polyCount += mesh_ptr->getPolyCount() ;
|
|
|
|
detailLevels.push_back(dl) ;
|
|
|
|
// --------------------------------------------------------
|
|
// Collision detail levels and meshes (optional)
|
|
// --------------------------------------------------------
|
|
|
|
// Update our bounds values as we might use them to build
|
|
// a collision mesh.
|
|
calculateBounds() ;
|
|
calculateCenter() ;
|
|
calculateRadius() ;
|
|
calculateTubeRadius() ;
|
|
|
|
// Export every mesh called "Collision" as a collision mesh.
|
|
if (config.collisionType != Shape::C_None)
|
|
{
|
|
// Material for visible meshes
|
|
int meshMaterial = materials.size();
|
|
if (config.collisionVisible)
|
|
{
|
|
Material mat ;
|
|
mat.name = "Collision_Mesh" ;
|
|
mat.flags = Material::NeverEnvMap | Material::SelfIlluminating;
|
|
mat.reflectance = materials.size() ;
|
|
mat.bump = -1 ;
|
|
mat.detail = -1 ;
|
|
mat.reflection = 1.0f ;
|
|
mat.detailScale = 1.0f ;
|
|
materials.push_back(mat) ;
|
|
}
|
|
|
|
// Loop through all the meshes and extract those that will be used for collision
|
|
if (config.collisionType == Shape::C_Mesh)
|
|
{
|
|
int numCollisions = 1;
|
|
for (i = 0 ; i < numMeshes ; i++)
|
|
{
|
|
msMesh * mesh = msModel_GetMeshAt(model, i);
|
|
msMesh_GetName (mesh, buffer, 510) ;
|
|
// Changed 01/16/2004 by Sven Knie
|
|
bool colMesh = !strnicmp(buffer,"Collision", 9);
|
|
bool losMesh = !strnicmp(buffer,"LoSCollision", 11);
|
|
if (!colMesh && !losMesh)
|
|
continue;
|
|
|
|
Mesh* collmesh = new MilkshapeMesh(mesh, nodes.size() - 1, config.scaleFactor, true) ;
|
|
|
|
// Create the collision detail level
|
|
char detailName[256];
|
|
sprintf(detailName,colMesh? "Collision-%d": "LOS-%d", numCollisions++);
|
|
dl.name = addName(detailName) ;
|
|
// Changed 01/16/2004 by Sven Knie
|
|
dl.size = (float)numCollisions * (-1) ; // negativ sized detail levels are never rendered
|
|
dl.objectDetail = detailLevels.size() ;
|
|
dl.subshape = 0 ;
|
|
dl.polyCount = collmesh->getPolyCount() ;
|
|
detailLevels.push_back(dl) ;
|
|
|
|
// Create an object for the collision mesh and attach it to
|
|
// the "Root" node.
|
|
o.name = colMesh? addName("Col"): addName("LoSCol") ;
|
|
o.node = nodes.size() - 1 ;
|
|
o.firstMesh = meshes.size() ;
|
|
o.numMeshes = dl.objectDetail + 1 ;
|
|
objects.push_back(o) ;
|
|
|
|
// Create a renderable copy of the collision meshes
|
|
// for visible detail levels, or a null placeholder
|
|
for (int d = 0 ; d < dl.objectDetail ; d++)
|
|
if (config.collisionVisible)
|
|
{
|
|
meshes.push_back(*collmesh) ;
|
|
meshes[meshes.size()-1].setMaterial(meshMaterial) ;
|
|
}
|
|
else
|
|
meshes.push_back(Mesh(Mesh::T_Null)) ;
|
|
|
|
// Add the mesh to the list
|
|
meshes.push_back(*collmesh);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Let's create the specified collision mesh type
|
|
Mesh* collmesh;
|
|
switch (config.collisionType)
|
|
{
|
|
case C_BBox:
|
|
collmesh = new BoxMesh(getBounds()) ;
|
|
break ;
|
|
case C_Cylinder:
|
|
collmesh = new CylinderMesh(getBounds(), getTubeRadius(), config.collisionComplexity) ;
|
|
break ;
|
|
default:
|
|
assert (0 && "Invalid collision mesh type") ;
|
|
}
|
|
|
|
// Create the collision detail level
|
|
dl.name = addName("Collision-1") ;
|
|
dl.size = -1 ; // -1 sized detail levels are never rendered
|
|
dl.objectDetail = detailLevels.size() ;
|
|
dl.subshape = 0 ;
|
|
dl.polyCount = collmesh->getPolyCount() ;
|
|
detailLevels.push_back(dl) ;
|
|
|
|
// Create an object for the collision mesh and attach it to
|
|
// the "Root" node.
|
|
o.name = addName("Col") ;
|
|
o.node = nodes.size() - 1 ;
|
|
o.firstMesh = meshes.size() ;
|
|
o.numMeshes = dl.objectDetail + 1 ;
|
|
objects.push_back(o) ;
|
|
|
|
// Create a renderable copy of the collision meshes
|
|
// for visible detail levels, or a null placeholder
|
|
for (int d = 0 ; d < dl.objectDetail ; d++)
|
|
if (config.collisionVisible)
|
|
{
|
|
meshes.push_back(*collmesh) ;
|
|
meshes[meshes.size()-1].setMaterial(meshMaterial) ;
|
|
}
|
|
else
|
|
meshes.push_back(Mesh(Mesh::T_Null)) ;
|
|
|
|
// Add the mesh to the list
|
|
meshes.push_back(*collmesh);
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Subshape and final stuff
|
|
// --------------------------------------------------------
|
|
|
|
// Create a subshape with everything
|
|
|
|
Subshape s ;
|
|
s.firstObject = 0 ;
|
|
s.firstNode = 0 ;
|
|
s.firstDecal = 0 ;
|
|
s.numNodes = nodes.size() ;
|
|
s.numObjects = objects.size() ;
|
|
s.numDecals = decals.size() ;
|
|
subshapes.push_back(s) ;
|
|
|
|
// Create an object state for each object (not sure about this)
|
|
|
|
ObjectState os ;
|
|
os.vis = 1.0f ;
|
|
os.frame = 0 ;
|
|
os.matFrame = 0 ;
|
|
for (i = 0 ; i < objects.size() ; i++)
|
|
objectStates.push_back(os) ;
|
|
|
|
// Recalculate bounds (they may have changed)
|
|
|
|
calculateBounds() ;
|
|
calculateCenter() ;
|
|
calculateRadius() ;
|
|
calculateTubeRadius() ;
|
|
|
|
setSmallestSize(config.minimumSize) ;
|
|
|
|
// --------------------------------------------------------
|
|
// Animation (optional)
|
|
|
|
// For each sequence, Torque wants an array of frame * nodes
|
|
// information for all nodes affected by that sequence. Node
|
|
// animation information can be translation, rotation, scale,
|
|
// visibility, material, etc.
|
|
//
|
|
// The sequence contains a "matters" array for each type of
|
|
// animation info. Each type has it's own array of frame * nodes
|
|
// which contains only the nodes affected by that type of
|
|
// information for the sequence. Since each array is NxN,
|
|
// if a node is animated on a single frame of the sequence, it
|
|
// will get an entry for every frame.
|
|
|
|
// --------------------------------------------------------
|
|
|
|
int frameCount = msModel_GetTotalFrames (model);
|
|
if (frameCount && numBones && config.animation)
|
|
{
|
|
// Process all the sequences.
|
|
for (int sc = 0; sc < config.sequence.size(); sc++)
|
|
{
|
|
MilkshapeShape::ImportConfig::Sequence& si = config.sequence[sc];
|
|
|
|
// Build the exported sequence structure
|
|
Sequence s;
|
|
s.flags = Sequence::UniformScale;
|
|
if (si.cyclic)
|
|
s.flags |= Sequence::Cyclic;
|
|
s.nameIndex = addName(si.name) ;
|
|
s.numKeyFrames = si.end - si.start;
|
|
s.duration = float(s.numKeyFrames) / si.fps;
|
|
s.baseTranslation = nodeTranslations.size() ;
|
|
s.baseRotation = nodeRotations.size();
|
|
|
|
// Count how many nodes are affected by the sequence and
|
|
// set the sequence.matter arrays to indicate which ones.
|
|
s.matters.translation.assign (nodes.size(), false);
|
|
s.matters.rotation.assign (nodes.size(), false);
|
|
int nodeCount = 0;
|
|
for (i = 0 ; i < numBones ; i++)
|
|
{
|
|
msBone * bone = msModel_GetBoneAt(model, i);
|
|
if (isBoneAnimated(bone,si.start,si.end))
|
|
{
|
|
// Milkshape seems to always produce rotation & position
|
|
// keys in pairs, so we'll just deal with them together.
|
|
s.matters.translation[i] = true;
|
|
s.matters.rotation[i] = true;
|
|
nodeCount++;
|
|
}
|
|
}
|
|
|
|
// Size arrays to hold keyframe * nodeCount
|
|
nodeTranslations.resize (nodeTranslations.size() + nodeCount * s.numKeyFrames);
|
|
nodeRotations.resize (nodeTranslations.size());
|
|
|
|
// Set the keyframe data for each affected bone. Unaffected
|
|
// bones are skipped so the final NxN array of transforms
|
|
// and rotations is "compressed", sort of. We could compress
|
|
// both the rotation and the position arrays individually,
|
|
// but MS seems to always generate the values in pairs, so
|
|
// we'll do them together. Though the msKey loops are seperate,
|
|
// just in case.
|
|
int index = 0;
|
|
for (i = 0 ; i < numBones ; i++)
|
|
{
|
|
msBone * bone = msModel_GetBoneAt(model, i) ;
|
|
if (isBoneAnimated(bone,si.start,si.end))
|
|
{
|
|
int numPKeys = msBone_GetPositionKeyCount(bone);
|
|
int numRKeys = msBone_GetRotationKeyCount(bone);
|
|
|
|
// Insert translation keys into the table.
|
|
Point *translations = &nodeTranslations[s.baseTranslation + index * s.numKeyFrames];
|
|
int lastFrame = 0;
|
|
|
|
for (j = 0 ; j < numPKeys ; j++)
|
|
{
|
|
msPositionKey * msKey = msBone_GetPositionKeyAt (bone, j);
|
|
int frame = int(msKey->fTime) - 1;
|
|
|
|
// Only want keys in the sequence range. If it's before
|
|
// our range, we'll put it into the 0 frame in case we don't
|
|
// get a key for that frame later.
|
|
if (frame >= si.end)
|
|
break;
|
|
if (frame < si.start)
|
|
frame = 0;
|
|
else
|
|
frame -= si.start;
|
|
|
|
// Store the total translation for the node and fill in
|
|
// the initial frame if this is the first key frame.
|
|
translations[frame] = nodeDefTranslations[i] +
|
|
nodeDefRotations[i].apply(MilkshapePoint(msKey->Position) * config.scaleFactor);
|
|
if (!j && frame > 0)
|
|
translations[0] = translations[frame];
|
|
|
|
// Interpolate the missing frames.
|
|
for (int f = lastFrame + 1; f < frame; f++)
|
|
lerpTranslation(&translations[f],translations[lastFrame],
|
|
translations[frame], f, lastFrame, frame);
|
|
|
|
lastFrame = frame;
|
|
}
|
|
|
|
// Duplicate the last frame to the end.
|
|
for (int t = lastFrame + 1; t < s.numKeyFrames; t++)
|
|
translations[t] = translations[lastFrame];
|
|
|
|
// Insert rotation keys into the table.
|
|
Quaternion *rotations = &nodeRotations[s.baseTranslation + index * s.numKeyFrames];
|
|
lastFrame = 0;
|
|
|
|
for (j = 0 ; j < numRKeys ; j++)
|
|
{
|
|
msRotationKey * msKey = msBone_GetRotationKeyAt (bone, j) ;
|
|
int frame = int(msKey->fTime) - 1;
|
|
|
|
// Only want keys in the sequence range. If it's before
|
|
// our range, we'll put it into the 0 frame in case we don't
|
|
// get a key for that frame later.
|
|
if (frame >= si.end)
|
|
break;
|
|
if (frame < si.start)
|
|
frame = 0;
|
|
else
|
|
frame -= si.start;
|
|
|
|
// Store the total rotation for the node and fill in
|
|
// the initial frame if this is the first key frame.
|
|
rotations[frame] = MilkshapeQuaternion(msKey->Rotation) * nodeDefRotations[i];
|
|
if (!j && frame > 0)
|
|
rotations[0] = rotations[frame];
|
|
|
|
// Interpolate the missing frames.
|
|
for (int f = lastFrame + 1; f < frame; f++)
|
|
lerpRotation(&rotations[f],rotations[lastFrame],
|
|
rotations[frame], f, lastFrame, frame);
|
|
|
|
lastFrame = frame;
|
|
}
|
|
|
|
// Duplicate the last frame to the end.
|
|
for (int r = lastFrame + 1; r < s.numKeyFrames; r++)
|
|
rotations[r] = rotations[lastFrame];
|
|
|
|
// Increment the position & rotation array index
|
|
index++;
|
|
}
|
|
}
|
|
|
|
sequences.push_back(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // DTS namespace
|