//************************************************************************** //* Animout.cpp - Ascii File Exporter //* //* By Christer Janson //* Kinetix Development //* //* January 20, 1997 CCJ Initial coding //* //* This module handles controller key output and controller sampling. //* //* Copyright (c) 1997, All Rights Reserved. //*************************************************************************** #include "asciiexp.h" #define ALMOST_ZERO 1.0e-3f BOOL EqualPoint3(Point3 p1, Point3 p2); /**************************************************************************** TM Animation output ****************************************************************************/ // Get hold of the transform controllers for the node... void AsciiExp::ExportAnimKeys( INode* node ) { BOOL bPosAnim; BOOL bRotAnim; BOOL bScaleAnim; BOOL bDoKeys = FALSE; // We can only export keys if all TM controllers are "known" to us. // The reason for that is that some controllers control more than what // they should. Consider a path position controller, if you turn on // follow and banking, this position controller will also control // rotation. If a node that had a path position controller also had a // TCB rotation controller, the TCB keys would not describe the whole // rotation of the node. // For that reason we will only export keys if all controllers // position, rotation and scale are linear, hybrid (bezier) or tcb. /* if (!GetAlwaysSample()) { Control* pC = node->GetTMController()->GetPositionController(); Control* rC = node->GetTMController()->GetRotationController(); Control* sC = node->GetTMController()->GetScaleController(); if (IsKnownController(pC) && IsKnownController(rC) && IsKnownController(sC)) { bDoKeys = TRUE; } } */ if (bDoKeys) { // Only dump the track header if any of the controllers have keys if (node->GetTMController()->GetPositionController()->NumKeys() || node->GetTMController()->GetRotationController()->NumKeys() || node->GetTMController()->GetScaleController()->NumKeys()) { // fprintf(pStream,"%s\t%s {\n", indent.data(), ID_TM_ANIMATION); // fprintf(pStream,"%s\t\t%s \"%s\"\n", indent.data(), ID_NODE_NAME, FixupName(node->GetName())); DumpPosKeys(node->GetTMController()->GetPositionController()); DumpRotKeys(node->GetTMController()->GetRotationController()); DumpScaleKeys(node->GetTMController()->GetScaleController()); // fprintf(pStream,"%s\t}\n", indent.data()); } } else if (CheckForAnimation(node, bPosAnim, bRotAnim, bScaleAnim)) { // fprintf(pStream,"%s\t%s {\n", indent.data(), ID_TM_ANIMATION); // fprintf(pStream,"%s\t\t%s \"%s\"\n", indent.data(), ID_NODE_NAME, FixupName(node->GetName())); if (bPosAnim) DumpPosSample(node); if (bRotAnim) DumpRotSample(node); if (bScaleAnim) DumpScaleSample(node); // fprintf(pStream,"%s\t}\n", indent.data()); } } // 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. // // Some controllers makes it problematic to examine the TMContollers instead of // the actual TMMatrix. For example, a path controller is a position controller, // but if you turn on follow and banking, it will also affect the rotation component. // If we want to, we can examine the position, rotation and scale controllers and // if they all are Linear, Hybrid (bezier) or TCB, then we could export the actual keys. // This is not at all difficult, but the importer has to know the exact interpolation // algorithm in order to use it. The source code to the interpolation routines are available // to ADN members. // // For an example of how to export actual keys, look at DumpPoint3Keys() below. // This method will check the actual controller to determine if the controller is known. // If we know how to work this controller, its actual keys will be exported, // otherwise the controller will be sampled using the user specified sampling frequency. BOOL AsciiExp::CheckForAnimation(INode* node, BOOL& bPos, BOOL& bRot, BOOL& bScale) { 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; Point3 firstScaleFactor; bPos = bRot = bScale = 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); if (t != start) { // We examine the rotation angle to see if the rotation component // has changed. // Although not entierly true, it should work. // It is rare that the rotation axis is animated without // the rotation angle being somewhat affected. bPos = TRUE; bRot = TRUE; bScale = TRUE; // if (!EqualPoint3(ap.t, firstPos)) bPos = TRUE; // if (fabs(rotAngle - firstRotAngle) > ALMOST_ZERO) bRot = TRUE; // if (!EqualPoint3(ap.k, firstScaleFactor)) bScale = TRUE; } else { firstPos = ap.t; firstRotAngle = rotAngle; firstScaleFactor = ap.k; } // No need to continue looping if all components are animated if (bPos && bRot && bScale) break; } return bPos || bRot || bScale; } void AsciiExp::DumpPosQuatSample(INode* node) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_POS_TRACK); TimeValue start = ip->GetAnimRange().Start(); TimeValue end = ip->GetAnimRange().End(); TimeValue t; int delta = GetTicksPerFrame(); Matrix3 tm; AffineParts ap; Point3 prevPos; Quat prevQ; Quat q; char name[256]; sprintf( name, "%s", node->GetName() ); fprintf( tempStream, "OUTPUTTING - %s\n", name ); // DEBUG FILE // fwrite( &name, sizeof(char), NAME_LENGTH, boneStream ); // WRITE BONE NAME prevQ.Identity(); for (t=start; t<=end; t+=delta) { // TRANSLATION tm = node->GetNodeTM(t);// * Inverse(node->GetParentTM(t)); decomp_affine(tm, &ap); fwrite( &ap.t.x, sizeof(float), 1, expStream ); // WRITE BONE X-POS fwrite( &ap.t.z, sizeof(float), 1, expStream ); // WRITE BONE Y-POS fwrite( &ap.t.y, sizeof(float), 1, expStream ); // WRITE BONE Z-POS fwrite( &ap.q.x, sizeof(float), 1, expStream ); // WRITE BONE ROT X-AXIS fwrite( &ap.q.z, sizeof(float), 1, expStream ); // WRITE BONE ROT Y-AXIS fwrite( &ap.q.y, sizeof(float), 1, expStream ); // WRITE BONE ROT Z-AXIS fwrite( &ap.q.w, sizeof(float), 1, expStream ); // WRITE BONE ROT W ANGLE } } void AsciiExp::DumpPosSample(INode* node) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_POS_TRACK); TimeValue start = ip->GetAnimRange().Start(); TimeValue end = ip->GetAnimRange().End(); TimeValue t; int delta = GetTicksPerFrame(); Matrix3 tm; AffineParts ap; Point3 prevPos; Quat prevQ; Quat q; char name[256]; sprintf( name, "%s", node->GetName() ); fprintf( tempStream, "OUTPUTTING - %s\n", name ); // DEBUG FILE // fwrite( &name, sizeof(char), NAME_LENGTH, boneStream ); // WRITE BONE NAME prevQ.Identity(); for (t=start; t<=end; t+=delta) { // TRANSLATION tm = node->GetNodeTM(t);// * Inverse(node->GetParentTM(t)); decomp_affine(tm, &ap); Point3 fpos; Point3 pos = ap.t; prevPos = pos; fpos.x = pos.x; fpos.y = pos.z; fpos.z = -pos.y; fprintf( tempStream, " POS = %f %f %f\n", pos.x, pos.z, -pos.y ); // fwrite( &fpos.x, sizeof(float), 1, boneStream ); // WRITE BONE X-POS // fwrite( &fpos.y, sizeof(float), 1, boneStream ); // WRITE BONE Y-POS // fwrite( &fpos.z, sizeof(float), 1, boneStream ); // WRITE BONE Z-POS // ROTATION tm = node->GetNodeTM(t);// * Inverse(node->GetParentTM(t)); decomp_affine(tm, &ap); if (t == start) { q = ap.q; prevQ = ap.q; // SAVE BASE ROTATION, THEN USE THIS AS ROTATION ORIGIN } else { q = ap.q / prevQ; } fprintf( tempStream, " QUAT = %f %f %f %f\n", q.x, q.y, q.z, q.w ); // fwrite( &q.x, sizeof(float), 1, boneStream ); // WRITE BONE ROT X-AXIS // fwrite( &q.y, sizeof(float), 1, boneStream ); // WRITE BONE ROT Y-AXIS // fwrite( &q.z, sizeof(float), 1, boneStream ); // WRITE BONE ROT Z-AXIS // fwrite( &q.w, sizeof(float), 1, boneStream ); // WRITE BONE ROT W ANGLE } } void AsciiExp::DumpRotSample(INode* node) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_ROT_TRACK); TimeValue start = ip->GetAnimRange().Start(); TimeValue end = ip->GetAnimRange().End(); TimeValue t; int delta = GetTicksPerFrame(); Matrix3 tm; AffineParts ap; Quat prevQ; prevQ.Identity(); for (t=start; t<=end; t+=delta) { tm = node->GetNodeTM(t) * Inverse(node->GetParentTM(t)); decomp_affine(tm, &ap); // Rotation keys should be relative, so we need to convert these // absolute samples to relative values. Quat q = ap.q / prevQ; prevQ = ap.q; // No point in exporting null keys... // if (q.IsIdentity()) continue; // Output the sample fprintf( tempStream, " QUAT = %f %f %f %f\n", q.x, q.y, q.z, q.w ); // fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_ROT_SAMPLE, t, Format(q)); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } void AsciiExp::DumpScaleSample(INode* node) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_SCALE_TRACK); TimeValue start = ip->GetAnimRange().Start(); TimeValue end = ip->GetAnimRange().End(); TimeValue t; int delta = GetTicksPerFrame(); Matrix3 tm; AffineParts ap; Point3 prevFac; for (t=start; t<=end; t+=delta) { tm = node->GetNodeTM(t) * Inverse(node->GetParentTM(t)); decomp_affine(tm, &ap); // Skip identical keys if (t!= start && EqualPoint3(ap.k, prevFac)) continue; prevFac = ap.k; // Output the sample // fprintf(pStream, "%s\t\t\t%s %d\t%s %s\n", indent.data(), ID_SCALE_SAMPLE, t, Format(ap.k), Format(ap.u)); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } // Output point3 keys if this is a known point3 controller that // supports key operations. Otherwise we will sample the controller // once for each frame to get the value. // Point3 controllers can control, for example, color. void AsciiExp::DumpPoint3Keys(Control* cont) { // Bug out if no controller. if (!cont) return; int i; IKeyControl *ikc = NULL; // If the user wants us to always sample, we will ignore the KeyControlInterface // if (!GetAlwaysSample()) ikc = GetKeyControlInterface(cont); // TCB point3 if (ikc && cont->ClassID() == Class_ID(TCBINTERP_POINT3_CLASS_ID, 0)) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_POINT3_TCB); for (i=0; iGetNumKeys(); i++) { ITCBPoint3Key key; ikc->GetKey(i, &key); fprintf( tempStream, " KEYPOS = %f %f %f\n", key.val.x, key.val.y, key.val.z ); // fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_TCB_POINT3_KEY, key.time, Format(key.val)); // Add TCB specific data // fprintf(pStream, "\t%s\t%s\t%s\t%s\t%s\n", Format(key.tens), Format(key.cont), Format(key.bias), Format(key.easeIn), Format(key.easeOut)); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } // Bezier point3 else if (ikc && cont->ClassID() == Class_ID(HYBRIDINTERP_POINT3_CLASS_ID, 0)) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_POINT3_BEZIER); for (i=0; iGetNumKeys(); i++) { IBezPoint3Key key; ikc->GetKey(i, &key); fprintf( tempStream, " KEYPOS = %f %f %f\n", key.val.x, key.val.y, key.val.z ); // fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_BEZIER_POINT3_KEY, key.time, Format(key.val)); // fprintf(pStream, "\t%s\t%s\t%d\n", Format(key.intan), Format(key.outtan), key.flags); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } // Bezier color else if (ikc && cont->ClassID() == Class_ID(HYBRIDINTERP_COLOR_CLASS_ID, 0)) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_COLOR_BEZIER); for (i=0; iGetNumKeys(); i++) { IBezPoint3Key key; ikc->GetKey(i, &key); fprintf( tempStream, " KEYPOS = %f %f %f\n", key.val.x, key.val.y, key.val.z ); // fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_BEZIER_POINT3_KEY, key.time, Format(key.val)); // fprintf(pStream, "\t%s\t%s\t%d\n", Format(key.intan), Format(key.outtan), key.flags); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } else { // Unknown controller, no key interface or sample on demand - // This might be a procedural controller or something else we // don't know about. The last resort is to get the value from the // controller at every n frames. TSTR name; cont->GetClassName(name); // fprintf(pStream,"%s\t\t%s \"%s\" {\n", indent.data(), ID_CONTROL_POINT3_SAMPLE, FixupName(name)); // If it is animated at all... if (cont->IsAnimated()) { // Get the range of the controller animation Interval range; // Get range of full animation Interval animRange = ip->GetAnimRange(); TimeValue t = cont->GetTimeRange(TIMERANGE_ALL).Start(); Point3 value; // While we are inside the animation... while (animRange.InInterval(t)) { // Sample the controller range = FOREVER; cont->GetValue(t, &value, range); // Set time to start of controller validity interval t = range.Start(); // Output the sample fprintf( tempStream, " KEYPOS = %f %f %f\n", value.x, value.y, value.z ); // fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_POINT3_KEY, t, Format(value)); // If the end of the controller validity is beyond the // range of the animation if (range.End() > cont->GetTimeRange(TIMERANGE_ALL).End()) { break; } else { t = (range.End()/GetTicksPerFrame()+1) * GetTicksPerFrame(); } } } // fprintf(pStream,"%s\t\t}\n", indent.data()); } } // Output float keys if this is a known float controller that // supports key operations. Otherwise we will sample the controller // once for each frame to get the value. void AsciiExp::DumpFloatKeys(Control* cont) { if (!cont) return; int i; IKeyControl *ikc = NULL; // If the user wants us to always sample, we will ignore the KeyControlInterface // if (!GetAlwaysSample()) ikc = GetKeyControlInterface(cont); // TCB float if (ikc && cont->ClassID() == Class_ID(TCBINTERP_FLOAT_CLASS_ID, 0)) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_FLOAT_TCB); for (i=0; iGetNumKeys(); i++) { ITCBFloatKey key; ikc->GetKey(i, &key); // fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_TCB_FLOAT_KEY, key.time, Format(key.val)); // fprintf(pStream, "\t%s\t%s\t%s\t%s\t%s\n", Format(key.tens), Format(key.cont), Format(key.bias), Format(key.easeIn), Format(key.easeOut)); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } // Bezier float else if (ikc && cont->ClassID() == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID, 0)) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_FLOAT_BEZIER); for (i=0; iGetNumKeys(); i++) { IBezFloatKey key; ikc->GetKey(i, &key); // fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_BEZIER_FLOAT_KEY, key.time, Format(key.val)); // fprintf(pStream, "\t%s\t%s\t%d\n", Format(key.intan), Format(key.outtan), key.flags); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } else if (ikc && cont->ClassID() == Class_ID(LININTERP_FLOAT_CLASS_ID, 0)) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_FLOAT_LINEAR); for (i=0; iGetNumKeys(); i++) { ILinFloatKey key; ikc->GetKey(i, &key); // fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_FLOAT_KEY, key.time, Format(key.val)); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } else { // Unknown controller, no key interface or sample on demand - // This might be a procedural controller or something else we // don't know about. The last resort is to get the value from the // controller at every n frames. TSTR name; cont->GetClassName(name); // fprintf(pStream,"%s\t\t%s \"%s\" {\n", indent.data(), ID_CONTROL_FLOAT_SAMPLE, FixupName(name)); // If it is animated at all... if (cont->IsAnimated()) { // Get the range of the controller animation Interval range; // Get range of full animation Interval animRange = ip->GetAnimRange(); TimeValue t = cont->GetTimeRange(TIMERANGE_ALL).Start(); float value; // While we are inside the animation... while (animRange.InInterval(t)) { // Sample the controller range = FOREVER; cont->GetValue(t, &value, range); // Set time to start of controller validity interval t = range.Start(); // Output the sample // fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_FLOAT_KEY, t, Format(value)); // If the end of the controller validity is beyond the // range of the animation if (range.End() > cont->GetTimeRange(TIMERANGE_ALL).End()) { break; } else { t = (range.End()/GetTicksPerFrame()+1) * GetTicksPerFrame(); } } } // fprintf(pStream,"%s\t\t}\n", indent.data()); } } void AsciiExp::DumpPosKeys(Control* cont) { if (!cont) return; int i; IKeyControl *ikc = GetKeyControlInterface(cont); // TCB position if (ikc && cont->ClassID() == Class_ID(TCBINTERP_POSITION_CLASS_ID, 0)) { int numKeys; if (numKeys = ikc->GetNumKeys()) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_POS_TCB); for (i=0; iGetKey(i, &key); // fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_TCB_POS_KEY, key.time, Format(key.val)); // fprintf(pStream, "\t%s\t%s\t%s\t%s\t%s\n", Format(key.tens), Format(key.cont), Format(key.bias), Format(key.easeIn), Format(key.easeOut)); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } } // Bezier position else if (ikc && cont->ClassID() == Class_ID(HYBRIDINTERP_POSITION_CLASS_ID, 0)) { int numKeys; if(numKeys = ikc->GetNumKeys()) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_POS_BEZIER); for (i=0; iGetKey(i, &key); // fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_BEZIER_POS_KEY, key.time, Format(key.val)); // fprintf(pStream, "\t%s\t%s\t%d\n", Format(key.intan), Format(key.outtan), key.flags); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } } // Linear position else if (ikc && cont->ClassID() == Class_ID(LININTERP_POSITION_CLASS_ID, 0)) { int numKeys; if(numKeys = ikc->GetNumKeys()) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_POS_LINEAR); for (i=0; iGetKey(i, &key); // fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_POS_KEY, key.time, Format(key.val)); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } } } void AsciiExp::DumpRotKeys(Control* cont) { if (!cont) return; int i; IKeyControl *ikc = GetKeyControlInterface(cont); if (ikc && cont->ClassID() == Class_ID(TCBINTERP_ROTATION_CLASS_ID, 0)) { int numKeys; if (numKeys = ikc->GetNumKeys()) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_ROT_TCB); for (i=0; iGetKey(i, &key); // fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_TCB_ROT_KEY, key.time, Format(key.val)); // fprintf(pStream, "\t%s\t%s\t%s\t%s\t%s\n", Format(key.tens), Format(key.cont), Format(key.bias), Format(key.easeIn), Format(key.easeOut)); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } } else if (ikc && cont->ClassID() == Class_ID(HYBRIDINTERP_ROTATION_CLASS_ID, 0)) { int numKeys; if (numKeys = ikc->GetNumKeys()) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_ROT_BEZIER); for (i=0; iGetKey(i, &key); // Quaternions are converted to AngAxis when written to file // There is no intan/outtan for Quat Rotations // fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_ROT_KEY, key.time, Format(key.val)); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } } else if (ikc && cont->ClassID() == Class_ID(LININTERP_ROTATION_CLASS_ID, 0)) { int numKeys; if (numKeys = ikc->GetNumKeys()) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_ROT_LINEAR); for (i=0; iGetKey(i, &key); // Quaternions are converted to AngAxis when written to file // fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_ROT_KEY, key.time, Format(key.val)); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } } } void AsciiExp::DumpScaleKeys(Control* cont) { if (!cont) return; int i; IKeyControl *ikc = GetKeyControlInterface(cont); if (ikc && cont->ClassID() == Class_ID(TCBINTERP_SCALE_CLASS_ID, 0)) { int numKeys; if (numKeys = ikc->GetNumKeys()) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_SCALE_TCB); for (i=0; iGetKey(i, &key); // fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_TCB_SCALE_KEY, key.time, Format(key.val)); // fprintf(pStream, "\t%s\t%s\t%s\t%s\t%s\n", Format(key.tens), Format(key.cont), Format(key.bias), Format(key.easeIn), Format(key.easeOut)); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } } else if (ikc && cont->ClassID() == Class_ID(HYBRIDINTERP_SCALE_CLASS_ID, 0)) { int numKeys; if (numKeys = ikc->GetNumKeys()) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_SCALE_BEZIER); for (i=0; iGetKey(i, &key); // fprintf(pStream, "%s\t\t\t%s %d\t%s", indent.data(), ID_BEZIER_SCALE_KEY, key.time, Format(key.val)); // fprintf(pStream, "\t%s\t%s\t%d\n", Format(key.intan), Format(key.outtan), key.flags); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } } else if (ikc && cont->ClassID() == Class_ID(LININTERP_SCALE_CLASS_ID, 0)) { int numKeys; if (numKeys = ikc->GetNumKeys()) { // fprintf(pStream,"%s\t\t%s {\n", indent.data(), ID_CONTROL_SCALE_LINEAR); for (i=0; iGetKey(i, &key); // fprintf(pStream, "%s\t\t\t%s %d\t%s\n", indent.data(), ID_SCALE_KEY, key.time, Format(key.val)); } // fprintf(pStream,"%s\t\t}\n", indent.data()); } } } // 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; } // Determine if a TM controller is known by the system. AsciiExp::IsKnownController(Control* cont) { ulong partA, partB; if (!cont) return FALSE; partA = cont->ClassID().PartA(); partB = cont->ClassID().PartB(); if (partB != 0x00) return FALSE; switch (partA) { case TCBINTERP_POSITION_CLASS_ID: case TCBINTERP_ROTATION_CLASS_ID: case TCBINTERP_SCALE_CLASS_ID: case HYBRIDINTERP_POSITION_CLASS_ID: case HYBRIDINTERP_ROTATION_CLASS_ID: case HYBRIDINTERP_SCALE_CLASS_ID: case LININTERP_POSITION_CLASS_ID: case LININTERP_ROTATION_CLASS_ID: case LININTERP_SCALE_CLASS_ID: return TRUE; } return FALSE; }