/****************************************************************************************/ /* MKMOTION.C */ /* */ /* Author: Stephen Balkum */ /* Description: Motion construction from MAX export. */ /* */ /* The contents of this file are subject to the Genesis3D Public License */ /* Version 1.01 (the "License"); you may not use this file except in */ /* compliance with the License. You may obtain a copy of the License at */ /* http://www.genesis3d.com */ /* */ /* Software distributed under the License is distributed on an "AS IS" */ /* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See */ /* the License for the specific language governing rights and limitations */ /* under the License. */ /* */ /* The Original Code is Genesis3D, released March 25, 1999. */ /*Genesis3D Version 1.1 released November 15, 1999 */ /* Copyright (C) 1999 WildTangent, Inc. All Rights Reserved */ /* */ /****************************************************************************************/ #include #include #include #include #include "actor.h" #include "body.h" #include "motion.h" #include "ram.h" #include "strblock.h" #include "mkutil.h" #include "tdbody.h" #include "maxmath.h" #include "mkmotion.h" #define MK_PI 3.141592654f #define LINE_LENGTH 5000 typedef struct MkMotion_Options { char BodyFile[_MAX_PATH]; char KeyFile[_MAX_PATH]; char MotionFile[_MAX_PATH]; MK_Boolean KeepStationary; MK_Boolean Originate; MK_Boolean Capitalize; geFloat TimeOffset; geStrBlock* pMotionRoots; char* pMotionName; char EventBoneSeparator; geVec3d RootEulerAngles; geVec3d RootTranslation; geVec3d EulerAngles; // rotation at read time } MkMotion_Options; const MkMotion_Options DefaultOptions = { "", "", "", MK_FALSE, MK_FALSE, MK_FALSE, 0.0f, NULL, NULL, '\0', // default to no bone names on events { 0.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 0.0f}, }; typedef struct { geStrBlock* pEvents; char name[LINE_LENGTH]; geXForm3d* pMatrixKeys; // keys with respect to parent geXForm3d* pWSKeys; // keys in world space } BoneKeyInfo; void DestroyBoneKeyArray(BoneKeyInfo** ppInfo, int nNumBones) { int i; BoneKeyInfo* pInfo; assert(ppInfo != NULL); pInfo = *ppInfo; assert(pInfo != NULL); assert(nNumBones > 0); for(i=0;i 0); assert(nNumKeys > 0); pInfo = GE_RAM_ALLOCATE_ARRAY(BoneKeyInfo, nNumBones); if(pInfo == NULL) return(NULL); // explicitly null the pointers for(i=0;iTranslation, &pM2->Translation, XFORM_COMPARE_TOLERANCE) == GE_FALSE) return(GE_FALSE); return(GE_TRUE); } // TKArray cannot handle two keys with the same time, so keep incrementing the // time by a little bit until an empty hole is found or reach the LimitDistance geBoolean KEYMotion_InsertEventNoDuplicateTime(geMotion* pMotion, geFloat tKey, const char* String, geFloat LimitDistance) { geFloat TimeOffset; geFloat KeyTime; geFloat InsertKeyTime; const char* pEventString; #define GE_TKA_TIME_TOLERANCE (0.00001f) #define TIME_STEP_DISTANCE (GE_TKA_TIME_TOLERANCE * 10.0f) TimeOffset = 0.0f; while(TimeOffset < LimitDistance) { InsertKeyTime = tKey + TimeOffset; geMotion_SetupEventIterator(pMotion, InsertKeyTime, InsertKeyTime + TIME_STEP_DISTANCE); if(geMotion_GetNextEvent(pMotion, &KeyTime, &pEventString) == GE_FALSE) { #pragma message("geMotion_InsertEvent should take a const const*") return(geMotion_InsertEvent(pMotion, InsertKeyTime, (char*)String)); } TimeOffset += TIME_STEP_DISTANCE; } return(GE_FALSE); } ReturnCode MkMotion_DoMake(MkMotion_Options* options,MkUtil_Printf Printf) { int i; char* pdot; geBody* pBody; geMotion* pMotion; gePath* pPath; geVFile *VF=NULL; FILE* fp=NULL; ReturnCode retValue = RETURN_SUCCESS; int nVersion = 0; int j, k, Index, ParentIndex; int NumBones=0; int NumFrames, FramesPerSecond; geFloat SecondsPerFrame; char line[LINE_LENGTH]; char name[LINE_LENGTH]; geXForm3d InvAttach, KeyMatrix, TmpMatrix; geQuaternion Q; TopDownBody* pTDBody = NULL; int MotionRootIndex = GE_BODY_NO_PARENT_BONE; TDBodyHeritage BoneIsDescendent = TDBODY_IS_DESCENDENT; // default to all in the family geXForm3d RootRotation; geXForm3d euler; BoneKeyInfo* pKeyInfo = NULL; const BoneKeyInfo* pParentInfo; assert(options != NULL); if(options->BodyFile[0] == 0) { Printf("ERROR: No body file specified\n"); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); return retValue; } if(options->KeyFile[0] == 0) { Printf("ERROR: No key file specified\n"); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); return retValue; } // who knows how many times this will get used in here geXForm3d_SetEulerAngles(&euler, &options->EulerAngles); // Here is the official fclose to use #define FCLOSE(f) { fclose(f); f = NULL; } // Read the body file VF = geVFile_OpenNewSystem(NULL,GE_VFILE_TYPE_DOS,options->BodyFile,NULL,GE_VFILE_OPEN_READONLY); if(VF == NULL) { Printf("ERROR: Could not open '%s' body file\n", options->BodyFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); return retValue; } else { pBody = geBody_CreateFromFile(VF); if(pBody == NULL) { Printf("ERROR: Could not create body from file '%s'\n", options->BodyFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); geVFile_Close(VF); VF = NULL; return retValue; } geVFile_Close(VF); VF = NULL; } // Create the Top Down Hierarchy if needed if(geStrBlock_GetCount(options->pMotionRoots) > 0) { pTDBody = TopDownBody_CreateFromBody(pBody); if(pTDBody == NULL) { Printf("ERROR: Could not create top down hierarchy from body.\n"); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); geBody_Destroy(&pBody); return retValue; } } // Options permit a rotation applied to the root. Convert the Euler angles // to a transform for easier use later. geXForm3d_SetEulerAngles(&RootRotation, &options->RootEulerAngles); pMotion = geMotion_Create(GE_TRUE); if(pMotion == NULL) { Printf("ERROR: Could not create motion\n"); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); goto DoMake_Cleanup; } if(options->pMotionName != NULL) geMotion_SetName(pMotion, options->pMotionName); // Read key data fp = fopen(options->KeyFile, "rt"); if(fp == NULL) { Printf("ERROR: Could not open '%s' key file\n", options->KeyFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); geMotion_Destroy(&pMotion); goto DoMake_Cleanup; } #define FGETS_LINE_OR_QUIT(s) \ if(fgets(s, LINE_LENGTH, fp) == NULL) \ { \ Printf("ERROR: Could not read from '%s' key file\n", options->KeyFile); \ MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); \ goto DoMake_Cleanup; \ } // Read and check version FGETS_LINE_OR_QUIT(line); #define FILETYPE_LENGTH 7 if(strncmp(line, "KEYEXP ", FILETYPE_LENGTH) != 0) { Printf("ERROR: '%s' is not a KEY file\n", options->KeyFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); goto DoMake_Cleanup; } // check version numbers StripNewLine(line); if(strcmp(line + FILETYPE_LENGTH, "1.0") == 0) { nVersion = 0x0100; } else if(strcmp(line + FILETYPE_LENGTH, "2.0") == 0) { nVersion = 0x0200; } else if(strcmp(line + FILETYPE_LENGTH, "2.1") == 0) { nVersion = 0x0210; // added notetracks } else { Printf("ERROR: '%s' KEY file version \"%s\" is not supported\n", options->KeyFile, line + FILETYPE_LENGTH); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); goto DoMake_Cleanup; } assert(nVersion != 0); // Read number of bones FGETS_LINE_OR_QUIT(line); sscanf(line, "Number of Bones = %d", &NumBones); // Read "Key Data" FGETS_LINE_OR_QUIT(line); if(strcmp(line, "Key Data\n") != 0) { Printf("ERROR: '%s' key file does not contain \"Key Data\"\n", options->KeyFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); goto DoMake_Cleanup; } // Read number of frames FGETS_LINE_OR_QUIT(line); sscanf(line, "%d %d %d", &j, &NumFrames, &FramesPerSecond); NumFrames -= j; NumFrames++; SecondsPerFrame = 1.0f / (geFloat)FramesPerSecond; // Allocate key data pKeyInfo = CreateBoneKeyArray(NumBones, NumFrames); if(pKeyInfo == NULL) { Printf("ERROR: Could not create key data\n"); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); goto DoMake_Cleanup; } // Read in key data for(k=0;kCapitalize != MK_FALSE) strupr(pKeyInfo[k].name); // Notes appear here if(nVersion >= 0x0210) { int NumNotes; // Read number of notes FGETS_LINE_OR_QUIT(line); sscanf(line, "Number of Notes = %d", &NumNotes); for(i=0;iBodyFile, options->KeyFile); Printf("Body has these bones:\n"); for (m=0; mpWSKeys + j, pKeyInfo[k].pWSKeys + j ); #endif } } else { for(j=0;jBodyFile, options->KeyFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); goto DoMake_Cleanup; } // apply rotation to world space for(j=0;jpWSKeys + j, pKeyInfo[k].pMatrixKeys + j ); } } else { for(j=0;jBodyFile, options->KeyFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); goto DoMake_Cleanup; } // If we have specified a root for the motion, make sure this bone // is a descendent. j = geStrBlock_GetCount(options->pMotionRoots); if(j > 0) { assert(pTDBody != NULL); while(j > 0) { j--; if(geBody_GetBoneByName(pBody, geStrBlock_GetString(options->pMotionRoots, j), &MotionRootIndex, &TmpMatrix, &ParentIndex) == GE_FALSE) { Printf("ERROR: Could not find bone '%s' in body '%s' for motion root\n", geStrBlock_GetString(options->pMotionRoots, j), options->BodyFile); { int m; Printf("Body has these bones:\n"); for (m=0; mKeyFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); goto DoMake_Cleanup; } notestring += 2; // this jump past the space to, at the very least, a terminator if(options->EventBoneSeparator != '\0') { // base string length decisions on "line", not "notestring" linelen = strlen(line); linelen++; // add one for separator character if( (LINE_LENGTH - linelen) < (strlen(name))) { // some string too long Printf("ERROR: Note \"%s\" is too long for bone '%s' for key file '%s'\n", notestring, name, options->KeyFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); goto DoMake_Cleanup; } line[linelen - 1] = options->EventBoneSeparator; line[linelen] = '\0'; strcat(line, name); } notetime = (geFloat)noteframe * SecondsPerFrame + options->TimeOffset; // insert the note, but not past this frame if(KEYMotion_InsertEventNoDuplicateTime(pMotion, notetime, notestring, SecondsPerFrame) == GE_FALSE) { Printf("ERROR: Could not add note \"%s\" for bone '%s' for key file '%s'\n", notestring, name, options->KeyFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); goto DoMake_Cleanup; } } } pPath = gePath_Create(GE_PATH_INTERPOLATE_HERMITE, GE_PATH_INTERPOLATE_SLERP, GE_FALSE); if (pPath == NULL) { Printf("ERROR: Unable to create a path for bone '%s' for key file '%s'\n",name, options->KeyFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); goto DoMake_Cleanup; } if(BoneIsDescendent != TDBODY_IS_NOT_DESCENDENT) { if(geMotion_AddPath(pMotion, pPath, name, &Index) == GE_FALSE) { if (geMotion_GetPathNamed(pMotion,name) != NULL) { Printf("ERROR: More than one bone named '%s' in key file '%s'\n", name, options->KeyFile); } else { Printf("ERROR: Could not add path for bone '%s' for key file '%s'\n", name, options->KeyFile); } MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); goto DoMake_Cleanup; } //pPath = geMotion_GetPath(pMotion, Index); } // Read the key data, divide out the attachment (multiply by inverse), // and insert the keyframe for(j=0;jRootTranslation.X, options->RootTranslation.Y, options->RootTranslation.Z); // flip the rotation direction TmpMatrix.Translation = KeyMatrix.Translation; geQuaternion_FromMatrix(&KeyMatrix, &Q); Q.W = -Q.W; geQuaternion_ToMatrix(&Q, &KeyMatrix); KeyMatrix.Translation = TmpMatrix.Translation; geXForm3d_GetTranspose(&InvAttach, &TmpMatrix); geXForm3d_Multiply(&TmpMatrix, &KeyMatrix, &TmpMatrix); Q; FirstFrameOffset; geXForm3d_Orthonormalize(&TmpMatrix); gePath_InsertKeyframe( pPath, GE_PATH_ROTATION_CHANNEL | GE_PATH_TRANSLATION_CHANNEL, (geFloat)j / (geFloat)FramesPerSecond + options->TimeOffset, &TmpMatrix); } // First, destroy path //if(BoneIsDescendent == MK_FALSE) { gePath_Destroy(&pPath); } // Then, check for errors if(j != NumFrames) { // There must have been an error goto DoMake_Cleanup; } } if(k != NumBones) { // There must have been an error goto DoMake_Cleanup; } if (options->MotionFile[0] == 0) { // build motion filename: keyfile.key -> keyfile.mot strcpy(options->MotionFile,options->KeyFile); pdot = strrchr(options->MotionFile, '.'); if(pdot == NULL) { pdot = options->MotionFile + strlen(options->MotionFile); } strcpy(pdot, ".mot"); } // Write the motion VF = geVFile_OpenNewSystem(NULL,GE_VFILE_TYPE_DOS,options->MotionFile,NULL,GE_VFILE_OPEN_CREATE); //fp = fopen(options->MotionFile, "wt"); if(VF == NULL) { Printf("ERROR: Could not create '%s' motion file\n", options->MotionFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); unlink(options->MotionFile); goto DoMake_Cleanup; } else { if(geMotion_WriteToFile(pMotion, VF) == GE_FALSE) { geVFile_Close(VF); VF = NULL; Printf("ERROR: Motion file '%s' was not written correctly\n", options->MotionFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); unlink(options->MotionFile); } else { if (geVFile_Close(VF)==GE_FALSE) { Printf("ERROR: Motion file '%s' was not written correctly\n", options->MotionFile); MkUtil_AdjustReturnCode(&retValue, RETURN_ERROR); unlink(options->MotionFile); } VF = NULL; Printf("SUCCESS: Motion file '%s' written successfully\n", options->MotionFile); } } DoMake_Cleanup: if (MkUtil_Interrupt()) { Printf("Interrupted... Unable to make motion '%s'\n",options->MotionFile); } else if (retValue==RETURN_ERROR) { Printf("Error. Unable to make motion '%s'\n",options->MotionFile); } if(pKeyInfo != NULL) DestroyBoneKeyArray(&pKeyInfo, NumBones); if(fp != NULL) FCLOSE(fp); if(VF != NULL) geVFile_Close(VF); if(pTDBody != NULL) TopDownBody_Destroy(&pTDBody); if(pMotion != NULL) geMotion_Destroy(&pMotion); if(pBody != NULL) geBody_Destroy(&pBody); return retValue; } void MkMotion_OutputUsage(MkUtil_Printf Printf) { //COLS: 0 1 2 3 4 5 6 7 | 8 Printf("\n"); Printf("Builds a motion file from a key info file from 3DSMax.\n"); Printf("\n"); Printf("MKMOTION [options] /B /K [/C] [/E[]] [/N] [/O]\n"); Printf(" [/R] [/S] [/T