/* ** 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/util.cpp 28 10/27/00 4:12p Greg_h $ */ /*********************************************************************************************** *** Confidential - Westwood Studios *** *********************************************************************************************** * * * Project Name : Commando Tools - W3D export * * * * $Archive:: /Commando/Code/Tools/max2w3d/util.cpp $* * * * $Author:: Greg_h $* * * * $Modtime:: 10/27/00 1:13p $* * * * $Revision:: 28 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * Cleanup_Orthogonal_Matrix -- removes very small numbers from the matrix * * Set_W3D_Name -- set a W3D name * * Split_Node_Name -- break a node name into the base and extension * * Is_Max_Tri_Mesh -- Is this node a triangle mesh? * * -- checks if the node is the origin of a model * * -- Checks if the node is the origin for the base obect (non-LOD'd). * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "util.h" #include "w3dutil.h" #include "skin.h" #include "skindata.h" #include "modstack.h" #define MAX_NODE_NAME_LEN 256 // max name size we can handle const float EPSILON = 0.00001f; static char _string[256]; static int get_geometry_type(INode * node) { assert(node != NULL); return W3DAppData2Struct::Get_App_Data(node)->Get_Geometry_Type(); //return (get_w3d_bits(node) & GEO_TYPE_MASK); } /*********************************************************************************************** * Cleanup_Orthogonal_Matrix -- removes very small numbers from the matrix * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ Matrix3 Cleanup_Orthogonal_Matrix(Matrix3 & mat) { Matrix3 newmat = mat; for (int j=0; j<3; j++) { Point3 row = newmat.GetRow(j); if (fabs(row.x) < EPSILON) row.x = 0.0f; if (fabs(row.y) < EPSILON) row.y = 0.0f; if (fabs(row.z) < EPSILON) row.z = 0.0f; row = Normalize(row); newmat.SetRow(j,row); } return newmat; } /*********************************************************************************************** * Set_W3D_Name -- set a W3D name * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * * 9/13/1999 AJA : Strip off the trailing ".digits" since this is a convention we've set in * * MAX to help artists manage LODs. * *=============================================================================================*/ void Set_W3D_Name(char * set_name,const char * src) { memset(set_name,0,W3D_NAME_LEN); strncpy(set_name,src,W3D_NAME_LEN-1); char *dot = strrchr(set_name, '.'); if (dot) { // If a number comes after the dot, strip it off int value; if (sscanf(dot+1, "%d", &value) == 1) *dot = 0; // If nothing comes after the dot, strip it off else if (*(dot+1) == 0) *dot = 0; } strupr(set_name); } /*********************************************************************************************** * Split_Node_Name -- break a node name into the base and extension * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * *=============================================================================================*/ void Split_Node_Name(const char * name,char * set_base,char * set_exten,int * set_exten_index) { // Nodes are assumed to be named in the following way: // . // for example: mesh.d1 char buf[MAX_NODE_NAME_LEN]; char * ptr; assert(strlen(name) < MAX_NODE_NAME_LEN); // Initialize if (set_base != NULL) set_base[0] = 0; if (set_exten != NULL) set_exten[0] = 0; if (set_exten_index != NULL) *set_exten_index = 0; // Get the base name strncpy(buf,name,MAX_NODE_NAME_LEN); ptr = buf; while ((*ptr != 0) && (*ptr != '.')) { ptr++; } if (*ptr == '.') { // copy what we have so far into set_base *ptr = 0; if (set_base != NULL) strncpy(set_base,buf,MAX_NODE_NAME_LEN); // copy the rest back into the extension ptr++; if (set_exten != NULL) strncpy(set_exten,ptr,MAX_NODE_NAME_LEN); // now get the extension index ptr++; if (set_exten_index != NULL) *set_exten_index = atoi(ptr); } else { // no extension, just copy the base name if (set_base != NULL) strncpy(set_base,buf,MAX_NODE_NAME_LEN); return; } } bool Append_Lod_Character (char *meshname, int lod_level, INodeListClass *origin_list) { if (meshname == NULL || lod_level < 0) return false; if (!origin_list) return false; int num_lods = origin_list->Num_Nodes(); /* ** Search the other LODs to see if there is a mesh with the same name. ** If there is, we will append the current LOD level digit to the name. ** If there is not, the name will not be modified. */ INode *conflict = NULL, *cur_origin = NULL; int i, lod; for (i = 0; i < num_lods; i++) { // Don't bother searching the current LOD. lod = Get_Lod_Level((*origin_list)[i]); if (lod == lod_level) continue; // Search this lod for a node of the same name. conflict = Find_Named_Node(meshname, (*origin_list)[i]); if (conflict) { // Name length is a worry here, because the name plus the number // must be less than W3D_NAME_LEN (which is fairly small!). int length = strlen(meshname); if ( (lod_level < 10) && (length < W3D_NAME_LEN - 1) ) { // Append a number corresponding to the LOD level to the mesh name. // Highest-detail LOD is '0' (convention). char *insert = meshname + length; *insert++ = '0' + lod_level; *insert = '\0'; } else if ( (lod_level < 100) && (length < W3D_NAME_LEN - 2) ) { // Append a number corresponding to the LOD level to the mesh name. // Highest-detail LOD is '0' (convention). char buf[3]; sprintf(buf, "%d", lod_level); strcat(meshname, buf); } else { // Replace the last character of the mesh name with the lod character (as above). meshname[W3D_NAME_LEN-2] = '0' + lod_level; } // Name mangling finished (conflict with other LODs is solved on their pass). break; } } return true; } void Create_Full_Path(char *full_path, const char *curr, const char *rel_path) { // Copy current dir to full path. If it doesn't end with a slash, add one. strcpy(full_path, curr); int curr_len = strlen(curr); char *full_p = full_path + curr_len; // Point at the terminating NULL if (curr_len == 0 ||(*(full_p - 1) != '/' && *(full_p - 1) != '\\')) { *full_p = '\\'; *(++full_p) = '\000'; // Point at the terminating NULL } // Scan "..\"s at the beginning of the rel path, scan backwards on the // full path (current dir): const char *rel_p; for ( rel_p = rel_path; *rel_p == '.' && *(rel_p+1) == '.' && ( *(rel_p+2) == '/' || *(rel_p+2) == '\\' ); rel_p += 3) { full_p--; for (; full_p > full_path && *(full_p-1) != '/' && *(full_p-1) != '\\'; full_p--); *full_p = '\000'; } // Copy the remainder of the relative path to the full path: strcpy(full_p, rel_p); } // This enum is used inside Create_Relative_Path: enum PathCharType { NULL_CHAR, SLASH_CHAR, PLAIN_CHAR }; void Create_Relative_Path(char *rel_path, const char *curr, const char *full_path) { // Copy both constant strings and convert them to uppercase: int curr_len = strlen(curr); char *up_curr = (char *)malloc(curr_len + 1); strcpy(up_curr, curr); _strupr(up_curr); int full_len = strlen(full_path); char *up_full = (char *)malloc(full_len + 1); strcpy(up_full, full_path); _strupr(up_full); char *rel_p = rel_path; // Find shared prefix of curr and full path const char *full_p = up_full; const char *curr_p = up_curr; for ( ; *full_p && *full_p == *curr_p || (*full_p == '/' && *curr_p == '\\') || (*full_p == '\\' && *curr_p == '/'); full_p++, curr_p++ ); // If no shared prefix at this point set the relative path to 0 // This will force the code to use the absolute path. if (full_p == up_full) { rel_path[0] = 0; goto end; } // The first different character for each string can be: a NULL, a slash, // or an ordinary character. PathCharType full_type, curr_type; if (*full_p == '\000') { full_type = NULL_CHAR; } else { if (*full_p == '/' || *full_p == '\\') { full_type = SLASH_CHAR; } else { full_type = PLAIN_CHAR; } } if (*curr_p == '\000') { curr_type = NULL_CHAR; } else { if (*curr_p == '/' || *curr_p == '\\') { curr_type = SLASH_CHAR; } else { curr_type = PLAIN_CHAR; } } // If the last fullpath char is a NULL or both are slashes, we have an // error - return full path if (full_type == NULL_CHAR || (full_type == SLASH_CHAR && curr_type == SLASH_CHAR)) { strcpy(rel_path, up_full); goto end; } // If the current path has ended (last char is a NULL) and the full path's // last char is a slash, then just copy the remainder of the full path // (w/o the slash) to the relative path, and exit. if (curr_type == NULL_CHAR && full_type == SLASH_CHAR) { full_p++; // skip slash strcpy(rel_path, full_p); goto end; } // If one of following holds: // 1) One of the last chars is a slash and the other is a plain char // 2) The current path has ended (last char is NULL) and the last char // of the full path is a plain char // 3) The last char of both are plain chars and the previous char is not a // slash // Then we must backtrack both pointers until the characters before them // are slashes. If there are no previous slashes, we have an error. if ( (full_type == SLASH_CHAR && curr_type == PLAIN_CHAR) || (curr_type == SLASH_CHAR && full_type == PLAIN_CHAR) || (curr_type == NULL_CHAR && full_type == PLAIN_CHAR) || (curr_type == PLAIN_CHAR && full_type == PLAIN_CHAR && *(full_p-1) != '/' && *(full_p-1) != '\\') ) { for (; full_p > up_full && ( (*(full_p - 1) != '/' && *(full_p - 1) != '\\') || (*(curr_p - 1) != '/' && *(curr_p - 1) != '\\') ); full_p--, curr_p--); } // If no shared prefix at this point (not even a drive letter) return the // full path if (full_p == up_full) { strcpy(rel_path, up_full); goto end; } // Scan all directories levels in current path from shared point to end - // for each one add a "../" to the relative path. Note that at this point // we know we have to add at least one. *rel_p++ = '.'; *rel_p++ = '.'; *rel_p++ = '\\'; // Go over remaining current path, for each slash we find add one "../" to // the relative path for (; *curr_p; curr_p++) { if (*curr_p == '/' || *curr_p == '\\') { *rel_p++ = '.'; *rel_p++ = '.'; *rel_p++ = '\\'; } } // If the last char of the current path is a slash remove a "../" from the // relative path. if (*(curr_p - 1) == '/' || *(curr_p - 1) == '\\') { rel_p -= 3; } // Copy remaining full path (from shared point to end) to relative path strcpy(rel_p, full_p); end: free(up_curr); free(up_full); } bool Is_Full_Path(char * path) { // first scan for a drive letter (scan for a colon) if (strchr(path,':') != NULL) { return true; } // now scan for a "network" path (starts with "//") if ((path[0] == '/') && (path[1] == '/')) { return true; } if ((path[0] == '\\') && (path[1] == '\\')) { return true; } return false; } /*********************************************************************************************** * Is_Max_Tri_Mesh -- Is this node a triangle mesh? * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 2/2/98 GTH : Created. * *=============================================================================================*/ bool Is_Max_Tri_Mesh(INode * node) { Object *obj = node->EvalWorldState(0).obj; if (obj && obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0))) { return true; } return false; } bool Is_Damage_Root(INode *node) { if (node == NULL) return false; // Is the node's parent the scene root? INode *parent = node->GetParentNode(); if (!parent || !parent->IsRootNode()) return false; // Is the node's name in the form "damage.*"? char *name = node->GetName(); if (strnicmp(name, "damage.", strlen("damage.")) != 0) return false; /* This won't pick up references to a dummy object for some reason. // Does the node point to a dummy object? Object *obj = node->GetObjectRef(); if (!obj || !obj->CanConvertToType(Class_ID(DUMMY_CLASS_ID, 0))) return false; */ return true; } /*********************************************************************************************** * Is_Origin -- checks if the node is the origin of a model * * * * A node is an origin if its parent is the scene root, it is a dummy object, and its name * * is of the form "origin.*" (case insensitive). All descendants of the origin will be * * expressed in coordinates relative to the origin object. * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 9/13/99 AJA : Created. *=============================================================================================*/ bool Is_Origin(INode * node) { if (!node) return false; if (node->IsRootNode()) return true; if (node->IsHidden()) return false; // Is the node's parent the scene root? INode *parent = node->GetParentNode(); if (!parent || !parent->IsRootNode()) return false; // Is the node's name in the form "origin.*"? char *name = node->GetName(); if (strnicmp(name, "origin.", strlen("origin.")) != 0) return false; /* This won't pick up references to a dummy object for some reason. // Does the node point to a dummy object? Object *obj = node->GetObjectRef(); if (!obj || !obj->CanConvertToType(Class_ID(DUMMY_CLASS_ID, 0))) return false; */ // This is an origin. return true; } /*********************************************************************************************** * Is_Base_Origin -- Checks if the node is the origin for the base obect (non-LOD'd). * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 9/13/1999 AJA : Created. * *=============================================================================================*/ bool Is_Base_Origin(INode * node) { if (!node) return false; if (node->IsRootNode()) return true; if (!Is_Origin(node)) return false; // An origin is the base object origin if it's name is "origin." or // "origin.0" (a numeric value evaluating to zero as scanned by sscanf // which would include "origin.00" "origin.000", etc.). bool is_base_origin = false; char *name = node->GetName(); if (stricmp(name, "origin.") == 0) is_base_origin = true; else if (strlen(name) > strlen("origin.")) { // We know the first 7 characters are "origin." because that // was tested in Is_Origin(). Is it "origin.0"? int idx; if ((sscanf(name+strlen("origin."), "%d", &idx) == 1) && (idx == 0)) is_base_origin = true; } return is_base_origin; } int Get_Lod_Level(INode *node) { if (node->IsRootNode()) return 0; if (!Is_Origin(node)) return -1; char *name = node->GetName(); char *dot = strrchr(name, '.'); assert(dot); return atoi(dot+1); } int Get_Damage_State(INode *node) { if (!Is_Damage_Root(node)) return -1; char *name = node->GetName(); char *dot = strrchr(name, '.'); assert(dot); return atoi(dot+1); } INode *Find_Named_Node(char *nodename, INode *root) { if (!root || !nodename) return NULL; // Perform a breadth-first search of the tree for a node // of the given name. INode *child = NULL; int i; char cur_name[W3D_NAME_LEN]; // Is this the node we're looking for? Set_W3D_Name(cur_name, root->GetName()); if (strcmp(cur_name, nodename) == 0) return root; // Check the children against the given name. for (i = 0; i < root->NumChildren(); i++) { // Is it this child? child = root->GetChildNode(i); Set_W3D_Name(cur_name, child->GetName()); if (strcmp(nodename, cur_name) == 0) return child; } // Wasn't any children. Check each child's descendants. for (i = 0; i < root->NumChildren(); i++) { child = root->GetChildNode(i); INode *found = Find_Named_Node(nodename, child); if (found) return found; } // Didn't find the node anywhere. return NULL; }