/* ** Command & Conquer Renegade(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 . */ #include #include #include "asf_data.h" #include "read_asf.h" #include "exception.h" //---------------------------------------------------------------------------- // ASF_Lexer::ASF_Lexer //---------------------------------------------------------------------------- ASF_Lexer::ASF_Lexer ( const char * file_name ): Input_file ( file_name, "r" ) { Current_char = tolower ( fgetc ( Input_file.fp ) ); advance (); } //---------------------------------------------------------------------------- // ASF_Lexer::skip_whitespace //---------------------------------------------------------------------------- // Skip whitespace and comments, leaving the Current_char pointing to the // first non-whitespace character in the input stream. //---------------------------------------------------------------------------- void ASF_Lexer::skip_whitespace () { while (1) { if ( Current_char == '#' ) // Strip out comments { while ( Current_char != '\n' && Current_char != EOF ) Current_char = tolower ( fgetc ( Input_file.fp ) ); } else if ( (isspace ( Current_char ) && Current_char != '\n') || Current_char == ',' || Current_char == '(' || Current_char == ')' ) { Current_char = tolower ( fgetc ( Input_file.fp ) ); } else { break; } } } //---------------------------------------------------------------------------- // ASF_Lexer::advance //---------------------------------------------------------------------------- // Advance to the next token in the input stream. //---------------------------------------------------------------------------- void ASF_Lexer::advance () { skip_whitespace (); if ( Current_char == EOF ) { Current_type = EOF_MARKER; strcpy ( Current_text, "" ); return; } if ( Current_char == '\n' ) { Current_type = NEWLINE; strcpy ( Current_text, "" ); Current_char = tolower ( fgetc ( Input_file.fp ) ); return; } Current_type = TOKEN; // Read characters into the token text buffer. int i = 0; while ( ! isspace (Current_char) && Current_char != '#' && Current_char != ',' && Current_char != '(' && Current_char != ')' && Current_char != EOF ) { if ( i >= (MAX_TOKEN_LENGTH - 1) ) { // Abort -- maximum token length exceeded. throw Parse_Error ( "Maximum token length exceeded." ); } Current_text [i] = Current_char; ++ i; Current_char = tolower ( fgetc ( Input_file.fp ) ); } Current_text [i] = '\0'; } //---------------------------------------------------------------------------- // Skeleton_Class::Skeleton_Class //---------------------------------------------------------------------------- Skeleton_Class::Skeleton_Class ( const char * file_name, ImpInterface * iface, Interface * gi ) : Angle_multiplier (DEGREES_TO_RADIANS), Length_multiplier (1.0f), First_bone (NULL), Import_interface (iface), Max_interface (gi) { ASF_Lexer lexer ( file_name ); parse_units_block ( lexer ); parse_root_block ( lexer ); parse_bonedata_block ( lexer ); parse_hierarchy_block ( lexer ); } //---------------------------------------------------------------------------- // Skeleton_Class::~Skeleton_Class //---------------------------------------------------------------------------- Skeleton_Class::~Skeleton_Class () { Bone_Class * p = First_bone; while ( p != NULL ) { Bone_Class * delete_p = p; p = p->next_bone (); delete delete_p; } } //---------------------------------------------------------------------------- // Skeleton_Class::parse_units_block //---------------------------------------------------------------------------- void Skeleton_Class::parse_units_block ( ASF_Lexer & lexer ) { skip_unrecognized_blocks ( lexer ); match_token ( lexer, ":units" ); match_newline ( lexer ); BOOL mass_defined = FALSE; BOOL length_defined = FALSE; BOOL angle_defined = FALSE; while (1) { verify_token ( lexer ); if ( strcmp ( lexer.text (), "mass" ) == 0 ) { if ( mass_defined ) throw Parse_Error ( "Multiple mass definitions." ); lexer.advance (); skip_token ( lexer ); // Ignore the mass definition. match_newline ( lexer ); mass_defined = TRUE; } else if ( strcmp ( lexer.text (), "length" ) == 0 ) { if ( length_defined ) throw Parse_Error ( "Multiple length definitions." ); lexer.advance (); verify_token ( lexer ); Length_multiplier = 1.0f / float_token ( lexer ); match_newline ( lexer ); length_defined = TRUE; } else if ( strcmp ( lexer.text (), "angle" ) == 0 ) { if ( angle_defined ) throw Parse_Error ( "Multiple angle definitions." ); lexer.advance (); verify_token ( lexer ); if ( strcmp ( lexer.text (), "deg" ) == 0 ) Angle_multiplier = DEGREES_TO_RADIANS; else if ( strcmp ( lexer.text (), "rad" ) == 0 ) Angle_multiplier = 1.0f; else throw Parse_Error ( "\"deg\" or \"rad\" expected after angle keyword." ); lexer.advance (); match_newline ( lexer ); angle_defined = TRUE; } else { break; } } } //---------------------------------------------------------------------------- // Skeleton_Class::parse_root_block //---------------------------------------------------------------------------- void Skeleton_Class::parse_root_block ( ASF_Lexer & lexer ) { skip_unrecognized_blocks ( lexer ); match_token ( lexer, ":root" ); match_newline ( lexer ); // Create a root bone. Bone_Class * new_bone = new Bone_Class; new_bone->set_name ( "root" ); // The rotation order for orientation offset. match_token ( lexer, "axis" ); skip_token ( lexer ); // &&& get rotation order for orientation offset. match_newline ( lexer ); // The order of transformations for root. match_token ( lexer, "order" ); for ( int i = 0; i < 6; ++ i ) { verify_token ( lexer ); if ( strcmp ( lexer.text (), "rx" ) == 0 ) new_bone->add_axis ( ROTATE_X ); else if ( strcmp ( lexer.text (), "ry" ) == 0 ) new_bone->add_axis ( ROTATE_Y ); else if ( strcmp ( lexer.text (), "rz" ) == 0 ) new_bone->add_axis ( ROTATE_Z ); else if ( strcmp ( lexer.text (), "tx" ) == 0 ) new_bone->add_axis ( TRANSLATE_X ); else if ( strcmp ( lexer.text (), "ty" ) == 0 ) new_bone->add_axis ( TRANSLATE_Y ); else if ( strcmp ( lexer.text (), "tz" ) == 0 ) new_bone->add_axis ( TRANSLATE_Z ); else throw Parse_Error ( "Unrecognized order token in :root." ); lexer.advance (); } match_newline ( lexer ); // Translation data for root node. match_token ( lexer, "position" ); skip_token ( lexer ); // &&& skip_token ( lexer ); // &&& skip_token ( lexer ); // &&& match_newline ( lexer ); // Rotation data to orient the skeleton. match_token ( lexer, "orientation" ); float rot0 = float_token ( lexer ) * Angle_multiplier; float rot1 = float_token ( lexer ) * Angle_multiplier; float rot2 = float_token ( lexer ) * Angle_multiplier; match_newline ( lexer ); Matrix3 axis_tm; axis_tm.IdentityMatrix (); axis_tm.RotateX ( rot0 ); axis_tm.RotateY ( rot1 ); axis_tm.RotateZ ( rot2 ); new_bone->set_axis_tm ( axis_tm ); new_bone->set_direction ( Point3 (0,0,0) ); new_bone->set_length ( 0.0f ); add_bone_to_list ( new_bone ); new_bone->create_node ( Import_interface, Max_interface ); // Add an AppData chunk to the root node to indicate how much to scale // its position keys. Position_Key_Scale_Chunk * data_p = (Position_Key_Scale_Chunk *) malloc ( sizeof (Position_Key_Scale_Chunk) ); data_p->Position_Key_Scale = Length_multiplier; new_bone->add_app_data ( 2, data_p, sizeof (Position_Key_Scale_Chunk) ); } //---------------------------------------------------------------------------- // Skeleton_Class::parse_bonedata_block //---------------------------------------------------------------------------- void Skeleton_Class::parse_bonedata_block ( ASF_Lexer & lexer ) { skip_unrecognized_blocks ( lexer ); match_token ( lexer, ":bonedata" ); match_newline ( lexer ); // Parse bone definition blocks. while (1) { verify_token ( lexer ); if ( strcmp ( lexer.text (), "begin" ) == 0 ) { parse_bone ( lexer ); } else { break; } } } //---------------------------------------------------------------------------- // Skeleton_Class::parse_hierarchy_block //---------------------------------------------------------------------------- void Skeleton_Class::parse_hierarchy_block ( ASF_Lexer & lexer ) { skip_unrecognized_blocks ( lexer ); match_token ( lexer, ":hierarchy" ); match_newline ( lexer ); match_token ( lexer, "begin" ); match_newline ( lexer ); while (1) { verify_token ( lexer ); if ( strcmp ( lexer.text (), "end" ) == 0 ) { break; } else { parse_hierarchy_line ( lexer ); } } lexer.advance (); match_newline ( lexer ); } //---------------------------------------------------------------------------- // Skeleton_Class::parse_hierarchy_line //---------------------------------------------------------------------------- void Skeleton_Class::parse_hierarchy_line ( ASF_Lexer & lexer ) { verify_token ( lexer ); Bone_Class * parent_bone = find_bone ( lexer.text () ); if ( parent_bone == NULL ) throw Parse_Error ( "Undefined parent bone." ); lexer.advance (); while ( lexer.type () == TOKEN ) { Bone_Class * child_bone = find_bone ( lexer.text () ); if ( child_bone == NULL ) throw Parse_Error ( "Undefined child bone." ); child_bone->set_parent ( parent_bone ); lexer.advance (); } match_newline ( lexer ); } //---------------------------------------------------------------------------- // Skeleton_Class::find_bone //---------------------------------------------------------------------------- Bone_Class * Skeleton_Class::find_bone ( const char * name ) { Bone_Class * p = First_bone; while ( p != NULL ) { if ( strcmp ( name, p->name () ) == 0 ) break; p = p->next_bone (); } return p; } //---------------------------------------------------------------------------- // lh_to_rh //---------------------------------------------------------------------------- static Point3 lh_to_rh ( Point3 point ) { Point3 new_point; new_point.x = point.x; new_point.y = -point.z; new_point.z = point.y; return new_point; } //---------------------------------------------------------------------------- // Skeleton_Class::parse_bone //---------------------------------------------------------------------------- void Skeleton_Class::parse_bone ( ASF_Lexer & lexer ) { match_token ( lexer, "begin" ); match_newline ( lexer ); // Create a new bone object. Bone_Class * new_bone = new Bone_Class; // Optional ID number. verify_token ( lexer ); if ( strcmp ( lexer.text (), "id" ) == 0 ) { lexer.advance (); skip_token ( lexer ); // Ignore the bone ID number for now. match_newline ( lexer ); } // Name. match_token ( lexer, "name" ); verify_token ( lexer ); new_bone->set_name ( lexer.text () ); lexer.advance (); match_newline ( lexer ); // Direction vector. match_token ( lexer, "direction" ); Point3 direction; direction.x = float_token ( lexer ); direction.y = float_token ( lexer ); direction.z = float_token ( lexer ); new_bone->set_direction ( direction ); match_newline ( lexer ); // Length. match_token ( lexer, "length" ); new_bone->set_length ( float_token ( lexer ) * Length_multiplier / (float)GetMasterScale(UNITS_INCHES)); match_newline ( lexer ); // Rotation axis in world coordinates, with order of rotations. match_token ( lexer, "axis" ); float rot0 = float_token ( lexer ) * Angle_multiplier; float rot1 = float_token ( lexer ) * Angle_multiplier; float rot2 = float_token ( lexer ) * Angle_multiplier; // &&& Ultimately this should handle any order of rotations. match_token ( lexer, "xyz" ); match_newline ( lexer ); Matrix3 axis_tm; axis_tm.IdentityMatrix (); axis_tm.RotateX ( rot0 ); axis_tm.RotateY ( rot1 ); axis_tm.RotateZ ( rot2 ); new_bone->set_axis_tm ( axis_tm ); // Optional mass of skinbody associated with this bone. verify_token ( lexer ); if ( strcmp ( lexer.text (), "bodymass" ) == 0 ) { lexer.advance (); skip_token ( lexer ); // Ignore the bodymass. match_newline ( lexer ); } // Optional position of center of mass along the bone. verify_token ( lexer ); if ( strcmp ( lexer.text (), "cofmass" ) == 0 ) { lexer.advance (); skip_token ( lexer ); // Ignore the center of mass position. match_newline ( lexer ); } // Optional degrees of freedom. verify_token ( lexer ); if ( strcmp ( lexer.text (), "dof" ) == 0 ) { lexer.advance (); if ( lexer.type () == TOKEN && strcmp ( lexer.text (), "rx" ) == 0 ) { new_bone->add_axis ( ROTATE_X ); lexer.advance (); } if ( lexer.type () == TOKEN && strcmp ( lexer.text (), "ry" ) == 0 ) { new_bone->add_axis ( ROTATE_Y ); lexer.advance (); } if ( lexer.type () == TOKEN && strcmp ( lexer.text (), "rz" ) == 0 ) { new_bone->add_axis ( ROTATE_Z ); lexer.advance (); } if ( lexer.type () == TOKEN && strcmp ( lexer.text (), "l" ) == 0 ) { new_bone->add_axis ( TRANSLATE_LENGTH ); lexer.advance (); } match_newline ( lexer ); // Limits for the given degrees of freedom. match_token ( lexer, "limits" ); // &&& while ( strcmp ( lexer.text (), "end" ) != 0 ) lexer.advance (); } match_token ( lexer, "end" ); match_newline ( lexer ); // Add the bone to the list of bones. add_bone_to_list ( new_bone ); new_bone->create_node ( Import_interface, Max_interface ); } //---------------------------------------------------------------------------- // Skeleton_Class::skip_unrecognized_blocks //---------------------------------------------------------------------------- // It is assumed that the current lexeme is the first on a line. //---------------------------------------------------------------------------- void Skeleton_Class::skip_unrecognized_blocks ( ASF_Lexer & lexer ) { while (1) { if ( lexer.type () == EOF_MARKER ) break; if ( strcmp ( lexer.text (), ":units" ) == 0 ) break; if ( strcmp ( lexer.text (), ":root" ) == 0 ) break; if ( strcmp ( lexer.text (), ":bonedata" ) == 0 ) break; if ( strcmp ( lexer.text (), ":hierarchy" ) == 0 ) break; skip_to_next_line ( lexer ); } } //---------------------------------------------------------------------------- // Skeleton_Class::skip_to_next_line //---------------------------------------------------------------------------- void Skeleton_Class::skip_to_next_line ( ASF_Lexer & lexer ) { while ( lexer.type () != NEWLINE && lexer.type () != EOF_MARKER ) lexer.advance (); if ( lexer.type () == NEWLINE ) lexer.advance (); } //---------------------------------------------------------------------------- // Skeleton_Class::match_newline //---------------------------------------------------------------------------- void Skeleton_Class::match_newline ( ASF_Lexer & lexer ) { char message_buffer [ 512 ]; if ( lexer.type () != NEWLINE ) { sprintf ( message_buffer, "Expected newline; found \"%s\" instead.", lexer.text () ); throw Parse_Error ( message_buffer ); } lexer.advance (); } //---------------------------------------------------------------------------- // Skeleton_Class::verify_token //---------------------------------------------------------------------------- void Skeleton_Class::verify_token ( ASF_Lexer & lexer ) { char message_buffer [ 512 ]; if ( lexer.type () != TOKEN ) { sprintf ( message_buffer, "Expected token; found \"%s\" instead.", lexer.text () ); throw Parse_Error ( message_buffer ); } } //---------------------------------------------------------------------------- // Skeleton_Class::skip_token //---------------------------------------------------------------------------- void Skeleton_Class::skip_token ( ASF_Lexer & lexer ) { verify_token ( lexer ); lexer.advance (); } //---------------------------------------------------------------------------- // Skeleton_Class::float_token //---------------------------------------------------------------------------- float Skeleton_Class::float_token ( ASF_Lexer & lexer ) { verify_token ( lexer ); float value = (float) strtod ( lexer.text (), NULL ); lexer.advance (); return value; } //---------------------------------------------------------------------------- // Skeleton_Class::match_token //---------------------------------------------------------------------------- void Skeleton_Class::match_token ( ASF_Lexer & lexer, const char * token_text ) { char message_buffer [ 512 ]; verify_token ( lexer ); if ( strcmp ( lexer.text (), token_text ) != 0 ) { sprintf ( message_buffer, "Expected \"%s\"; found \"%s\" instead.", token_text, lexer.text () ); throw Parse_Error ( message_buffer ); } lexer.advance (); } //---------------------------------------------------------------------------- // Skeleton_Class::add_bone_to_list //---------------------------------------------------------------------------- void Skeleton_Class::add_bone_to_list ( Bone_Class * new_bone ) { new_bone->set_next_bone ( First_bone ); First_bone = new_bone; } //---------------------------------------------------------------------------- // Bone_Class::create_node //---------------------------------------------------------------------------- void Bone_Class::create_node ( ImpInterface * import_interface, Interface * max_interface ) { Node = import_interface->CreateNode (); Node->SetName ( Name ); // Create a box object. GeomObject * obj = (GeomObject *) max_interface->CreateInstance ( GEOMOBJECT_CLASS_ID, Class_ID (BOXOBJ_CLASS_ID, 0) ); IParamArray *iBoxParams = obj->GetParamBlock(); assert(iBoxParams); // Set the value of width, height and length. int width_index = obj->GetParamBlockIndex(BOXOBJ_WIDTH); assert(width_index >= 0); iBoxParams->SetValue(width_index,TimeValue(0),Length / 4); int height_index = obj->GetParamBlockIndex(BOXOBJ_HEIGHT); assert(height_index >= 0); iBoxParams->SetValue(height_index,TimeValue(0),Length); int length_index = obj->GetParamBlockIndex(BOXOBJ_LENGTH); assert(length_index >= 0); iBoxParams->SetValue(length_index,TimeValue(0),Length / 4); Node->Reference ( (Object *) obj ); Node->GetINode ()->SetNodeTM ( 0, Axis_tm ); // Add the node to the scene. import_interface->AddNodeToScene ( Node ); // Add an AppData chunk to the inode to indicate which transformation // axes are active. ASF_Data_Chunk * data_p = (ASF_Data_Chunk *) malloc ( sizeof (ASF_Data_Chunk) ); *data_p = Active_axes; add_app_data ( 1, data_p, sizeof (ASF_Data_Chunk) ); } //---------------------------------------------------------------------------- // Bone_Class::set_parent //---------------------------------------------------------------------------- void Bone_Class::set_parent ( Bone_Class * parent_bone ) { Parent_bone = parent_bone; parent_bone->Node->GetINode ()->AttachChild ( Node->GetINode () ); // Build the child node's transform matrix. Matrix3 parent_tm = Node->GetINode ()->GetParentTM ( 0 ); Matrix3 child_tm = Axis_tm; child_tm.Translate ( Normalize (parent_bone->Direction) * parent_bone->Length ); child_tm.Translate ( parent_tm.GetTrans () ); Node->GetINode ()->SetNodeTM ( 0, child_tm ); } //---------------------------------------------------------------------------- // Bone_Class::add_app_data //---------------------------------------------------------------------------- void Bone_Class::add_app_data ( int chunk_id, void * data, int data_size ) { Node->GetINode ()->AddAppDataChunk ( Class_ID(0x74975aa6, 0x1810323f), SCENE_IMPORT_CLASS_ID, chunk_id, data_size, data ); }