/*
** 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 .
*/
//----------------------------------------------------------------------------
// amc_import.cpp
//
// Acclaim Motion Capture import module
//
// James McNeill
//
// Created October 1996
//
// Copyright (c) 1996 Westwood Studios
//----------------------------------------------------------------------------
#include
#include
#include
#include "resource.h"
#include "exception.h"
#include "asf_data.h"
const float ANGLE_MULTIPLIER = 1.7453293e-2f; // degrees to radians
const first_frame_time = 320;
const amc_ticks_per_frame = 80;
static int max_ticks_per_frame = 160;
static HINSTANCE hInstance;
static TCHAR * GetString ( int id )
{
static TCHAR buf[256];
if (hInstance)
return LoadString(hInstance, id, buf, sizeof(buf)) ? buf : NULL;
return NULL;
}
static int MessageBox ( int s1, int s2, int option = MB_OK )
{
TSTR str1(GetString(s1));
TSTR str2(GetString(s2));
return MessageBox(GetActiveWindow(), str1, str2, option);
}
static int Alert ( int s1, int s2 = IDS_LIB_SHORT_DESC, int option = MB_OK )
{
return MessageBox(s1, s2, option);
}
//----------------------------------------------------------------------------
// AMC_Import
//----------------------------------------------------------------------------
class AMC_Import : public SceneImport
{
public:
AMC_Import();
~AMC_Import();
int ExtCount(); // Number of extensions supported
const TCHAR * Ext(int n); // Extension #n
const TCHAR * LongDesc(); // Long ASCII description
const TCHAR * ShortDesc(); // Short ASCII description
const TCHAR * AuthorName(); // ASCII Author name
const TCHAR * CopyrightMessage(); // ASCII Copyright message
const TCHAR * OtherMessage1(); // Other message #1
const TCHAR * OtherMessage2(); // Other message #2
unsigned int Version(); // Version number * 100
void ShowAbout(HWND); // Show DLL's "About..." box
int DoImport
(
const TCHAR * name,
ImpInterface * i,
Interface * gi,
BOOL suppressPrompts
);
};
//----------------------------------------------------------------------------
// DllMain
//----------------------------------------------------------------------------
static int controlsInit = FALSE;
BOOL WINAPI DllMain
(
HINSTANCE hinstDLL,
ULONG fdwReason,
LPVOID lpvReserved
)
{
hInstance = hinstDLL;
if ( !controlsInit )
{
controlsInit = TRUE;
InitCustomControls(hInstance);
InitCommonControls();
}
switch(fdwReason)
{
case DLL_PROCESS_ATTACH: break;
case DLL_THREAD_ATTACH: break;
case DLL_THREAD_DETACH: break;
case DLL_PROCESS_DETACH: break;
}
return TRUE;
}
//----------------------------------------------------------------------------
// AMC_ClassDesc
//----------------------------------------------------------------------------
class AMC_ClassDesc : public ClassDesc
{
public:
int IsPublic() { return 1; }
void * Create(BOOL loading = FALSE) { return new AMC_Import; }
const TCHAR * ClassName() { return GetString(IDS_SHORT_DESC); }
SClass_ID SuperClassID() { return SCENE_IMPORT_CLASS_ID; }
Class_ID ClassID() { return Class_ID(0x5be11422, 0x6e0177f0); }
const TCHAR* Category() { return GetString(IDS_CATEGORY); }
};
static AMC_ClassDesc AMC_desc;
//----------------------------------------------------------------------------
// This is the interface to Jaguar:
//----------------------------------------------------------------------------
__declspec( dllexport ) const TCHAR * LibDescription()
{
return GetString(IDS_LIB_LONG_DESC);
}
__declspec( dllexport ) int LibNumberClasses()
{
return 1;
}
__declspec( dllexport ) ClassDesc * LibClassDesc(int i)
{
switch(i)
{
case 0: return & AMC_desc; break;
default: return 0; break;
}
}
// Return version so can detect obsolete DLLs
__declspec( dllexport ) ULONG LibVersion()
{
return VERSION_3DSMAX;
}
//
// ASF import module functions follow:
//
AMC_Import::AMC_Import() {}
AMC_Import::~AMC_Import() {}
int AMC_Import::ExtCount()
{
return 1;
}
// Extensions supported for import/export modules
const TCHAR * AMC_Import::Ext(int n)
{
return _T("AMC");
}
const TCHAR * AMC_Import::LongDesc()
{
return GetString(IDS_LONG_DESC);
}
const TCHAR * AMC_Import::ShortDesc()
{
return GetString(IDS_SHORT_DESC);
}
const TCHAR * AMC_Import::AuthorName()
{
return GetString(IDS_AUTHOR_NAME);
}
const TCHAR * AMC_Import::CopyrightMessage()
{
return GetString(IDS_COPYRIGHT);
}
const TCHAR * AMC_Import::OtherMessage1()
{
return _T("");
}
const TCHAR * AMC_Import::OtherMessage2()
{
return _T("");
}
unsigned int AMC_Import::Version()
{
return 100;
}
void AMC_Import::ShowAbout(HWND hWnd)
{
}
//----------------------------------------------------------------------------
// Key_Class
//----------------------------------------------------------------------------
struct Key_Class
{
TimeValue Time;
Quat Orientation;
Point3 Position;
};
//----------------------------------------------------------------------------
// Bone
//----------------------------------------------------------------------------
class Bone
{
public:
Bone ( const char * name, Bone * next )
{
Name = new char [ strlen (name) + 1 ];
strcpy ( Name, name );
Next = next;
Max_keys = 1;
Number_of_keys = 0;
Key = NULL;
}
~Bone ()
{
delete [] Name;
if ( Key != NULL )
delete [] Key;
}
void alloc_key_space ( Interface * gi )
{
if ( Key == NULL && Max_keys > 0 )
Key = new Key_Class [ Max_keys ];
// Find the bone's I-node.
// TODO: check to ensure we got the inode!
INode * inode = gi->GetINodeByName ( Name );
if (inode) {
// Add a key at frame zero to hold the original position.
Quat rotation ( 0.0f, 0.0f, 0.0f, 1.0f );
Matrix3 localTM = inode->GetNodeTM (0) *
Inverse (inode->GetParentTM (0));
rotation *= localTM;
add_key ( 0, rotation, Point3 (0,0,0) );
}
}
void inc_max_keys () { ++ Max_keys; }
void add_key ( TimeValue time, const Quat & rot, const Point3 & pos )
{
if ( Key != NULL && Number_of_keys < Max_keys )
{
Key [Number_of_keys].Time = time;
Key [Number_of_keys].Orientation = rot;
Key [Number_of_keys].Position = pos;
++ Number_of_keys;
}
}
void put_keys_into_max ( ImpInterface * imp_i, Interface * i );
Bone * next () const { return Next; }
char * name () const { return Name; }
protected:
char * Name;
Bone * Next;
unsigned Max_keys;
unsigned Number_of_keys;
Key_Class * Key;
};
//----------------------------------------------------------------------------
// Bone::put_keys_into_max
//----------------------------------------------------------------------------
void Bone::put_keys_into_max ( ImpInterface * imp_i, Interface * i )
{
unsigned k;
// Find the bone's I-node.
INode * inode = i->GetINodeByName ( Name );
if ( inode == NULL )
return;
// Make each quaternion be on the same side of the hypersphere as its
// predecessor.
for ( k = 1; k < Number_of_keys; ++ k )
{
Key [k].Orientation.MakeClosest ( Key [k-1].Orientation );
}
// Make each rotation relative to the previous one.
if ( Number_of_keys > 1 )
{
for ( unsigned k = Number_of_keys - 1; k > 0; -- k )
{
Key [k].Orientation = Key [k].Orientation / Key [k-1].Orientation;
}
}
// Add the keys to the bone's rotation controller.
// Replace the rotation controller if it is not the right type.
Control * c = inode->GetTMController ()->GetRotationController ();
if ( c && c->ClassID () != Class_ID (TCBINTERP_ROTATION_CLASS_ID, 0) )
{
Control *tcb = (Control*) i->CreateInstance ( CTRL_ROTATION_CLASS_ID,
Class_ID (TCBINTERP_ROTATION_CLASS_ID,0) );
if ( ! inode->GetTMController ()->SetRotationController(tcb) )
{
tcb->DeleteThis();
}
}
c = inode->GetTMController ()->GetRotationController ();
// c->SetORT ( ORT_LOOP, ORT_BEFORE );
// c->SetORT ( ORT_LOOP, ORT_AFTER );
IKeyControl * keys = GetKeyControlInterface ( c );
keys->SetNumKeys ( Number_of_keys );
// Create keys.
for ( k = 0; k < Number_of_keys; ++ k )
{
ITCBRotKey key;
key.tens = 0.0f;
key.cont = 0.0f;
key.bias = 0.0f;
key.easeIn = 0.0f;
key.easeOut = 0.0f;
key.time = Key [k].Time;
key.val = (AngAxis) Key [k].Orientation;
keys->SetKey ( k, & key );
}
keys->SortKeys ();
// &&& This is a hack; add position keys for the root node.
// Another hack: The positions are multiplied by a scale factor which
// should be derived from the ASF file.
float length_multiplier;
if ( strcmp ( Name, "root" ) == 0 )
{
// Since this is the root node, check for an AppData chunk defining
// the length multiplier.
// This "length multiplier" will convert whatever numbers are in the file
// into *inches*. We later convert this to whatever units Max is using...
AppDataChunk * chunk = inode->GetAppDataChunk
(
Class_ID(0x74975aa6, 0x1810323f),
SCENE_IMPORT_CLASS_ID,
2
);
if ( chunk == NULL )
{
// (gth) HACK HACK HACK! For some time, I had removed this 'length_multiplier'
// Many commando animations were created without the multiplier which was 1.0 / 0.0254
// so, shove that multiplier in if one wasn't found... This only comes into play
// when loading a motion into a max file that use the old importer to create the
// base pose.
length_multiplier = 1.0f / 0.0254f;
}
else
{
Position_Key_Scale_Chunk * data_p = (Position_Key_Scale_Chunk *) chunk->data;
length_multiplier = data_p->Position_Key_Scale;
}
// Account for the current units setting of Max (e.g. 1 unit = 1 Meter)
length_multiplier /= (float)GetMasterScale(UNITS_INCHES);
// Make sure we have the right kind of controller.
c = inode->GetTMController ()->GetPositionController ();
if ( c && c->ClassID () != Class_ID (TCBINTERP_POSITION_CLASS_ID, 0) )
{
Control *tcb = (Control*) i->CreateInstance ( CTRL_POSITION_CLASS_ID,
Class_ID (TCBINTERP_POSITION_CLASS_ID,0) );
if ( ! inode->GetTMController ()->SetPositionController(tcb) )
{
tcb->DeleteThis();
}
}
c = inode->GetTMController ()->GetPositionController ();
// c->SetORT ( ORT_LOOP, ORT_BEFORE );
// c->SetORT ( ORT_LOOP, ORT_AFTER );
keys = GetKeyControlInterface ( c );
keys->SetNumKeys ( Number_of_keys );
// Create keys.
for ( k = 0; k < Number_of_keys; ++ k )
{
ITCBPoint3Key key;
key.tens = 0.0f;
key.cont = 0.0f;
key.bias = 0.0f;
key.easeIn = 0.0f;
key.easeOut = 0.0f;
key.time = Key [k].Time;
key.val = Key [k].Position * length_multiplier;
keys->SetKey ( k, & key );
}
keys->SortKeys ();
}
}
//----------------------------------------------------------------------------
// Key_Manager
//----------------------------------------------------------------------------
class Key_Manager
{
public:
Key_Manager () { Bones = NULL; }
~Key_Manager ();
// Call this first to increment the maximum number of keys for a given
// bone.
void inc_max_keys ( const char * bone_name );
// After all the maximums are set properly, call this to allocate space
// for holding the keys.
void alloc_key_space ( Interface * gi );
// After allocating space, add keys by calling this.
void add_key
(
const char * bone_name,
TimeValue time,
const Quat & rot,
const Point3 & pos
);
// Once all the keys have been added, call this to massage them and
// put them into 3D Studio.
void put_keys_into_max ( ImpInterface *, Interface * );
protected:
Bone * Bones;
};
//----------------------------------------------------------------------------
// Key_Manager::~Key_Manager
//----------------------------------------------------------------------------
Key_Manager::~Key_Manager ()
{
Bone * p = Bones;
while ( p != NULL )
{
Bone * delete_p = p;
p = p->next ();
delete delete_p;
}
}
//----------------------------------------------------------------------------
// Key_Manager::inc_max_keys
//----------------------------------------------------------------------------
void Key_Manager::inc_max_keys ( const char * name )
{
for ( Bone * p = Bones; p != NULL; p = p->next () )
{
if ( strcmp ( p->name (), name ) == 0 )
break;
}
if ( p == NULL )
{
// This is a new bone; add it to the list.
p = new Bone ( name, Bones );
Bones = p;
}
p->inc_max_keys ();
}
//----------------------------------------------------------------------------
// Key_Manager::alloc_key_space
//----------------------------------------------------------------------------
void Key_Manager::alloc_key_space ( Interface * gi )
{
for ( Bone * p = Bones; p != NULL; p = p->next () )
{
p->alloc_key_space ( gi );
}
}
//----------------------------------------------------------------------------
// Key_Manager::add_key
//----------------------------------------------------------------------------
void Key_Manager::add_key
(
const char * name,
TimeValue time,
const Quat & rot,
const Point3 & pos
)
{
for ( Bone * p = Bones; p != NULL; p = p->next () )
{
if ( strcmp ( p->name (), name ) == 0 )
break;
}
if ( p != NULL )
{
p->add_key ( time, rot, pos );
}
}
//----------------------------------------------------------------------------
// Key_Manager::put_keys_into_max
//----------------------------------------------------------------------------
void Key_Manager::put_keys_into_max ( ImpInterface * imp_i, Interface * i )
{
for ( Bone * p = Bones; p != NULL; p = p->next () )
{
p->put_keys_into_max ( imp_i, i );
}
}
//----------------------------------------------------------------------------
// get_name
//----------------------------------------------------------------------------
void get_name
(
char * & line_p,
char * & name_p
)
{
name_p = line_p;
while ( ! isspace (*line_p) )
++ line_p;
*line_p = '\0';
++ line_p;
}
//----------------------------------------------------------------------------
// read_frames
//----------------------------------------------------------------------------
static void read_frames
(
FILE * file,
ImpInterface * iface,
Interface * gi
)
{
char line [ 512 ];
Key_Manager key_manager;
// Start by identifying the bones in the system and the number of keys
// for each.
long start_pos = ftell ( file );
while (1)
{
char * rv = fgets ( line, sizeof line, file );
if ( rv == NULL )
break;
if ( ! isdigit (line [0]) )
{
char * line_p = line;
char * name_p;
get_name ( line_p, name_p );
key_manager.inc_max_keys ( name_p );
}
}
// Prepare to scan the file again. Allocate space to hold the keys.
fseek ( file, start_pos, SEEK_SET );
key_manager.alloc_key_space ( gi );
int frame_time = 0;
while (1)
{
char * rv = fgets ( line, sizeof line, file );
if ( rv == NULL )
break;
if ( isdigit (line [0]) )
{
// This line marks the start of a new frame.
int frame_number = strtol ( line, NULL, 10 );
frame_time = (frame_number - 1) * amc_ticks_per_frame +
first_frame_time;
}
else
{
// This line contains a bone's data for the current frame.
// Get the name of the bone whose key is being defined.
char * line_p = line;
char * name_p;
get_name ( line_p, name_p );
// Find the I-node with the name given in the input file.
INode * inode = gi->GetINodeByName ( name_p );
#if 0
if ( inode == NULL )
{
char message [ 256 ];
sprintf ( message, "Can't find node named \"%s\".",
name_p );
MessageBox ( GetActiveWindow (), message, "Parse error",
MB_OK );
return;
}
#else
if ( inode == NULL) {
continue;
}
#endif
// From the I-node, get the appdata chunk that defines the order
// in which rotations should be applied.
AppDataChunk * chunk = inode->GetAppDataChunk
(
Class_ID(0x74975aa6, 0x1810323f),
SCENE_IMPORT_CLASS_ID,
1
);
if ( chunk == NULL )
{
char message [ 256 ];
sprintf ( message, "\"%s\" has no app data chunk.",
name_p );
MessageBox ( GetActiveWindow (), message, "Parse error",
MB_OK );
return;
}
ASF_Data_Chunk * data_p = (ASF_Data_Chunk *) chunk->data;
// Read in angle settings and build a transform matrix from them.
Quat rotation (0.0f,0.0f,0.0f,1.0f);
Point3 translation ( 0, 0, 0 );
for ( unsigned i = 0; i < data_p->Number_of_axes; ++ i )
{
float value;
value = (float) strtod ( line_p, & line_p );
switch ( data_p->Axis [i] )
{
case ROTATE_X:
rotation *= (Quat) AngAxis ( Point3 (1, 0, 0),
-value * ANGLE_MULTIPLIER );
break;
case ROTATE_Y:
rotation *= (Quat) AngAxis ( Point3 (0, 1, 0),
-value * ANGLE_MULTIPLIER );
break;
case ROTATE_Z:
rotation *= (Quat) AngAxis ( Point3 (0, 0, 1),
-value * ANGLE_MULTIPLIER );
break;
case TRANSLATE_X:
translation.x = value;
break;
case TRANSLATE_Y:
translation.y = value;
break;
case TRANSLATE_Z:
translation.z = value;
break;
case TRANSLATE_LENGTH:
default:
break;
}
}
Matrix3 localTM = inode->GetNodeTM (0) *
Inverse (inode->GetParentTM (0));
rotation *= localTM;
if ( frame_time % max_ticks_per_frame == 0 )
key_manager.add_key ( name_p, frame_time, rotation, translation );
}
}
key_manager.put_keys_into_max ( iface, gi );
gi->SetAnimRange ( Interval (0, frame_time) );
}
//----------------------------------------------------------------------------
// amc_load
//----------------------------------------------------------------------------
static int amc_load
(
const TCHAR * filename,
ImpInterface * iface,
Interface * gi
)
{
char line [ 512 ];
// Load the motion-capture data file.
FILE * file = fopen ( filename, "r" );
// Ignore the first line.
fgets ( line, sizeof line, file );
// Match the second line to ":FULLY-SPECIFIED".
fgets ( line, sizeof line, file );
if ( strcmp ( line, ":FULLY-SPECIFIED\n" ) != 0 )
{
MessageBox ( GetActiveWindow (), "First line.", "Parse error",
MB_OK );
return -1;
}
// Match the third line to ":DEGREES".
fgets ( line, sizeof line, file );
if ( strcmp ( line, ":DEGREES\n" ) != 0 )
{
MessageBox ( GetActiveWindow (), "Second line.", "Parse error",
MB_OK );
return -1;
}
// Build the data structures to hold the rotation keys.
read_frames ( file, iface, gi );
fclose ( file );
return 1;
}
//----------------------------------------------------------------------------
// AMC_Import::DoImport
//----------------------------------------------------------------------------
int AMC_Import::DoImport
(
const TCHAR * filename,
ImpInterface * iface,
Interface * gi,
BOOL
)
{
max_ticks_per_frame = GetTicksPerFrame ();
int status = amc_load ( filename, iface, gi );
if ( status == 0 )
status = IMPEXP_CANCEL;
return (status <= 0) ? IMPEXP_FAIL : status;
}