/*
** 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
);
}