/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /* $Header: /Commando/Code/Tools/max2w3d/motion.cpp 26 10/30/00 6:56p Greg_h $ */ /*********************************************************************************************** *** Confidential - Westwood Studios *** *********************************************************************************************** * * * Project Name : Commando Tools - W3D export * * * * $Archive:: /Commando/Code/Tools/max2w3d/motion.cpp $* * * * $Author:: Greg_h $* * * * $Modtime:: 10/30/00 5:25p $* * * * $Revision:: 26 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * MotionClass::MotionClass -- constructor * * MotionClass::MotionClass -- constructor * * MotionClass::init -- initialize * * MotionClass::~MotionClass -- destructor * * MotionClass::compute_frame_motion -- compute the motion for a specified frame * * MotionClass::set_motion_matrix -- save a motin matrix * * MotionClass::get_motion_matrix -- retrieve a motion matrix * * MotionClass::set_eulers -- store euler angles * * MotionClass::get_eulers -- retrieve euler angles * * MotionClass::Save -- save the motion to a W3D file * * MotionClass::save_header -- save the header * * MotionClass::save_channels -- save the motion channels * * MotionClass::set_visibility -- store a visibility bit * * MotionClass::get_visibility -- retrieve the visibility bit for this node:frame * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "motion.h" #include "w3d_file.h" #include "vchannel.h" #include "bchannel.h" #include "euler.h" #include "util.h" #include "errclass.h" #include "w3dutil.h" #include "exportlog.h" /*********************************************************************************************** * MotionClass::MotionClass -- constructor * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ MotionClass::MotionClass ( IScene * scene, INode * rootnode, HierarchySaveClass * basepose, W3dExportOptionsStruct & options, int framerate, Progress_Meter_Class * meter, HWND MaxHwnd, char * name, Matrix3 & offset ): BasePose(basepose), Scene(scene), RootNode(rootnode), RootList(NULL), StartFrame(options.StartFrame), EndFrame(options.EndFrame), ReduceAnimation(options.ReduceAnimation), ReduceAnimationPercent(options.ReduceAnimationPercent), CompressAnimation(options.CompressAnimation), CompressAnimationFlavor(options.CompressAnimationFlavor), CompressAnimationTranslationError(options.CompressAnimationTranslationError), CompressAnimationRotationError(options.CompressAnimationRotationError), FrameRate(framerate), Meter(meter), Offset(offset) { ExportLog::printf("Initializing Capture....\n"); init(); Set_W3D_Name(Name,name); } /*********************************************************************************************** * MotionClass::MotionClass -- constructor * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ MotionClass::MotionClass ( IScene * scene, INodeListClass * rootlist, HierarchySaveClass * basepose, W3dExportOptionsStruct & options, int framerate, Progress_Meter_Class * meter, HWND MaxHwnd, char * name, Matrix3 & offset ): BasePose(basepose), Scene(scene), RootNode(NULL), RootList(rootlist), StartFrame(options.StartFrame), EndFrame(options.EndFrame), ReduceAnimation(options.ReduceAnimation), ReduceAnimationPercent(options.ReduceAnimationPercent), CompressAnimation(options.CompressAnimation), CompressAnimationFlavor(options.CompressAnimationFlavor), CompressAnimationTranslationError(options.CompressAnimationTranslationError), CompressAnimationRotationError(options.CompressAnimationRotationError), FrameRate(framerate), Meter(meter), Offset(offset) { ExportLog::printf("Initializing Capture....\n"); init(); Set_W3D_Name(Name,name); } /*********************************************************************************************** * MotionClass::init -- initialize * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ void MotionClass::init(void) { int i,j; NumFrames = (EndFrame - StartFrame + 1); ExportLog::printf("Extracting %d frames of animation from Max\n", NumFrames); ExportLog::printf("\n"); /* ** Allocate space for a matrix per frame per node ** and an XYZEulers per frame per node. */ MotionMatrix = new Matrix3 * [BasePose->Num_Nodes()]; if (MotionMatrix == NULL) { throw (ErrorClass("Out of Memory")); } EulerDelta = new Point3 * [BasePose->Num_Nodes()]; if (EulerDelta == NULL) { throw (ErrorClass("Out of Memory")); } for (i=0; iNum_Nodes(); i++) { MotionMatrix[i] = new Matrix3[NumFrames]; if (MotionMatrix[i] == NULL) { throw (ErrorClass("Out of Memory")); } /* ** Initialize all of the matrices to identity. */ for (j=0; jNum_Nodes(); i++) { EulerDelta[i] = new Point3[NumFrames]; if (EulerDelta[i] == NULL) { throw (ErrorClass("Out of Memory")); } /* ** Initialize all of the Euler angles to 0,0,0 */ for (j=0; jNum_Nodes()]; for (i=0; iNum_Nodes(); i++) { VisData[i].Resize(NumFrames); /* ** initialize to always visible */ for (j=0; jNum_Nodes()]; for (i=0; iNum_Nodes(); i++) { BinMoveData[i].Resize(NumFrames); /* ** initialize to always interpolate */ for (j=0; jNum_Nodes()); for (i=0; iNum_Nodes(); i++) { NodeValidFlags[i] = 0; } /* ** Compute motion data for each frame */ for (i=0; i < NumFrames; i++) { ExportLog::rprintf("( %d ) ", i); ExportLog::updatebar(i, NumFrames); compute_frame_motion(i); } ExportLog::updatebar(1, 1); // 100% ExportLog::rprintf("Extraction Complete.\n"); } /*********************************************************************************************** * MotionClass::~MotionClass -- destructor * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ MotionClass::~MotionClass(void) { int i; for (i=0; iNum_Nodes(); i++) { if (MotionMatrix[i]) delete[] MotionMatrix[i]; } if (MotionMatrix) { delete[] MotionMatrix; } for (i=0; iNum_Nodes(); i++) { if (EulerDelta[i]) delete[] EulerDelta[i]; } if (EulerDelta) { delete[] EulerDelta; } if (VisData) { delete[] VisData; } if (BinMoveData) { delete[] BinMoveData; } ExportLog::printf("Destroy Log..%d,%d,%d,%d, %s..\n",1,2,3,4,"go"); } /*********************************************************************************************** * MotionClass::compute_frame_motion -- compute the motion for a specified frame * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ void MotionClass::compute_frame_motion(int frame) { /* ** Compute MAX's time value for this frame ** NOTE: the frame index passed in is the offset from StartFrame ** to get the original MAX frame number, we add StartFrame. */ TimeValue frametime = (StartFrame + frame) * GetTicksPerFrame(); /* ** Create a hierarchy tree object for the scene at this frame */ HierarchySaveClass * tree; if (RootNode != NULL) { tree = new HierarchySaveClass(RootNode,frametime,*Meter,"NoName",false,BasePose); } else { tree = new HierarchySaveClass(RootList,frametime,*Meter,"NoName",false,BasePose,Offset); } if (tree == NULL) { throw (ErrorClass("Out of memory!")); } /* ** Loop over each node in this frame's tree */ for (int tindex=0; tindexNum_Nodes(); tindex++) { /* ** Find the node in the Base Pose corresponding to this node. ** If this node is not in the base pose, skip */ int bindex = BasePose->Find_Named_Node(tree->Get_Node_Name(tindex)); if (bindex != -1) { /* ** Get the relative transform from the base and from ** this frame's tree. Assume that both have already been "fixed"; ** obviously the base pose has... However, the current tree ** needs to be built by passing the basepose in as the "fixup tree" ** ** What are the "fixup" matrices? These are the transforms which ** were applied to the base pose when the user wanted to force the ** base pose to use only matrices with certain properties. For ** example, if we wanted the base pose to use translations only, ** the fixup transform for each node is a transform which when ** multiplied by the real node's world transform, yeilds a pure ** translation matrix. Fixup matrices also show up in the mesh ** exporter since all vertices must be transformed by their inverses ** in order to make things work... */ Matrix3 basetm = BasePose->Get_Node_Relative_Transform(bindex); Matrix3 thistm = tree->Get_Node_Relative_Transform(tindex); INode *tree_node = tree->Get_Node(tindex); Matrix3 motion = thistm * Inverse(basetm); motion = Cleanup_Orthogonal_Matrix(motion); set_motion_matrix(bindex,frame,motion); /* ** Also, store the Euler angles for this node */ EulerAnglesClass my_eulers(motion,EulerOrderXYZr); float ex = my_eulers.Get_Angle(0); float ey = my_eulers.Get_Angle(1); float ez = my_eulers.Get_Angle(2); set_eulers(bindex,frame,ex,ey,ez); /* ** Store the visibility bit for this node */ INode * node = tree->Get_Node(tindex); bool vis; if (node) { vis = (node->GetVisibility(frametime) > 0.0f); } else { vis = 1; } set_visibility(bindex,frame,vis); // // Store out binary move or not // bool binary_move = false; if ((node)&&(vis)) { if (frame != 0) { // sample previous frame, and an inbetween time // to determine if there's a binary movement TimeValue frametime_prev = frametime - GetTicksPerFrame(); TimeValue frametime_mid = (frametime + frametime_prev) / 2; // if data at frametime_prev == data at frametime_mid and != data at frametime // then we have a binary movement! Control *c; c = node->GetTMController()->GetPositionController(); if (c) { Interval iValid; Matrix3 smat1; // sample matrix 1 Matrix3 smat2; // sample matrix 2 Matrix3 smat3; // sample matrix 3 iValid = FOREVER; smat1 = node->GetParentTM(frametime_prev); c->GetValue(frametime_prev, &smat1, iValid, CTRL_RELATIVE); iValid = FOREVER; smat2 = node->GetParentTM(frametime_mid); c->GetValue(frametime_mid, &smat2, iValid, CTRL_RELATIVE); iValid = FOREVER; smat3 = node->GetParentTM(frametime); c->GetValue(frametime, &smat3, iValid, CTRL_RELATIVE); if ((smat1 == smat2) && (!(smat2 == smat3))) { binary_move = true; DebugPrint(_T("Binary Move on Translation\n")); } if (false == binary_move) { c = node->GetTMController()->GetRotationController(); if (c) { iValid = FOREVER; smat1 = node->GetParentTM(frametime_prev); c->GetValue(frametime_prev, &smat1, iValid, CTRL_RELATIVE); iValid = FOREVER; smat2 = node->GetParentTM(frametime_mid); c->GetValue(frametime_mid, &smat2, iValid, CTRL_RELATIVE); iValid = FOREVER; smat3 = node->GetParentTM(frametime); c->GetValue(frametime, &smat3, iValid, CTRL_RELATIVE); if ((smat1 == smat2) && (!(smat2 == smat3))) { binary_move = true; DebugPrint(_T("Binary Move on Rotation\n")); } } } } } } set_binary_movement(bindex, frame, binary_move); } // if(bindex!=-1) } /* ** release allocated memory */ delete tree; } /*********************************************************************************************** * MotionClass::set_motion_matrix -- store a motion matrix * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ void MotionClass::set_motion_matrix(int node,int frame,const Matrix3 & motion) { assert(node >= 0); assert(frame >= 0); assert(node < BasePose->Num_Nodes()); assert(frame < NumFrames); MotionMatrix[node][frame] = motion; NodeValidFlags[node] = 1; } /*********************************************************************************************** * MotionClass::get_motion_matrix -- retrieve a motion matrix * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ Matrix3 MotionClass::get_motion_matrix(int node,int frame) { assert(node >= 0); assert(frame >= 0); assert(node < BasePose->Num_Nodes()); assert(frame < NumFrames); return MotionMatrix[node][frame]; } /*********************************************************************************************** * MotionClass::set_eulers -- store euler angles * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ void MotionClass::set_eulers(int node,int frame, float x, float y, float z) { /* ** if we're past the first frame, massage the euler angles to the ** representation closest to the previous frame. */ if (frame > 0) { /* ** First, compute equivalent euler angles */ double x2 = PI + x; double y2 = PI - y; double z2 = PI + z; if (x2 > PI) { x2 = x2 - 2*PI; } if (y2 > PI) { y2 = y2 - 2*PI; } if (z2 > PI) { z2 = z2 - 2*PI; } /* ** load up the previous frame eulers */ double px,py,pz; px = get_eulers(node,frame - 1)[0]; py = get_eulers(node,frame - 1)[1]; pz = get_eulers(node,frame - 1)[2]; // now, pick between the two double mag0 = (x - px) * (x - px) + (y - py) * (y - py) + (z - pz) * (z - pz); double mag1 = (x2 - px) * (x2 - px) + (y2 - py) * (y2 - py) + (z2 - pz) * (z2 - pz); if (mag1 < mag0) { x = x2; y = y2; z = z2; } } EulerDelta[node][frame].x = x; EulerDelta[node][frame].y = y; EulerDelta[node][frame].z = z; NodeValidFlags[node] = 1; } /*********************************************************************************************** * MotionClass::get_eulers -- retrieve euler angles * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ Point3 MotionClass::get_eulers(int node,int frame) { return Point3( EulerDelta[node][frame].x, EulerDelta[node][frame].y, EulerDelta[node][frame].z ); } /*********************************************************************************************** * MotionClass::set_visibility -- store a visibility bit * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/15/98 GTH : Created. * *=============================================================================================*/ void MotionClass::set_visibility(int node,int frame,bool visible) { VisData[node][frame] = visible; NodeValidFlags[node] = 1; } /*********************************************************************************************** * MotionClass::get_visibility -- retrieve the visibility bit for this node:frame * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/15/98 GTH : Created. * *=============================================================================================*/ bool MotionClass::get_visibility(int node,int frame) { return VisData[node][frame]; } /*********************************************************************************************** * MotionClass::set_binary_movement -- store a binary movement bit * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/15/98 GTH : Created. * *=============================================================================================*/ void MotionClass::set_binary_movement(int node,int frame,bool visible) { BinMoveData[node][frame] = visible; //NodeValidFlags[node] = 1; } /*********************************************************************************************** * MotionClass::get_visibility -- retrieve the movement bit for this node:frame * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/15/98 GTH : Created. * *=============================================================================================*/ bool MotionClass::get_binary_movement(int node,int frame) { return BinMoveData[node][frame]; } /*********************************************************************************************** * MotionClass::Save -- save the motion to a W3D file * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ bool MotionClass::Save(ChunkSaveClass & csave) { uint32 chunk_anim_type = W3D_CHUNK_ANIMATION; ExportLog::printf("\nBegin Save Motion Data\n"); if (CompressAnimation) { chunk_anim_type = W3D_CHUNK_COMPRESSED_ANIMATION; } if (!csave.Begin_Chunk( chunk_anim_type )) { return false; } if (!save_header(csave)) { return false; } if (!save_channels(csave)) { return false; } if (!csave.End_Chunk()) { return false; } return true; } /*********************************************************************************************** * MotionClass::save_header -- save the header * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ bool MotionClass::save_header(ChunkSaveClass & csave) { ExportLog::printf("Save Header Type: "); if (CompressAnimation) { // New Compressed Style if (!csave.Begin_Chunk(W3D_CHUNK_COMPRESSED_ANIMATION_HEADER)) { return false; } W3dCompressedAnimHeaderStruct aheader; aheader.Version = W3D_CURRENT_COMPRESSED_HANIM_VERSION; Set_W3D_Name(aheader.Name,Name); Set_W3D_Name(aheader.HierarchyName,BasePose->Get_Name()); aheader.NumFrames = NumFrames; aheader.FrameRate = FrameRate; aheader.Flavor = CompressAnimationFlavor; // for future expansion switch (CompressAnimationFlavor) { case ANIM_FLAVOR_TIMECODED: ExportLog::printf("TimeCoded\n"); break; case ANIM_FLAVOR_ADAPTIVE_DELTA: ExportLog::printf("Adaptive Delta\n"); break; default: ExportLog::printf("UNKNOWN\n"); break; } if (csave.Write(&aheader,sizeof(aheader)) != sizeof(aheader)) { return false; } if (!csave.End_Chunk()) { return false; } } else { ExportLog::printf("Non-Compressed.\n"); // Classic Non-Compressed Style if (!csave.Begin_Chunk(W3D_CHUNK_ANIMATION_HEADER)) { return false; } W3dAnimHeaderStruct aheader; aheader.Version = W3D_CURRENT_HANIM_VERSION; Set_W3D_Name(aheader.Name,Name); Set_W3D_Name(aheader.HierarchyName,BasePose->Get_Name()); aheader.NumFrames = NumFrames; aheader.FrameRate = FrameRate; if (csave.Write(&aheader,sizeof(aheader)) != sizeof(aheader)) { return false; } if (!csave.End_Chunk()) { return false; } } return true; } /*********************************************************************************************** * MotionClass::save_channels -- save the motion channels * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ bool MotionClass::save_channels(ChunkSaveClass & csave) { int NumNodes = BasePose->Num_Nodes(); ExportLog::printf("\nSaving Channel Data for %d Nodes\n", NumNodes); for (int nodeidx = 0; nodeidx < BasePose->Num_Nodes(); nodeidx++) { ExportLog::printf("\nnode: %d ", nodeidx); /* ** Just ignore this node if it didn't appear in the max scene. */ if (NodeValidFlags[nodeidx]) { float identity[] = { 0.0f,0.0f,0.0f,1.0f }; VectorChannelClass xchan (nodeidx, NumFrames, ANIM_CHANNEL_X, 1, identity); VectorChannelClass ychan (nodeidx, NumFrames, ANIM_CHANNEL_Y, 1, identity); VectorChannelClass zchan (nodeidx, NumFrames, ANIM_CHANNEL_Z, 1, identity); VectorChannelClass xrchan(nodeidx, NumFrames, ANIM_CHANNEL_XR, 1, identity); VectorChannelClass yrchan(nodeidx, NumFrames, ANIM_CHANNEL_YR, 1, identity); VectorChannelClass zrchan(nodeidx, NumFrames, ANIM_CHANNEL_ZR, 1, identity); VectorChannelClass qchan (nodeidx, NumFrames, ANIM_CHANNEL_Q, 4, identity); xchan.SetSaveOptions(CompressAnimation, CompressAnimationFlavor, CompressAnimationTranslationError, CompressAnimationRotationError, ReduceAnimation, ReduceAnimationPercent); ychan.SetSaveOptions(CompressAnimation, CompressAnimationFlavor, CompressAnimationTranslationError, CompressAnimationRotationError, ReduceAnimation, ReduceAnimationPercent); zchan.SetSaveOptions(CompressAnimation, CompressAnimationFlavor, CompressAnimationTranslationError, CompressAnimationRotationError, ReduceAnimation, ReduceAnimationPercent); xrchan.SetSaveOptions(CompressAnimation, CompressAnimationFlavor, CompressAnimationTranslationError, CompressAnimationRotationError, ReduceAnimation, ReduceAnimationPercent); yrchan.SetSaveOptions(CompressAnimation, CompressAnimationFlavor, CompressAnimationTranslationError, CompressAnimationRotationError, ReduceAnimation, ReduceAnimationPercent); zrchan.SetSaveOptions(CompressAnimation, CompressAnimationFlavor, CompressAnimationTranslationError, CompressAnimationRotationError, ReduceAnimation, ReduceAnimationPercent); qchan.SetSaveOptions(CompressAnimation, CompressAnimationFlavor, CompressAnimationTranslationError, CompressAnimationRotationError, ReduceAnimation, ReduceAnimationPercent); BitChannelClass vischan(nodeidx, NumFrames, BIT_CHANNEL_VIS, 1); vischan.Set_Bits(VisData[nodeidx]); BitChannelClass binmovechan(nodeidx, NumFrames, 0, 0); binmovechan.Set_Bits(BinMoveData[nodeidx]); for (int frameidx = 0; frameidx < NumFrames; frameidx++) { float vec[4]; Matrix3 tm = get_motion_matrix(nodeidx,frameidx); Point3 eulers = get_eulers(nodeidx,frameidx); Point3 old_tran = tm.GetTrans(); Quat old_rot(tm); Point3 tran; Point3 scale; Quat rot; DecomposeMatrix(tm,tran,rot,scale); /* ** fixup the quaternion - max's quaternions are different than mine(?) */ rot[0] = -rot[0]; rot[1] = -rot[1]; rot[2] = -rot[2]; rot[3] = rot[3]; /* ** Build the x translation channel */ vec[0] = tran.x; xchan.Set_Vector(frameidx,vec); /* ** Build the y translation channel */ vec[0] = tran.y; ychan.Set_Vector(frameidx,vec); /* ** Build the z translation channel */ vec[0] = tran.z; zchan.Set_Vector(frameidx,vec); /* ** Build the x rotation channel */ vec[0] = eulers.x; xrchan.Set_Vector(frameidx,vec); /* ** Build the y rotation channel */ vec[0] = eulers.y; yrchan.Set_Vector(frameidx,vec); /* ** Build the z rotation channel */ vec[0] = eulers.z; zrchan.Set_Vector(frameidx,vec); /* ** Build the quaternion rotation channel */ vec[0] = rot[0]; vec[1] = rot[1]; vec[2] = rot[2]; vec[3] = rot[3]; qchan.Set_Vector(frameidx,vec); /* ** build the visibility channel */ vischan.Set_Bit(frameidx,get_visibility(nodeidx,frameidx)); // // build binarymovement channel // binmovechan.Set_Bit(frameidx, get_binary_movement(nodeidx, frameidx)); } // If objects arn't visible, then the channel data may as well be empty if (!vischan.Is_Empty()) { if (!xchan.Is_Empty()) xchan.ClearInvisibleData(&vischan); if (!ychan.Is_Empty()) ychan.ClearInvisibleData(&vischan); if (!zchan.Is_Empty()) zchan.ClearInvisibleData(&vischan); if (!qchan.Is_Empty()) qchan.ClearInvisibleData(&vischan); } if (!xchan.Is_Empty()) { ExportLog::printf("x"); xchan.Save(csave, &binmovechan ); } if (!ychan.Is_Empty()) { ExportLog::printf("y"); ychan.Save(csave, &binmovechan ); } if (!zchan.Is_Empty()) { ExportLog::printf("z"); zchan.Save(csave, &binmovechan ); } // (gth) not saving Euler angles any more since we don't use them // if (!xrchan.Is_Empty()) xrchan.Save(csave); // if (!yrchan.Is_Empty()) yrchan.Save(csave); // if (!zrchan.Is_Empty()) zrchan.Save(csave); if (!qchan.Is_Empty()) { ExportLog::printf("q"); qchan.Save(csave, &binmovechan); } if (!vischan.Is_Empty()) { ExportLog::printf("v"); vischan.Save(csave, CompressAnimation); } } ExportLog::updatebar(nodeidx ,NumNodes); } ExportLog::updatebar(1,1); ExportLog::printf("\n\nSave Channel Data Complete.\n"); return true; } // EOF - motion.cpp