1046 lines
32 KiB
C++
1046 lines
32 KiB
C++
//**************************************************************************
|
|
//* Animout.cpp - Virtools File Exporter
|
|
//*
|
|
//* Romain Sididris - Copyright (c) Virtools 2000
|
|
//*
|
|
//* (Based on Ascii File Exporter source code
|
|
//* By Christer Janson
|
|
//* Kinetix Development
|
|
//* Copyright (c) Kinetix 1997, All Rights Reserved. )
|
|
//*
|
|
//* This module handles controller key output and controller sampling.
|
|
//*
|
|
//***************************************************************************
|
|
#include "Precomp.h"
|
|
#include "Max2Nmo.h"
|
|
|
|
#define ALMOST_ZERO 1.0e-3f
|
|
BOOL EqualPoint3(Point3 p1, Point3 p2);
|
|
BOOL EqualFloat(float v1, float v2);
|
|
|
|
//uncomment this define when developpement is done about that
|
|
//#define SUPPORT_NEW_CONTROLLERS
|
|
|
|
//--- An independent X, Y, Z position controller
|
|
///// See file: indepos.cpp
|
|
#define IPOS_CONTROL_CLASS_ID Class_ID(0x118f7e02,0xffee238a)
|
|
#define IPOINT3_CONTROL_CLASS_ID Class_ID(0x118f7e02,0xfeee238b)
|
|
#define IPOINT4_CONTROL_CLASS_ID Class_ID(0x118f7e02,0xfeee238c)
|
|
|
|
/****************************************************************************
|
|
Export animation keys for a node, if the node use known Max controllers
|
|
(Linear,Bezier,TCB) PRS scale controller, these controller are converted to Virtools
|
|
format, otherwise if the object is animated we sample the animation to create
|
|
key frame data.
|
|
****************************************************************************/
|
|
void Max2Nmo::ExportAnimKeys(INode* node,CK3dEntity *entity)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
BOOL bDoKeys = FALSE;
|
|
BOOL bForceKey = FALSE;
|
|
Control* tmC = node->GetTMController();
|
|
Control* pC = tmC->GetPositionController();
|
|
Control* rC = tmC->GetRotationController();
|
|
Control* sC = tmC->GetScaleController();
|
|
|
|
// Force at least one key to be created on biped controlled objects... :(
|
|
Class_ID cid = tmC->ClassID();
|
|
|
|
// For a biped controlled object some objects
|
|
// may not be animed but they may need at least one key to have
|
|
// a correct position when warping between different animation
|
|
// If the controller has at least one key, we force the creation of the animation
|
|
if ((cid == BIPBODY_CONTROL_CLASS_ID) || (cid == BIPSLAVE_CONTROL_CLASS_ID) ||
|
|
(cid == FOOTPRINT_CLASS_ID)) {
|
|
bForceKey = TRUE;
|
|
}
|
|
|
|
|
|
if ((cid == Class_ID(PRS_CONTROL_CLASS_ID,0)) && IsKnownController(pC) && IsKnownController(rC) && IsKnownController(sC)) {
|
|
bDoKeys = TRUE;
|
|
}
|
|
|
|
CKObjectAnimation* anim = NULL;
|
|
|
|
if (bDoKeys) {
|
|
anim = VirtoolsExporter->AddObjectAnimation(entity,entity->GetName(),node);
|
|
|
|
#ifdef SUPPORT_NEW_CONTROLLERS
|
|
if (pC->ClassID()==IPOS_CONTROL_CLASS_ID)
|
|
DumpIndePos(node,pC,anim); //try to dump default controler
|
|
else
|
|
#endif
|
|
DumpPosKeys(pC,anim);
|
|
|
|
#ifdef SUPPORT_NEW_CONTROLLERS
|
|
if (rC->ClassID()==IPOINT4_CONTROL_CLASS_ID || rC->ClassID()==IPOINT3_CONTROL_CLASS_ID)
|
|
DumpIndeRot(node,rC,anim); //try to dump default controler
|
|
else
|
|
#endif
|
|
DumpRotKeys(rC,anim);
|
|
DumpScaleKeys(sC,anim);
|
|
} else {
|
|
anim = VirtoolsExporter->AddObjectAnimation(entity,entity->GetName(),node);
|
|
DumpSampledAnim(node,anim);
|
|
}
|
|
|
|
if (bExportBlendShapeAnims)
|
|
{
|
|
// Blend shape anim
|
|
Modifier* morphModifier = FindModifier(node, MR3_CLASS_ID);
|
|
if (morphModifier)
|
|
{
|
|
Report(REPORT_MLEVEL, "\r\nSearching blend shape animations for node %s", node->GetName());
|
|
for (int i = 0; i < 100; ++i)
|
|
{
|
|
IParamBlock* pb = (IParamBlock*) morphModifier->SubAnim(i + 1);
|
|
if (!pb) continue;
|
|
Control* ctrl = pb->GetController(0);
|
|
// max encodes weights in the [0-100] range, we want weights in the [0-1] range -> hence the 0.01f factor
|
|
if (ctrl)
|
|
{
|
|
Report(REPORT_MLEVEL, "\r\nExporting animation of blend shape #%u (%s) for mesh %s", i, morphModifier->SubAnimName(i + 1), node->GetName());
|
|
if (IsKnownController(ctrl))
|
|
{
|
|
DumpFloatKeys(ctrl, anim, 0.01f, morphModifier->SubAnimName(i + 1));
|
|
}
|
|
else
|
|
{
|
|
DumpSampledFloatAnim(ctrl, anim, 0.01f, morphModifier->SubAnimName(i + 1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//--- Check validity of every animation controller and report
|
|
if (anim) {
|
|
|
|
anim->SetLength(FrameTime(m_EndFrame));
|
|
|
|
unsigned int KeyLimitCount = bForceKey ? 1 : 2;
|
|
//if (GetIgnore1KeyAnimation())
|
|
// KeyLimitCount = 0xFFFFFFFF;
|
|
|
|
// Check rotation controller
|
|
CKAnimController* Rctrl = anim->GetRotationController();
|
|
if (Rctrl) {
|
|
if (Rctrl->GetKeyCount()<KeyLimitCount && GetIgnore1KeyAnimation()) {
|
|
anim->DeleteController(CKANIMATION_CONTROLLER_ROT);
|
|
Rctrl = NULL;
|
|
Report(REPORT_LLEVEL,"Rotation Controller deleted for object %s\r\n",
|
|
entity->GetName());
|
|
}
|
|
|
|
if (Rctrl && Rctrl->GetKeyCount()>=KeyLimitCount && !anim->GetScaleController()) {
|
|
// We add a fake scale controller with only one key
|
|
// to preserve errors from coming from lose of precision
|
|
// when applying successive rotation without reseting the scale
|
|
VxVector Pos,Scale;
|
|
VxQuaternion Quat;
|
|
Vx3DDecomposeMatrix(entity->GetLocalMatrix(),Quat,Pos,Scale);
|
|
CKAnimController* Sctrl = anim->CreateController(CKANIMATION_LINSCL_CONTROL);
|
|
Sctrl->AddKey(&CKScaleKey(0,Scale));
|
|
}
|
|
|
|
}
|
|
// Check position controller
|
|
CKAnimController* Pctrl = anim->GetPositionController();
|
|
if (Pctrl && Pctrl->GetKeyCount()<KeyLimitCount && GetIgnore1KeyAnimation()) {
|
|
anim->DeleteController(CKANIMATION_CONTROLLER_POS);
|
|
Pctrl = NULL;
|
|
Report(REPORT_LLEVEL,"Position Controller deleted for object %s\r\n",
|
|
entity->GetName());
|
|
}
|
|
|
|
// Check scale controller
|
|
CKAnimController* Sctrl = anim->GetScaleController();
|
|
if (Sctrl && Sctrl->GetKeyCount()<KeyLimitCount && GetIgnore1KeyAnimation()) {
|
|
anim->DeleteController(CKANIMATION_CONTROLLER_SCL);
|
|
Sctrl = NULL;
|
|
Report(REPORT_LLEVEL,"Scale Controller deleted for object %s\r\n",
|
|
entity->GetName());
|
|
}
|
|
// Check Off-Axis scale controller
|
|
CKAnimController* SActrl= anim->GetScaleAxisController();
|
|
if (SActrl && SActrl->GetKeyCount()<KeyLimitCount && GetIgnore1KeyAnimation()) {
|
|
anim->DeleteController(CKANIMATION_CONTROLLER_SCLAXIS);
|
|
SActrl = NULL;
|
|
Report(REPORT_LLEVEL,"Scale Axis Controller deleted for object %s\r\n",
|
|
entity->GetName());
|
|
}
|
|
|
|
// Report
|
|
if (Pctrl || Rctrl || Sctrl || SActrl) {
|
|
if (bDoKeys)
|
|
Report(REPORT_MLEVEL,"\r\nSaving standard animation...\r\n");
|
|
else
|
|
Report(REPORT_MLEVEL,"\r\nConverting unknown animation (Sampling)...\r\n");
|
|
|
|
Report(REPORT_LLEVEL,"Position: %d keys, Rotation: %d keys, Scale: %d keys\r\n",
|
|
Pctrl ? Pctrl->GetKeyCount() : 0,
|
|
Rctrl ? Rctrl->GetKeyCount() : 0,
|
|
Sctrl ? Sctrl->GetKeyCount() : 0);
|
|
if (SActrl && SActrl->GetKeyCount())
|
|
Report(REPORT_LLEVEL,"Warning : OffAxis Scale : %d keys\r\n",SActrl->GetKeyCount());
|
|
} else {
|
|
// every controller was deleted : remove the animation
|
|
// will be done by exporter...
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************************************************
|
|
To really see if a node is animated we can step through the animation range
|
|
and decompose the TM matrix for every frame and examine the components.
|
|
This way we can identify position, rotation and scale animation separately.
|
|
|
|
RS : Added support for off axis scale...
|
|
****************************************************************************************/
|
|
BOOL Max2Nmo::CheckForAnimation(INode* node, BOOL& bPos, BOOL& bRot, BOOL& bScale, BOOL& bScaleAxis)
|
|
{
|
|
TimeValue start = ip->GetAnimRange().Start();
|
|
TimeValue end = ip->GetAnimRange().End();
|
|
TimeValue t;
|
|
int delta = GetTicksPerFrame();
|
|
Matrix3 tm;
|
|
AffineParts ap;
|
|
Point3 firstPos;
|
|
float rotAngle, firstRotAngle;
|
|
Point3 rotAxis, firstRotAxis;
|
|
float scaleRotAngle, firstScaleRotAngle;
|
|
Point3 scaleRotAxis, firstScaleRotAxis;
|
|
Point3 firstScaleFactor;
|
|
|
|
bPos = bRot = bScale = bScaleAxis = FALSE;
|
|
|
|
for (t=start; t<=end; t+=delta) {
|
|
tm = node->GetNodeTM(t) * Inverse(node->GetParentTM(t));
|
|
|
|
decomp_affine(tm, &ap);
|
|
AngAxisFromQ(ap.q, &rotAngle, rotAxis); // Rotation
|
|
AngAxisFromQ(ap.u, &scaleRotAngle,scaleRotAxis); // Off Axis rotation
|
|
if (!ap.u.IsIdentity()) bScaleAxis = TRUE;
|
|
|
|
if (t != start) {
|
|
// Position changes
|
|
if (!bPos) {
|
|
if (!EqualPoint3(ap.t, firstPos)) {
|
|
bPos = TRUE;
|
|
}
|
|
}
|
|
// Rotation changes
|
|
if (!bRot) {
|
|
if (fabs(rotAngle - firstRotAngle) > ALMOST_ZERO) {
|
|
bRot = TRUE;
|
|
} else if (!EqualPoint3(rotAxis, firstRotAxis)) {
|
|
bRot = TRUE;
|
|
}
|
|
}
|
|
// Scale changes
|
|
if (!bScale) {
|
|
if (!EqualPoint3(ap.k, firstScaleFactor)) {
|
|
bScale = TRUE;
|
|
}
|
|
}
|
|
// Off-Axis Scale changes
|
|
if (!bScaleAxis) {
|
|
if (fabs(scaleRotAngle - firstScaleRotAngle) > ALMOST_ZERO) {
|
|
bScaleAxis = TRUE;
|
|
} else if (!EqualPoint3(scaleRotAxis, firstScaleRotAxis)) {
|
|
bScaleAxis = TRUE;
|
|
}
|
|
}
|
|
} else {
|
|
firstPos = ap.t;
|
|
firstRotAngle = rotAngle;
|
|
firstRotAxis = rotAxis;
|
|
firstScaleRotAngle = scaleRotAngle;
|
|
firstScaleRotAxis = scaleRotAxis;
|
|
firstScaleFactor = ap.k;
|
|
}
|
|
|
|
// No need to continue looping if all components are animated
|
|
if (bPos && bRot && bScale && bScaleAxis) break;
|
|
}
|
|
if (!(bPos || bRot || bScale) && bScaleAxis) bScaleAxis = FALSE;
|
|
return bPos || bRot || bScale || bScaleAxis;
|
|
}
|
|
|
|
|
|
/********************************************************************************
|
|
Samples the animation range of a Node and create keys every N frame (given by GetKeyframeStep in
|
|
the dialog box ). This is done by decomposing the TM matrix.
|
|
If two successive keys are equal, no new key is created.
|
|
|
|
********************************************************************************/
|
|
void Max2Nmo::DumpSampledAnim(INode* node,CKObjectAnimation* anim)
|
|
{
|
|
Control* cont = node->GetTMController();
|
|
|
|
Class_ID cont_cid = cont->ClassID();
|
|
|
|
TimeValue start = ip->GetAnimRange().Start();
|
|
TimeValue end = ip->GetAnimRange().End();
|
|
|
|
//--- There's still some "unkown" controller for which we can still decide
|
|
///// not to create sampled keys as we know they should have keys to produce anim
|
|
// 05/12/05 - Reverted this change (Ericg)
|
|
// In some cases, the position/rotation controller may return 0 keys, while there
|
|
// actually are on the sub-controllers for each axis.
|
|
//Reported from source safe by NicolasA because of Laf changes
|
|
|
|
// if( (cont_cid == Class_ID(PRS_CONTROL_CLASS_ID, 0)) ){
|
|
|
|
// int keyCount = 0;
|
|
|
|
// Control* cpos = node->GetTMController()->GetPositionController();
|
|
// Class_ID cpos_id = cpos->ClassID();
|
|
// if( cpos_id == IPOS_CONTROL_CLASS_ID ){
|
|
// Tab<TimeValue> times;
|
|
// Interval range( start, end );
|
|
// cpos->GetKeyTimes( times, range, KEYAT_POSITION );
|
|
// keyCount += times.Count();
|
|
// }
|
|
// Control* crot = node->GetTMController()->GetRotationController();
|
|
// Class_ID crot_id = cpos->ClassID();
|
|
// if( crot_id == IPOS_CONTROL_CLASS_ID ){
|
|
// Tab<TimeValue> times;
|
|
// Interval range( start, end );
|
|
// cpos->GetKeyTimes( times, range, KEYAT_ROTATION );
|
|
// keyCount += times.Count();
|
|
// }
|
|
// Control* cscl = node->GetTMController()->GetScaleController();
|
|
// Class_ID cscl_id = cpos->ClassID();
|
|
// if( cscl_id == IPOS_CONTROL_CLASS_ID ){
|
|
// Tab<TimeValue> times;
|
|
// Interval range( start, end );
|
|
// cpos->GetKeyTimes( times, range, KEYAT_SCALE );
|
|
// keyCount += times.Count();
|
|
// }
|
|
|
|
// // there's no key control interface so we can't use the beneath lines (too bad)
|
|
// /*
|
|
// IKeyControl *ikc = GetKeyControlInterface(cont);
|
|
// if (!ikc) return ;
|
|
// int numKeys = ikc->GetNumKeys();
|
|
// */
|
|
|
|
// //--- If there's no "detectable" keys don't continue
|
|
// if( keyCount == 0 ){
|
|
// return;
|
|
// }
|
|
// }
|
|
|
|
|
|
TimeValue t;
|
|
int delta = GetTicksPerFrame() * GetKeyFrameStep();
|
|
Matrix3 tm;
|
|
AffineParts ap;
|
|
|
|
//-- Light and Camera animation need an additional rotation around X axis to be valid
|
|
//-- in Virtools ( Z axis is supposed to be the direction of the lights and cameras )
|
|
Quat RotateX;
|
|
RotateX.Identity();
|
|
CK3dEntity* entity = anim->Get3dEntity();
|
|
if (CKIsChildClassOf(entity,CKCID_CAMERA) || CKIsChildClassOf(entity,CKCID_LIGHT)) {
|
|
RotateX=QFromAngAxis(HALFPI,Point3(1.0f,0.0f,0.0f));
|
|
}
|
|
|
|
Point3 prevPos;
|
|
Quat prevQ;
|
|
Quat prevU;
|
|
Point3 prevScl;
|
|
|
|
prevQ.Identity();
|
|
prevU.Identity();
|
|
|
|
CKAnimController* Pctrl = anim->CreateController(CKANIMATION_LINPOS_CONTROL);
|
|
CKAnimController* Rctrl = anim->CreateController(CKANIMATION_LINROT_CONTROL);
|
|
CKAnimController* Sctrl = anim->CreateController(CKANIMATION_LINSCL_CONTROL);
|
|
CKAnimController* SActrl = anim->CreateController(CKANIMATION_LINSCLAXIS_CONTROL);
|
|
|
|
for (t=start; t<=end; t+=delta) {
|
|
|
|
tm = node->GetNodeTM(t) * Inverse(node->GetParentTM(t));
|
|
decomp_affine(tm, &ap);
|
|
|
|
if (ReduceRedondantKeys()) {
|
|
// Position key
|
|
if (Pctrl) {
|
|
Point3 pos = ap.t;
|
|
if ((t==start) || (!EqualPoint3(pos, prevPos))) {
|
|
Pctrl->AddKey(&CKPositionKey(FrameTime(t),
|
|
VxVector(pos.x,pos.z,pos.y)));
|
|
}
|
|
prevPos = pos;
|
|
}
|
|
|
|
// Rotation Key
|
|
if (Rctrl) {
|
|
Quat q = ap.q / prevQ;
|
|
prevQ = ap.q;
|
|
if ((t==start) || !q.IsIdentity()) {
|
|
Quat Val = RotateX*ap.q;
|
|
Rctrl->AddKey(&CKRotationKey(FrameTime(t),
|
|
VxQuaternion(-Val.x,-Val.z,-Val.y,Val.w)));
|
|
}
|
|
}
|
|
|
|
// Scale Key
|
|
if (Sctrl) {
|
|
Point3 scl = ap.k;
|
|
if ((t==start) || !EqualPoint3(scl, prevScl)) {
|
|
Sctrl->AddKey(&CKScaleKey(FrameTime(t),
|
|
VxVector(scl.x,scl.z,scl.y)));
|
|
}
|
|
prevScl = scl;
|
|
}
|
|
|
|
// Off-Axis Scale Key
|
|
if (SActrl) {
|
|
Quat u = ap.u / prevU;
|
|
prevU = ap.u;
|
|
if (t== start || !u.IsIdentity()) {
|
|
Quat Val = RotateX*ap.u;
|
|
SActrl->AddKey(&CKScaleAxisKey(FrameTime(t),
|
|
VxQuaternion(-Val.x,-Val.z,-Val.y,Val.w)));
|
|
}
|
|
}
|
|
} else {
|
|
// Position key
|
|
if (Pctrl) {
|
|
Point3 pos = ap.t;
|
|
Pctrl->AddKey(&CKPositionKey(FrameTime(t),
|
|
VxVector(pos.x,pos.z,pos.y)));
|
|
prevPos = pos;
|
|
}
|
|
|
|
// Rotation Key
|
|
if (Rctrl) {
|
|
Quat q = ap.q / prevQ;
|
|
prevQ = ap.q;
|
|
Quat Val = RotateX*ap.q;
|
|
Rctrl->AddKey(&CKRotationKey(FrameTime(t),
|
|
VxQuaternion(-Val.x,-Val.z,-Val.y,Val.w)));
|
|
}
|
|
|
|
// Scale Key
|
|
if (Sctrl) {
|
|
Point3 scl = ap.k;
|
|
Sctrl->AddKey(&CKScaleKey(FrameTime(t),
|
|
VxVector(scl.x,scl.z,scl.y)));
|
|
prevScl = scl;
|
|
}
|
|
|
|
// Off-Axis Scale Key
|
|
if (SActrl) {
|
|
Quat u = ap.u / prevU;
|
|
prevU = ap.u;
|
|
Quat Val = RotateX*ap.u;
|
|
SActrl->AddKey(&CKScaleAxisKey(FrameTime(t),
|
|
VxQuaternion(-Val.x,-Val.z,-Val.y,Val.w)));
|
|
}
|
|
}
|
|
|
|
// Ensure to have the last animation key
|
|
if ((t<end) && (t + delta > end)) {
|
|
t = end-delta;
|
|
}
|
|
}
|
|
}
|
|
|
|
/********************************************************************************
|
|
Samples the animation range of a Node blend shape float track and create keys every N frame (given by GetKeyframeStep in
|
|
the dialog box ). If two successive keys are equal, no new key is created.
|
|
|
|
********************************************************************************/
|
|
void Max2Nmo::DumpSampledFloatAnim(Control* cont, CKObjectAnimation* anim, float scale, const char* targetBlendShapeName)
|
|
{
|
|
if (!cont) return;
|
|
TimeValue start = ip->GetAnimRange().Start();
|
|
TimeValue end = ip->GetAnimRange().End();
|
|
CKAnimController* BSctrl = anim->CreateController(CKANIMATION_LINFLOAT_CONTROL, targetBlendShapeName);
|
|
TimeValue t;
|
|
int delta = GetTicksPerFrame() * GetBlendShapesStep();
|
|
float value, prevValue;
|
|
for (t=start; t<=end; t+=delta)
|
|
{
|
|
cont->GetValue(t, &value, FOREVER);
|
|
value *= scale;
|
|
if (!ReduceRedondantKeys() || (t==start) || (!EqualFloat(value, prevValue)))
|
|
{
|
|
BSctrl->AddKey(&CKFloatKey(FrameTime(t), value));
|
|
prevValue = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*****************************************************************
|
|
Convert Bezier keys Tangent flags from Max to Virtools
|
|
*****************************************************************/
|
|
CKBezierKeyFlags MaxBezFlags2Virtools(DWORD f)
|
|
{
|
|
CKBezierKeyFlags Flags;
|
|
DWORD InType = GetInTanType(f);
|
|
DWORD OutType = GetOutTanType(f);
|
|
switch (InType) {
|
|
case BEZKEY_SMOOTH: Flags.SetInTangentMode(BEZIER_KEY_AUTOSMOOTH); break;
|
|
case BEZKEY_LINEAR: Flags.SetInTangentMode(BEZIER_KEY_LINEAR); break;
|
|
case BEZKEY_STEP: Flags.SetInTangentMode(BEZIER_KEY_STEP); break;
|
|
case BEZKEY_FAST: Flags.SetInTangentMode(BEZIER_KEY_FAST); break;
|
|
case BEZKEY_SLOW: Flags.SetInTangentMode(BEZIER_KEY_SLOW); break;
|
|
case BEZKEY_USER: Flags.SetInTangentMode(BEZIER_KEY_TANGENTS); break;
|
|
}
|
|
switch (OutType) {
|
|
case BEZKEY_SMOOTH: Flags.SetOutTangentMode(BEZIER_KEY_AUTOSMOOTH); break;
|
|
case BEZKEY_LINEAR: Flags.SetOutTangentMode(BEZIER_KEY_LINEAR); break;
|
|
case BEZKEY_STEP: Flags.SetOutTangentMode(BEZIER_KEY_STEP); break;
|
|
case BEZKEY_FAST: Flags.SetOutTangentMode(BEZIER_KEY_FAST); break;
|
|
case BEZKEY_SLOW: Flags.SetOutTangentMode(BEZIER_KEY_SLOW); break;
|
|
case BEZKEY_USER: Flags.SetOutTangentMode(BEZIER_KEY_TANGENTS); break;
|
|
}
|
|
return Flags;
|
|
}
|
|
|
|
|
|
/********************************************************************************
|
|
Creates a Position Controller on a CKObjectAnimation using
|
|
Max Controller key frame data. the 3 standard type of controller
|
|
are supported : Linear,Bezier and TCB
|
|
*********************************************************************************/
|
|
void Max2Nmo::DumpPosKeys(Control* cont,CKObjectAnimation* anim)
|
|
{
|
|
if (!cont) return;
|
|
IKeyControl *ikc = GetKeyControlInterface(cont);
|
|
if (!ikc) return ;
|
|
|
|
int i,numKeys = ikc->GetNumKeys();
|
|
if (!numKeys) return ;
|
|
|
|
Class_ID cont_cid = cont->ClassID();
|
|
|
|
// If this is not a standard controller we return
|
|
if (cont_cid.PartB()) return;
|
|
|
|
//--- Create the appropriate Virtools position controller
|
|
CKANIMATION_CONTROLLER VirtoolsCtrlType;
|
|
|
|
switch (cont_cid.PartA()) {
|
|
case TCBINTERP_POSITION_CLASS_ID : VirtoolsCtrlType = CKANIMATION_TCBPOS_CONTROL; break;
|
|
case HYBRIDINTERP_POSITION_CLASS_ID : VirtoolsCtrlType = CKANIMATION_BEZIERPOS_CONTROL; break;
|
|
case LININTERP_POSITION_CLASS_ID : VirtoolsCtrlType = CKANIMATION_LINPOS_CONTROL; break;
|
|
default :
|
|
return;
|
|
}
|
|
CKAnimController* ctrl = anim->CreateController(VirtoolsCtrlType);
|
|
|
|
//--- And convert max keys
|
|
switch (cont_cid.PartA()) {
|
|
|
|
case TCBINTERP_POSITION_CLASS_ID :
|
|
for (i=0; i<numKeys; i++) {
|
|
ITCBPoint3Key key;
|
|
ikc->GetKey(i, &key); // Get Max Key
|
|
|
|
// Create Virtools Key
|
|
CKTCBPositionKey VirtoolsKey(FrameTime(key.time),
|
|
VxVector(key.val.x,key.val.z,key.val.y),
|
|
key.tens,key.cont,key.bias,
|
|
key.easeIn,key.easeOut);
|
|
ctrl->AddKey(&VirtoolsKey);
|
|
}
|
|
break;
|
|
|
|
case HYBRIDINTERP_POSITION_CLASS_ID :
|
|
for (i=0; i<numKeys; i++) {
|
|
IBezPoint3Key key;
|
|
ikc->GetKey(i, &key); // Get Max Key
|
|
|
|
// Convert Max Bezier tangent to Virtools format
|
|
CKBezierKeyFlags Flags=MaxBezFlags2Virtools(key.flags);
|
|
float ScaleFactor = GetFrameRate()*16.0f/3.0f;
|
|
VxVector InTang(key.intan.x,key.intan.z,key.intan.y),
|
|
OutTang(key.outtan.x,key.outtan.z,key.outtan.y);
|
|
InTang*=ScaleFactor;
|
|
OutTang*=ScaleFactor;
|
|
|
|
// Create Virtools Bezier Key
|
|
CKBezierPositionKey VirtoolsKey(FrameTime(key.time),
|
|
VxVector(key.val.x,key.val.z,key.val.y),
|
|
Flags,
|
|
InTang,OutTang);
|
|
ctrl->AddKey(&VirtoolsKey);
|
|
}
|
|
break;
|
|
|
|
case LININTERP_POSITION_CLASS_ID :
|
|
for (i=0; i<numKeys; i++) {
|
|
ILinPoint3Key key;
|
|
|
|
ikc->GetKey(i, &key); // Get Max Key
|
|
|
|
// Create Virtools linear Position Key
|
|
ctrl->AddKey(&CKPositionKey(FrameTime(key.time),
|
|
VxVector(key.val.x,key.val.z,key.val.y)));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/********************************************************************************
|
|
Creates a float (blend shape) Controller on a CKObjectAnimation using
|
|
Max Controller key frame data. the 3 standard type of controller
|
|
are supported : Linear,Bezier and TCB
|
|
*********************************************************************************/
|
|
void Max2Nmo::DumpFloatKeys(Control* cont, CKObjectAnimation* anim, float scale, const char* targetBlendShapeName)
|
|
{
|
|
if (!cont) return;
|
|
IKeyControl *ikc = GetKeyControlInterface(cont);
|
|
if (!ikc) return ;
|
|
|
|
int i,numKeys = ikc->GetNumKeys();
|
|
if (!numKeys) return ;
|
|
|
|
Class_ID cont_cid = cont->ClassID();
|
|
|
|
// If this is not a standard controller we return
|
|
if (cont_cid.PartB()) return;
|
|
|
|
//--- Create the appropriate Virtools position controller
|
|
CKANIMATION_CONTROLLER VirtoolsCtrlType;
|
|
|
|
switch (cont_cid.PartA()) {
|
|
case TCBINTERP_FLOAT_CLASS_ID : VirtoolsCtrlType = CKANIMATION_TCBFLOAT_CONTROL; break;
|
|
case HYBRIDINTERP_FLOAT_CLASS_ID : VirtoolsCtrlType = CKANIMATION_BEZIERFLOAT_CONTROL; break;
|
|
case LININTERP_FLOAT_CLASS_ID : VirtoolsCtrlType = CKANIMATION_LINFLOAT_CONTROL; break;
|
|
default :
|
|
return;
|
|
}
|
|
CKAnimController* ctrl = anim->CreateController(VirtoolsCtrlType, targetBlendShapeName);
|
|
|
|
//--- And convert max keys
|
|
switch (cont_cid.PartA()) {
|
|
|
|
case TCBINTERP_FLOAT_CLASS_ID :
|
|
for (i=0; i<numKeys; i++) {
|
|
ITCBFloatKey key;
|
|
ikc->GetKey(i, &key); // Get Max Key
|
|
|
|
// Create Virtools Key
|
|
CKTCBFloatKey VirtoolsKey(FrameTime(key.time),
|
|
scale * key.val,
|
|
key.tens,key.cont,key.bias,
|
|
scale * key.easeIn, scale * key.easeOut);
|
|
ctrl->AddKey(&VirtoolsKey);
|
|
}
|
|
break;
|
|
|
|
case HYBRIDINTERP_FLOAT_CLASS_ID :
|
|
for (i=0; i<numKeys; i++) {
|
|
IBezFloatKey key;
|
|
ikc->GetKey(i, &key); // Get Max Key
|
|
|
|
// Convert Max Bezier tangent to Virtools format
|
|
CKBezierKeyFlags Flags=MaxBezFlags2Virtools(key.flags);
|
|
float ScaleFactor = GetFrameRate()*16.0f/3.0f;
|
|
|
|
// Create Virtools Bezier Key
|
|
CKBezierFloatKey VirtoolsKey(FrameTime(key.time),
|
|
scale * key.val,
|
|
Flags,
|
|
scale * key.intan * ScaleFactor, scale * key.outtan * ScaleFactor);
|
|
ctrl->AddKey(&VirtoolsKey);
|
|
}
|
|
break;
|
|
|
|
case LININTERP_FLOAT_CLASS_ID :
|
|
for (i=0; i<numKeys; i++) {
|
|
ILinFloatKey key;
|
|
|
|
ikc->GetKey(i, &key); // Get Max Key
|
|
|
|
// Create Virtools linear Position Key
|
|
ctrl->AddKey(&CKFloatKey(FrameTime(key.time), scale * key.val));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/********************************************************************************
|
|
Creates a Rotation Controller on a CKObjectAnimation using
|
|
Max Controller key frame data. the 3 standard type of controller
|
|
are supported : Linear,TCB (Bezier will be converted to linear)
|
|
********************************************************************************/
|
|
void Max2Nmo::DumpRotKeys(Control* cont,CKObjectAnimation* anim)
|
|
{
|
|
if (!cont) return;
|
|
IKeyControl *ikc = GetKeyControlInterface(cont);
|
|
if (!ikc) return ;
|
|
|
|
int numKeys = ikc->GetNumKeys();
|
|
if (!numKeys) return ;
|
|
|
|
CK3dEntity* ent = anim->Get3dEntity();
|
|
|
|
//-- Light and Camera animation need an additional rotation around X axis to be valid
|
|
//-- in Virtools ( Z axis is supposed to be the direction of the lights and cameras )
|
|
Quat RotateX;
|
|
RotateX.Identity();
|
|
if (CKIsChildClassOf(ent,CKCID_CAMERA) || CKIsChildClassOf(ent,CKCID_LIGHT)) {
|
|
RotateX=QFromAngAxis(HALFPI,Point3(1.0f,0.0f,0.0f));
|
|
}
|
|
|
|
Class_ID cont_cid = cont->ClassID();
|
|
// If this is not a standard controller we return
|
|
if (cont_cid.PartB()) return;
|
|
|
|
//--- Create the appropriate Virtools position controller
|
|
CKANIMATION_CONTROLLER VirtoolsCtrlType;
|
|
|
|
switch (cont_cid.PartA()) {
|
|
case TCBINTERP_ROTATION_CLASS_ID : VirtoolsCtrlType = CKANIMATION_TCBROT_CONTROL; break;
|
|
case HYBRIDINTERP_ROTATION_CLASS_ID :
|
|
case LININTERP_ROTATION_CLASS_ID : VirtoolsCtrlType = CKANIMATION_LINROT_CONTROL; break;
|
|
default : return;
|
|
}
|
|
CKAnimController* ctrl = anim->CreateController(VirtoolsCtrlType);
|
|
|
|
//--- And convert key frame data
|
|
switch (cont_cid.PartA()) {
|
|
case TCBINTERP_ROTATION_CLASS_ID :
|
|
{
|
|
// TCB Rotation anim use relative keys in Max => Need to convert to absolute
|
|
// rotation for Virtools
|
|
Quat absoluteRotQuat;
|
|
Quat quatFromAngAxis;
|
|
absoluteRotQuat.Identity();
|
|
|
|
for (int i=0; i<numKeys; i++) {
|
|
// Get Max key
|
|
ITCBRotKey key;
|
|
ikc->GetKey(i, &key);
|
|
|
|
// Convert 2 Virtools format
|
|
if (key.val.angle<0) {
|
|
key.val.angle = -key.val.angle;
|
|
key.val.axis = -key.val.axis;
|
|
}
|
|
key.val.axis = Normalize(key.val.axis);
|
|
quatFromAngAxis=QFromAngAxis(key.val.angle,key.val.axis);
|
|
absoluteRotQuat*=(const Quat&)quatFromAngAxis;
|
|
Quat Val = RotateX*absoluteRotQuat;
|
|
|
|
// And create Virtools Key
|
|
CKTCBRotationKey VirtoolsKey(FrameTime(key.time),
|
|
VxQuaternion(-Val.x,-Val.z,-Val.y,Val.w),
|
|
key.tens,key.cont,key.bias,
|
|
key.easeIn,key.easeOut);
|
|
ctrl->AddKey(&VirtoolsKey);
|
|
}
|
|
} break;
|
|
|
|
case HYBRIDINTERP_ROTATION_CLASS_ID :
|
|
case LININTERP_ROTATION_CLASS_ID :
|
|
{
|
|
for (int i=0; i<numKeys; i++) {
|
|
ILinRotKey key;
|
|
ikc->GetKey(i, &key);
|
|
|
|
Quat Val = RotateX*key.val;
|
|
ctrl->AddKey(&CKRotationKey(FrameTime(key.time),
|
|
VxQuaternion(-Val.x,-Val.z,-Val.y,Val.w)));
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
/**********************************************************************************
|
|
Creates a Scale Controller on a CKObjectAnimation using
|
|
Max Controller key frame data
|
|
**********************************************************************************/
|
|
void Max2Nmo::DumpScaleKeys(Control* cont,CKObjectAnimation* anim)
|
|
{
|
|
if (!cont) return;
|
|
IKeyControl *ikc = GetKeyControlInterface(cont);
|
|
if (!ikc) return ;
|
|
|
|
int numKeys = ikc->GetNumKeys();
|
|
if (!numKeys) return ;
|
|
|
|
//----------- TCB position
|
|
if (cont->ClassID() == Class_ID(TCBINTERP_SCALE_CLASS_ID, 0)) {
|
|
CKAnimController* ctrl = anim->CreateController(CKANIMATION_TCBSCL_CONTROL);
|
|
for (int i=0; i<numKeys; i++) {
|
|
ITCBScaleKey key;
|
|
ikc->GetKey(i, &key);
|
|
|
|
CKTCBScaleKey VirtoolsKey(FrameTime(key.time),
|
|
VxVector(key.val.s.x,key.val.s.z,key.val.s.y),
|
|
key.tens,key.cont,key.bias,
|
|
key.easeIn,key.easeOut);
|
|
ctrl->AddKey(&VirtoolsKey);
|
|
}
|
|
}
|
|
//----------- Bezier position
|
|
else if (cont->ClassID() == Class_ID(HYBRIDINTERP_SCALE_CLASS_ID, 0))
|
|
{
|
|
CKAnimController* ctrl = anim->CreateController(CKANIMATION_BEZIERSCL_CONTROL);
|
|
for (int i=0; i<numKeys; i++) {
|
|
IBezScaleKey key;
|
|
ikc->GetKey(i, &key);
|
|
|
|
CKBezierKeyFlags Flags=MaxBezFlags2Virtools(key.flags);
|
|
float ScaleFactor = GetFrameRate()*16.0f/3.0f;
|
|
VxVector InTang(key.intan.x,key.intan.z,key.intan.y),OutTang(key.outtan.x,key.outtan.z,key.outtan.y);
|
|
InTang*=ScaleFactor;
|
|
OutTang*=ScaleFactor;
|
|
|
|
CKBezierScaleKey VirtoolsKey(FrameTime(key.time),
|
|
VxVector(key.val.s.x,key.val.s.z,key.val.s.y),
|
|
Flags,
|
|
InTang,OutTang);
|
|
ctrl->AddKey(&VirtoolsKey);
|
|
}
|
|
}
|
|
//----------- Linear position
|
|
else if (cont->ClassID() == Class_ID(LININTERP_SCALE_CLASS_ID, 0)) {
|
|
CKAnimController* ctrl = anim->CreateController(CKANIMATION_LINSCL_CONTROL);
|
|
for (int i=0; i<numKeys; i++) {
|
|
ILinScaleKey key;
|
|
ikc->GetKey(i, &key);
|
|
|
|
ctrl->AddKey(&CKScaleKey(FrameTime(key.time),
|
|
VxVector(key.val.s.x,key.val.s.z,key.val.s.y)));
|
|
}
|
|
}
|
|
}
|
|
|
|
//-- Not truly the correct way to compare floats of arbitary magnitude...
|
|
BOOL EqualPoint3(Point3 p1, Point3 p2)
|
|
{
|
|
if (fabs(p1.x - p2.x) > ALMOST_ZERO) return FALSE;
|
|
if (fabs(p1.y - p2.y) > ALMOST_ZERO) return FALSE;
|
|
if (fabs(p1.z - p2.z) > ALMOST_ZERO) return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL EqualFloat(float v1, float v2)
|
|
{
|
|
if (fabs(v1 - v2) > ALMOST_ZERO) return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//--- Determine if a TM controller is known by the system.
|
|
BOOL Max2Nmo::IsKnownController(Control* cont)
|
|
{
|
|
ulong partA, partB;
|
|
if (!cont) return TRUE;
|
|
|
|
Class_ID cid = cont->ClassID();
|
|
|
|
#ifdef SUPPORT_NEW_CONTROLLERS
|
|
if (cid==IPOS_CONTROL_CLASS_ID)
|
|
return TRUE; //indepos
|
|
if (cid==IPOINT3_CONTROL_CLASS_ID)
|
|
return TRUE; //inderot, euler ?
|
|
if (cid==IPOINT4_CONTROL_CLASS_ID)
|
|
return TRUE; //inderot, quaternion ?
|
|
#endif
|
|
|
|
partA = cid.PartA();
|
|
partB = cid.PartB();
|
|
|
|
if (partB != 0x00) return FALSE;
|
|
|
|
switch (partA) {
|
|
case TCBINTERP_POSITION_CLASS_ID:
|
|
case TCBINTERP_ROTATION_CLASS_ID:
|
|
case TCBINTERP_SCALE_CLASS_ID:
|
|
case TCBINTERP_FLOAT_CLASS_ID:
|
|
case HYBRIDINTERP_POSITION_CLASS_ID:
|
|
case HYBRIDINTERP_ROTATION_CLASS_ID:
|
|
case HYBRIDINTERP_SCALE_CLASS_ID:
|
|
case HYBRIDINTERP_FLOAT_CLASS_ID:
|
|
case LININTERP_POSITION_CLASS_ID:
|
|
case LININTERP_ROTATION_CLASS_ID:
|
|
case LININTERP_SCALE_CLASS_ID:
|
|
case LININTERP_FLOAT_CLASS_ID:
|
|
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL Max2Nmo::DumpIndePos(INode* node,Control* cont,CKObjectAnimation* anim)
|
|
{
|
|
Class_ID cid = cont->ClassID();
|
|
if (cid!=IPOS_CONTROL_CLASS_ID)
|
|
return FALSE;
|
|
|
|
Tab<TimeValue> keyTimes;
|
|
TimeValue start = ip->GetAnimRange().Start();
|
|
TimeValue end = ip->GetAnimRange().End();
|
|
Interval range(start,end);
|
|
cont->GetKeyTimes(keyTimes,range,KEYAT_POSITION);
|
|
int tcount = keyTimes.Count();
|
|
if (tcount==0)
|
|
return TRUE;
|
|
|
|
//check subcontrolers
|
|
Control* subX = cont->GetXController();
|
|
Control* subY = cont->GetYController();
|
|
Control* subZ = cont->GetZController();
|
|
if (!subX || !subY || !subZ)
|
|
return FALSE;
|
|
Class_ID subX_cid = subX->ClassID();
|
|
Class_ID subY_cid = subY->ClassID();
|
|
Class_ID subZ_cid = subZ->ClassID();
|
|
|
|
// If this is not a standard controller we return
|
|
if (subX_cid.PartB() || subY_cid.PartB() || subZ_cid.PartB())
|
|
return FALSE;
|
|
|
|
// if different controler per axis, not supported
|
|
if (subX_cid.PartA()!=subY_cid.PartA() || subX_cid.PartA()!=subZ_cid.PartA() )
|
|
return FALSE;
|
|
IKeyControl *ikcX = GetKeyControlInterface(subX);
|
|
IKeyControl *ikcY = GetKeyControlInterface(subY);
|
|
IKeyControl *ikcZ = GetKeyControlInterface(subZ);
|
|
int ikcXIndex=0,ikcYIndex=0,ikcZIndex=0;
|
|
|
|
//--- Create the appropriate Virtools position controller
|
|
CKANIMATION_CONTROLLER VirtoolsCtrlType;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//POSITION KEYS
|
|
switch (subX_cid.PartA()) {
|
|
case TCBINTERP_FLOAT_CLASS_ID:
|
|
VirtoolsCtrlType = CKANIMATION_TCBPOS_CONTROL; break;
|
|
case HYBRIDINTERP_FLOAT_CLASS_ID:
|
|
VirtoolsCtrlType = CKANIMATION_BEZIERPOS_CONTROL; break;
|
|
case LININTERP_FLOAT_CLASS_ID:
|
|
VirtoolsCtrlType = CKANIMATION_LINPOS_CONTROL; break;
|
|
default : return FALSE;
|
|
}
|
|
|
|
//now we sample, coz we cannot use independant bezier / tcb
|
|
TimeValue t;
|
|
int delta = GetTicksPerFrame();// * GetKeyFrameStep();
|
|
|
|
CKAnimController* ctrl = anim->CreateController(VirtoolsCtrlType);
|
|
|
|
//browse times
|
|
for (int i=0;i<tcount;++i)
|
|
{
|
|
t = keyTimes[i];
|
|
Matrix3 tm = node->GetNodeTM(t) * Inverse(node->GetParentTM(t));
|
|
AffineParts ap;
|
|
decomp_affine(tm, &ap);
|
|
Point3 pos = ap.t;
|
|
|
|
switch(VirtoolsCtrlType)
|
|
{
|
|
case CKANIMATION_BEZIERPOS_CONTROL:
|
|
{
|
|
//watch previous & next frame
|
|
tm = node->GetNodeTM(t-delta) * Inverse(node->GetParentTM(t-delta));
|
|
decomp_affine(tm, &ap);
|
|
Point3 tan1 = pos-ap.t;
|
|
tm = node->GetNodeTM(t+delta) * Inverse(node->GetParentTM(t+delta));
|
|
decomp_affine(tm, &ap);
|
|
Point3 tan2 = ap.t-pos;
|
|
|
|
//float intan, outtan;
|
|
|
|
//must read key for bezier flags
|
|
IBezFloatKey keyX,keyY,keyZ;
|
|
DWORD flags=0;
|
|
ikcX->GetKey(ikcXIndex,&keyX);
|
|
ikcY->GetKey(ikcYIndex,&keyY);
|
|
ikcZ->GetKey(ikcZIndex,&keyZ);
|
|
if (keyX.time==t)
|
|
{
|
|
ikcXIndex++;
|
|
flags = keyX.flags;
|
|
tan1.x = keyX.intan;
|
|
tan2.x = keyX.outtan;
|
|
}
|
|
if (keyY.time==t)
|
|
{
|
|
ikcYIndex++;
|
|
flags = keyY.flags;
|
|
tan1.y = keyY.intan;
|
|
tan2.y = keyY.outtan;
|
|
}
|
|
if (keyZ.time==t)
|
|
{
|
|
ikcZIndex++;
|
|
flags = keyZ.flags;
|
|
tan1.z = keyZ.intan;
|
|
tan2.z = keyZ.outtan;
|
|
}//take only one flags, hope this is the good one then
|
|
|
|
// Convert Max Bezier tangent to Virtools format
|
|
CKBezierKeyFlags Flags=MaxBezFlags2Virtools(flags);
|
|
float ScaleFactor = GetFrameRate()*16.0f/3.0f;
|
|
VxVector InTang(tan1.x,tan1.z,tan1.y),OutTang(tan2.x,tan2.z,tan2.y);
|
|
InTang*=ScaleFactor;
|
|
OutTang*=ScaleFactor;
|
|
|
|
// Create Virtools Bezier Key
|
|
CKBezierPositionKey VirtoolsKey(FrameTime(t),
|
|
VxVector(pos.x,pos.z,pos.y),
|
|
Flags,
|
|
InTang,OutTang);
|
|
ctrl->AddKey(&VirtoolsKey);
|
|
}break;
|
|
case CKANIMATION_LINPOS_CONTROL:
|
|
{
|
|
ILinPoint3Key key;
|
|
ctrl->AddKey(&CKPositionKey(FrameTime(t),
|
|
VxVector(pos.x,pos.z,pos.y)));
|
|
}break;
|
|
case CKANIMATION_TCBPOS_CONTROL:
|
|
{
|
|
}break;
|
|
}
|
|
}
|
|
|
|
/* other way
|
|
IKeyControl *ikcX = GetKeyControlInterface(subX);
|
|
IKeyControl *ikcY = GetKeyControlInterface(subY);
|
|
IKeyControl *ikcZ = GetKeyControlInterface(subZ);
|
|
int xnum = subX->NumKeys();
|
|
int ynum = subY->NumKeys();
|
|
int znum = subZ->NumKeys();
|
|
*/
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL Max2Nmo::DumpIndeRot(INode* node,Control* cont,CKObjectAnimation* anim)
|
|
{
|
|
/*
|
|
Class cid = cont->ClassID();
|
|
if (cid==IPOINT3_CONTROL_CLASS_ID)
|
|
{
|
|
}
|
|
else if (cid==IPOINT4_CONTROL_CLASS_ID)
|
|
{
|
|
}
|
|
*/
|
|
/*
|
|
HYBRIDINTERP_POINT4_CLASS_ID
|
|
TCBINTERP_POINT4_CLASS_ID
|
|
*/
|
|
return FALSE;
|
|
|
|
}
|