623 lines
23 KiB
C++
623 lines
23 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/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:
|
||
|
// <name>.<exten character><exten digits>
|
||
|
// 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;
|
||
|
}
|
||
|
|