/* ** 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/hiersave.cpp 56 10/30/00 6:58p Greg_h $ */ /*********************************************************************************************** *** Confidential - Westwood Studios *** *********************************************************************************************** * * * Project Name : Commando / G 3D Engine * * * * $Archive:: /Commando/Code/Tools/max2w3d/hiersave.cpp $* * * * $Author:: Greg_h $* * * * $Modtime:: 10/30/00 6:14p $* * * * $Revision:: 56 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * HierarchySaveClass::HierarchySaveClass -- constructor * * HierarchySaveClass::HierarchySaveClass -- constructor * * HierarchySaveClass::HierarchySaveClass -- constructor * * HierarchySaveClass::~HierarchySaveClass -- destructor * * HierarchySaveClass::Free -- releases all allocated memory * * HierarchySaveClass::Get_Node_Transform -- returns the transformation matrix of specified n* * HierarchySaveClass::get_relative_transform -- retruns tm between this node and its parent * * HierarchySaveClass::Get_Name -- returns the name of this hierarchy * * HierarchySaveClass::Get_Node -- Get the Max INode * * HierarchySaveClass::Get_Node_Name -- returns name of this hierarchy node * * HierarchySaveClass::Find_Named_Node -- returns index of a named node * * HierarchySaveClass::Get_Export_Coordinate_System - find the bone and coordinate system * * HierarchySaveClass::Save -- write the hierarchy into a W3D file * * HierarchySaveClass::Load -- read the hierarchy from a W3D file * * HierarchySaveClass::add_tree -- adds a node and all of its children * * HierarchySaveClass::add_node -- adds a single node to the tree * * HierarchySaveClass::Get_Fixup_Transform -- gets the "fixup" transform for a node * * HierarchySaveClass::fixup_matrix -- conditions a matrix * * HierarchySaveClass::save_header -- writes the header into a W3D file * * HierarchySaveClass::save_pivots -- writes the pivots into a W3D file * * HierarchySaveClass::save_fixups -- writes the fixup transforms into a W3D file * * HierarchySaveClass::load_header -- reads the header from a W3D file * * HierarchySaveClass::load_pivots -- reads the pivots from a W3D file * * HierarchySaveClass::load_fixups -- reads the fixup transforms from a W3D file * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "hiersave.h" #include "w3d_file.h" #include "nodefilt.h" #include "euler.h" #include "util.h" #include "w3dappdata.h" #include "errclass.h" #include "exportlog.h" bool HierarchySaveClass::TerrainModeEnabled = false; /*********************************************************************************************** * HierarchySaveClass::HierarchySaveClass -- constructor * * * * INPUT: * * root - root INode to construct the HTree from * * time - current time in Max, transforms at this time will be used * * treemeter - progress meter * * hname - name for the hierarchy tree * * fixup_type - can be used to force all transforms to be translation only * * fixuptree - htree loaded from a previous export * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ HierarchySaveClass::HierarchySaveClass ( INode * root, TimeValue time, Progress_Meter_Class & treemeter, char * hname, int fixuptype, HierarchySaveClass * fixuptree ) : Node(DEFAULT_NODE_ARRAY_SIZE), CurNode(0), FixupType(fixuptype), FixupTree(fixuptree) { CurNode = 0; CurTime = time; /* ** This code-path is activated when the user has created a custom origin. In this case, we ** need to compute the transform which will make all bones relative to this origin. */ OriginOffsetTransform = Inverse(root->GetNodeTM(CurTime)); /* ** Build our tree from the given tree of nodes */ int rootidx = add_node(NULL,-1); assert(rootidx == 0); add_tree(root,rootidx); HierarchyHeader.Version = W3D_CURRENT_HTREE_VERSION; Set_W3D_Name(HierarchyHeader.Name,hname); HierarchyHeader.NumPivots = CurNode; HierarchyHeader.Center.X = 0.0f; HierarchyHeader.Center.Y = 0.0f; HierarchyHeader.Center.Z = 0.0f; } /*********************************************************************************************** * HierarchySaveClass::HierarchySaveClass -- constructor * * * * INPUT: * * * * rootlist - list of root nodes to add to the htree * * time - current time in Max, transforms at this time will be used * * treemeter - progress meter * * hname - name for the hierarchy tree * * fixup_type - can be used to force all transforms to be translation only * * fixuptree - htree loaded from a previous export * * origin_offset - origin offset transform * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ HierarchySaveClass::HierarchySaveClass ( INodeListClass * rootlist, TimeValue time, Progress_Meter_Class & treemeter, char * hname, int fixuptype, HierarchySaveClass * fixuptree, const Matrix3 & origin_offset ) : Node(DEFAULT_NODE_ARRAY_SIZE), CurNode(0), FixupType(fixuptype), FixupTree(fixuptree), OriginOffsetTransform(origin_offset) { CurNode = 0; CurTime = time; /* ** Build the tree with all leaves of all of the nodes given */ int rootidx = add_node(NULL,-1); assert(rootidx == 0); for (unsigned int i = 0; i < rootlist->Num_Nodes(); i++) { add_tree((*rootlist)[i],rootidx); } HierarchyHeader.Version = W3D_CURRENT_HTREE_VERSION; Set_W3D_Name(HierarchyHeader.Name,hname); HierarchyHeader.NumPivots = CurNode; HierarchyHeader.Center.X = 0.0f; HierarchyHeader.Center.Y = 0.0f; HierarchyHeader.Center.Z = 0.0f; } /*********************************************************************************************** * HierarchySaveClass::HierarchySaveClass -- constructor * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ HierarchySaveClass::HierarchySaveClass(): Node(NULL), CurNode(0), CurTime(0) { } /*********************************************************************************************** * HierarchySaveClass::~HierarchySaveClass -- destructor * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ HierarchySaveClass::~HierarchySaveClass(void) { Free(); } /*********************************************************************************************** * HierarchySaveClass::Free -- releases all allocated memory * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ void HierarchySaveClass::Free(void) { Node.Clear(); } /*********************************************************************************************** * HierarchySaveClass::Get_Node_Transform -- returns the transformation matrix of specified no * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ Matrix3 HierarchySaveClass::Get_Node_Transform(int nodeidx) const { Matrix3 tm(1); int idx = nodeidx; while (idx != -1) { tm = tm * get_relative_transform(idx); idx = Node[idx].Pivot.ParentIdx; } return tm; } /*********************************************************************************************** * HierarchySaveClass::get_relative_transform -- retruns tm between this node and its parent * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ Matrix3 HierarchySaveClass::get_relative_transform(int nodeidx) const { assert(nodeidx >= 0); assert(nodeidx < CurNode); Point3 trans; Quat rot; Matrix3 tm(true); Matrix3 rtm(true); trans.x = Node[nodeidx].Pivot.Translation.X; trans.y = Node[nodeidx].Pivot.Translation.Y; trans.z = Node[nodeidx].Pivot.Translation.Z; // WARNING! I had to fudge the orientation // quaternion (Max's representation seems to // rotate in the opposite sense as mine...) rot[0] = -Node[nodeidx].Pivot.Rotation.Q[0]; rot[1] = -Node[nodeidx].Pivot.Rotation.Q[1]; rot[2] = -Node[nodeidx].Pivot.Rotation.Q[2]; rot[3] = Node[nodeidx].Pivot.Rotation.Q[3]; tm.Translate(trans); rot.MakeMatrix(rtm); tm = rtm * tm; return tm; } /*********************************************************************************************** * HierarchySaveClass::Get_Name -- returns the name of this hierarchy * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ const char * HierarchySaveClass::Get_Name(void) const { return HierarchyHeader.Name; } /*********************************************************************************************** * HierarchySaveClass::Get_Node -- Get the Max INode * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/15/98 GTH : Created. * *=============================================================================================*/ INode * HierarchySaveClass::Get_Node(int node) const { assert(node >= 0); assert(node < CurNode); return Node[node].MaxNode; } /*********************************************************************************************** * HierarchySaveClass::Get_Node_Name -- returns name of this hierarchy node * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ const char * HierarchySaveClass::Get_Node_Name(int node) const { assert(node >= 0); assert(node < CurNode); return Node[node].Pivot.Name; } /*********************************************************************************************** * HierarchySaveClass::Find_Named_Node -- returns index of a named node * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ int HierarchySaveClass::Find_Named_Node(const char * name) const { int match = -1; for (int index=0; indexGetName()); boneidx = Find_Named_Node(name); if (boneidx != -1) { /* ** We found the parent bone! */ done = true; } else if (Is_Origin(pbone)) { /* ** Don't go up past our origin, use this as our bone. */ boneidx = 0; done = true; } else { /* ** Nope, try the next parent */ pbone = pbone->GetParentNode(); assert(pbone != NULL); #if 0 if (pbone == NULL) { /* ** mesh isn't connected to a bone, use the root */ boneidx = 0; pbone = node; done = true; } #endif } } if (set_bone_index != NULL) { *set_bone_index = boneidx; } if (set_bone_node != NULL) { *set_bone_node = pbone; } if (set_transform != NULL) { *set_transform = Get_Fixup_Transform(boneidx) * pbone->GetNodeTM(CurTime); } } /*********************************************************************************************** * HierarchySaveClass::Save -- write the hierarchy into a W3D file * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ bool HierarchySaveClass::Save(ChunkSaveClass & csave) { ExportLog::printf("\nSaving Hierarchy Tree %s.\n",HierarchyHeader.Name); ExportLog::printf("Node Count: %d\n",CurNode); ExportLog::printf("Nodes: \n"); for (int inode = 0; inode < CurNode; inode++) { ExportLog::printf(" %s\n",Node[inode].Pivot.Name); } if (!csave.Begin_Chunk(W3D_CHUNK_HIERARCHY)) { return false; } if (!save_header(csave)) { return false; } if (!save_pivots(csave)) { return false; } if (!save_fixups(csave)) { return false; } if (!csave.End_Chunk()) { return false; } return true; } /*********************************************************************************************** * HierarchySaveClass::Load -- read the hierarchy from a W3D file * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ bool HierarchySaveClass::Load(ChunkLoadClass & cload) { Free(); bool error = false; while (cload.Open_Chunk()) { switch (cload.Cur_Chunk_ID()) { case W3D_CHUNK_HIERARCHY_HEADER: if (!load_header(cload)) error = true; break; case W3D_CHUNK_PIVOTS: if (!load_pivots(cload)) error = true; break; case W3D_CHUNK_PIVOT_FIXUPS: if (!load_fixups(cload)) error = true; break; default: break; } if (!cload.Close_Chunk() || error) { return false; } } CurNode = HierarchyHeader.NumPivots; return true; } /*********************************************************************************************** * HierarchySaveClass::add_tree -- adds a node and all of its children * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ void HierarchySaveClass::add_tree(INode * node,int pidx) { int nextparent; if (node->IsHidden ()) { // if the node is hidden, do not add it but add its children to the current parent. nextparent = pidx; } else if (TerrainModeEnabled && (Is_Normal_Mesh(node) || Is_Null_Object(node))) { // terrain optimization, normal meshes are not allowed to have transforms nextparent = pidx; } else if (!Is_Bone(node)) { // This node isn't a bone, don't add it nextparent = pidx; } else { // Add new pivot! it will be parent of all below it. nextparent = add_node(node,pidx); } // Add all of this nodes children for (int i=0; i < node->NumberOfChildren(); i++) { add_tree(node->GetChildNode(i),nextparent); } } /*********************************************************************************************** * HierarchySaveClass::add_node -- adds a single node to the tree * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ int HierarchySaveClass::add_node(INode * node,int pidx) { /* ** 'grow' the node array if necessary */ if (CurNode >= Node.Length ()) { Node.Resize (Node.Length () + NODE_ARRAY_GROWTH_SIZE); } /* ** setup the pivot */ Node[CurNode].MaxNode = node; Node[CurNode].Pivot.ParentIdx = pidx; if (node) { Set_W3D_Name(Node[CurNode].Pivot.Name,node->GetName()); } else { Set_W3D_Name(Node[CurNode].Pivot.Name,"RootTransform"); } /* ** Now, check if there is a bone with this W3D name already ** if there is, scold the user and bail out */ if (Find_Named_Node(Node[CurNode].Pivot.Name) != -1) { char buf[128]; sprintf(buf,"Bones with duplicate names found!\nDuplicated Name: %s\n",Node[CurNode].Pivot.Name); throw ErrorClass(buf); } /* ** Compute the transformation for this node */ Matrix3 maxnodeTM(1); Matrix3 ournodeTM(1); Matrix3 fixupTM(1); Point3 trans(0,0,0); Quat rot(1); Point3 scale(1,1,1); if (node) { maxnodeTM = node->GetNodeTM(CurTime) * OriginOffsetTransform; } else { maxnodeTM = Matrix3(1); } /* ** If this tree is being "fixed up" the first thing we do ** is to transform Max's nodeTM by the fixup transform. ** This is done when a base pose was created using our own ** types of transforms and we want to apply the same ** changes to this tree. ** ** Note that if FixupType is not "NONE", FixupTree must be NULL, */ assert(!((FixupTree != NULL) && (FixupType != MATRIX_FIXUP_NONE))); if (FixupTree != NULL) { int fi = FixupTree->Find_Named_Node(Node[CurNode].Pivot.Name); if (fi == -1) { char buf[128]; sprintf(buf,"Incompatible Base Pose!\nMissing Bone: %s\n",Node[CurNode].Pivot.Name); throw ErrorClass(buf); } Matrix3 fixup = FixupTree->Get_Fixup_Transform(fi); maxnodeTM = fixup * maxnodeTM; } ournodeTM = fixup_matrix(maxnodeTM); fixupTM = ournodeTM * Inverse(maxnodeTM); /* ** Now, make ournodeTM relative to its parent transform. We ** will always store relative transformations. (Also, note ** that it is relative to our version of the parent transform ** which is not necessarily the same as the MAX version...) */ if (pidx != -1) { Matrix3 parentTM = Get_Node_Transform(pidx); Matrix3 pinv = Inverse(parentTM); ournodeTM = ournodeTM * pinv; } /* ** Break the matrix down into a rotation and translation. */ DecomposeMatrix(ournodeTM,trans,rot,scale); /* ** Save the "fixup" matrix */ for (int j=0;j<4;j++) { Point3 row = fixupTM.GetRow(j); Node[CurNode].Fixup.TM[j][0] = row.x; Node[CurNode].Fixup.TM[j][1] = row.y; Node[CurNode].Fixup.TM[j][2] = row.z; } /* ** Set the translation and rotation for this pivot. */ Node[CurNode].Pivot.Translation.X = trans.x; Node[CurNode].Pivot.Translation.Y = trans.y; Node[CurNode].Pivot.Translation.Z = trans.z; Node[CurNode].Pivot.Rotation.Q[0] = -rot[0]; Node[CurNode].Pivot.Rotation.Q[1] = -rot[1]; Node[CurNode].Pivot.Rotation.Q[2] = -rot[2]; Node[CurNode].Pivot.Rotation.Q[3] = rot[3]; /* ** Compute the Euler angles and set them. */ Matrix3 rotmat; rot.MakeMatrix(rotmat); EulerAnglesClass eangs(rotmat,EulerOrderXYZr); Node[CurNode].Pivot.EulerAngles.X = eangs.Get_Angle(0); Node[CurNode].Pivot.EulerAngles.Y = eangs.Get_Angle(1); Node[CurNode].Pivot.EulerAngles.Z = eangs.Get_Angle(2); return CurNode++; } /*********************************************************************************************** * HierarchySaveClass::Get_Fixup_Transform -- gets the "fixup" transform for a node * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ Matrix3 HierarchySaveClass::Get_Fixup_Transform(int node) const { assert(node >= 0); assert(node < CurNode); Matrix3 m; for (int j=0;j<4;j++) { m.SetRow(j,Point3(Node[node].Fixup.TM[j][0],Node[node].Fixup.TM[j][1],Node[node].Fixup.TM[j][2])); } return m; } /*********************************************************************************************** * HierarchySaveClass::fixup_matrix -- conditions a matrix * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ Matrix3 HierarchySaveClass::fixup_matrix(const Matrix3 & csrc) const { Matrix3 src = csrc; // the GetTrans function is not const correct... Matrix3 newtm(1); Point3 trans; Quat rot; Point3 scale; switch (FixupType) { case MATRIX_FIXUP_NONE: newtm = src; break; case MATRIX_FIXUP_TRANS: newtm.SetTrans(src.GetTrans()); newtm = Cleanup_Orthogonal_Matrix(newtm); break; case MATRIX_FIXUP_TRANS_ROT: DecomposeMatrix(src,trans,rot,scale); rot.MakeMatrix(newtm); newtm.SetTrans(trans); newtm = Cleanup_Orthogonal_Matrix(newtm); break; }; return newtm; } /*********************************************************************************************** * HierarchySaveClass::save_header -- writes the header into a W3D file * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ bool HierarchySaveClass::save_header(ChunkSaveClass & csave) { if (!csave.Begin_Chunk(W3D_CHUNK_HIERARCHY_HEADER)) { return false; } if (csave.Write(&HierarchyHeader,sizeof(HierarchyHeader)) != sizeof(HierarchyHeader)) { return false; } if (!csave.End_Chunk()) { return false; } return true; } /*********************************************************************************************** * HierarchySaveClass::save_pivots -- writes the pivots into a W3D file * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ bool HierarchySaveClass::save_pivots(ChunkSaveClass & csave) { if (!csave.Begin_Chunk(W3D_CHUNK_PIVOTS)) { return false; } for (uint32 i=0; i