1028 lines
41 KiB
C++
1028 lines
41 KiB
C++
/*
|
|
** 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* $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; i<BasePose->Num_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; j<NumFrames; j++) {
|
|
MotionMatrix[i][j] = Matrix3(1);
|
|
}
|
|
}
|
|
|
|
for (i=0; i<BasePose->Num_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; j<NumFrames; j++) {
|
|
EulerDelta[i][j] = Point3(0,0,0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** allocate boolean vectors for the visiblity data
|
|
*/
|
|
VisData = new BooleanVectorClass[BasePose->Num_Nodes()];
|
|
|
|
for (i=0; i<BasePose->Num_Nodes(); i++) {
|
|
VisData[i].Resize(NumFrames);
|
|
|
|
/*
|
|
** initialize to always visible
|
|
*/
|
|
for (j=0; j<NumFrames; j++) {
|
|
VisData[i][j] = true;
|
|
}
|
|
}
|
|
|
|
//
|
|
// allocate boolean vectors for movement data
|
|
//
|
|
BinMoveData = new BooleanVectorClass[BasePose->Num_Nodes()];
|
|
|
|
for (i=0; i<BasePose->Num_Nodes(); i++) {
|
|
BinMoveData[i].Resize(NumFrames);
|
|
|
|
/*
|
|
** initialize to always interpolate
|
|
*/
|
|
for (j=0; j<NumFrames; j++) {
|
|
BinMoveData[i][j] = false;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
** Allocate a bit for each node in the base pose. These
|
|
** bits indicate whether the node actually appeared in the
|
|
** scene. If the bit is zero after all of the animation
|
|
** has been processed, the node can be ignored.
|
|
*/
|
|
NodeValidFlags.Resize(BasePose->Num_Nodes());
|
|
|
|
for (i=0; i<BasePose->Num_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; i<BasePose->Num_Nodes(); i++) {
|
|
if (MotionMatrix[i]) delete[] MotionMatrix[i];
|
|
}
|
|
if (MotionMatrix) {
|
|
delete[] MotionMatrix;
|
|
}
|
|
|
|
for (i=0; i<BasePose->Num_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; tindex<tree->Num_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
|
|
|
|
|