//************************************************************************** //* 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()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()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()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()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 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 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 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 = 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; iGetKey(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; iGetKey(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; iGetKey(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; iGetKey(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; iGetKey(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; iGetKey(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; iGetKey(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; iGetKey(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; iGetKey(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; iGetKey(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; iGetKey(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 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;iGetNodeTM(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; }