/* ====================================================================== doom3md5.c doom3 md5 mesh import Clinton Reese Jan 19 2007 May 19 2007 NOTE: autokey needs to be on (plugin may take care of that, not tested) load mesh through file dialog all else is in additional plugins bones will load to selected mesh after import select a bone or the mesh select all bones of current object "r" to activate and set rest pos and rot NOTE: surface assignment manual process cant tell how to do it "right" open mesh in modeler w - open statistics polygon selection mode select surface q select same surface deselect and do again for each surface final do save-as from modeler to get rid of ".md5mesh" in name everything is still in z up imported, but maybe not matter for custom objects exports the preview animation range May 18 2007 192 joint limit, 24 mesh limit, 8092 weights,verts,tris,uvs anim import baseframe==frame 0 of anim character import(z top) left and right are mirrored(reversed) X value front of character faces -Y May 19 2007 multi mesh is good each layer -> different doom3 mesh if only 1 layer name from lw mesh otherwise from layer name uv v was reversed now fixed set frame rate import warning and status disp on long mesh export animation import export tested and working ====================================================================== */ #include #include #include #include #include #include #include #include #include #include "eulerangle.h" // Disable visual studio express security warning messages 4996. #pragma warning( disable : 4996 ) #define PI 3.1415927 static double RADperDEG = PI/180; static double RADtoDEG = 180/PI; //lightwave euler angle order #define EulHPB EulOrdYXZr XCALL_( int ) geomLoad( long version, GlobalFunc *global, LWObjectImport *local, void *serverData ) { FILE *fp = NULL; char lnbuffer[1024], *msgtag, *err; char seps[] = " \"\t"; // space quote and tab characters int numJoints, numMeshes,numverts, curJoint, curvert, numtris, parentIndex[192]; int curweight, wtJoint[8192]; char boneName[192][64]; char uvName[24][64], meshName[24][64]; int procJoints=0; int curmesh = -1; LWFVector pos = {0.0f}; LWFVector pos0 = {0.0f}; LWPntID *pntID = NULL; LWPntID *JpntID = NULL;//joint points LWPntID vID[3];//single polygon max 3 verts LWPolID polID; float jx[192], jy[192], jz[192];//joint position in world space float xq[192], yq[192], zq[192], wq[192];//joint orientation quaternion int firstWeight[8192],numVWeights[8192];//first weight index and num weights on the uv vertex int curtri, trifaceI[8192], trifaceJ[8192], trifaceK[8192], numweights, vt, wtCnt, fw, wt, wi; int jointIndex, polyi; float weight[8192],wtX[8192], wtY[8192], wtZ[8192], posX, posY, posZ, myU[8192], myV[8192], myUV[2],myWeight[1]; float qx, qy, qz, qw, rm11, rm12, rm13, rm21, rm22, rm23, rm31, rm32, rm33; float rposX, rposY, rposZ; int sLen; char surfName[24][64];//May 18 found q4 model with 11 meshes struct assWeight { char name[64]; float weight; } myass[10]; if ( version != LWOBJECTIMPORT_VERSION ) return AFUNC_BADVERSION; //open the file fp=fopen(local->filename,"r"); if(!fp) { local->result=LWOBJIM_BADFILE; return AFUNC_OK; } //check for proper header for md5 mesh file fgets(lnbuffer,1023,fp); if(strncmp(lnbuffer,"MD5Version 10",13)) { fclose(fp); local->result=LWOBJIM_NOREC; return AFUNC_OK; } //loop through file while (!feof(fp)) { fgets(lnbuffer,1023,fp); msgtag = strtok(lnbuffer,seps); if (!strncmp(msgtag, "numJoints",9)) sscanf(strtok(NULL,seps),"%i",&numJoints); if (!strncmp(msgtag, "numMeshes",9)) sscanf(strtok(NULL,seps),"%i",&numMeshes); //process joint line if(procJoints) { strcpy(boneName[curJoint],msgtag); msgtag = strtok(NULL,seps); //create weight map local->vmap( local->data, LWVMAP_WGHT, 1, boneName[curJoint] ); sscanf(msgtag,"%i",&parentIndex[curJoint]);//parent index //joint points msgtag = strtok(NULL,seps);// ( msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&jx[curJoint]);//joint xyz msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&jy[curJoint]); msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&jz[curJoint]); msgtag = strtok(NULL,seps);// ) //create points in lightwave for skelegons pos[0]=jx[curJoint]; pos[1]=jy[curJoint]; pos[2]=jz[curJoint];//March 4 2007 DONT SWAP YET TO MATCH ANIMATION if(!(JpntID[curJoint] = local->point(local->data, pos))) { err = "Couldn't create point."; goto Finish; } //create 2 point polygons for fake bones if(curJoint != 0) { vID[0] = JpntID[parentIndex[curJoint]]; vID[1] = JpntID[curJoint]; polID = local->polygon( local->data, LWPOLTYPE_BONE, 0, 2, vID); } //joint angles msgtag = strtok(NULL,seps);// ( msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&xq[curJoint]);//joint quat xyz msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&yq[curJoint]); msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&zq[curJoint]); //calculate w component of quat wq[curJoint] = 1.0 - xq[curJoint]*xq[curJoint] - yq[curJoint]*yq[curJoint] - zq[curJoint]*zq[curJoint]; if(wq[curJoint]<0.0) wq[curJoint]=0.0; else wq[curJoint] = -sqrt(wq[curJoint]);//March 4 2007 negative curJoint++; if(curJoint>=numJoints) { procJoints=0;//exit process joint mode } } //enter process joint mode if (!strncmp(msgtag, "joints",6)) { procJoints=1;//1 for true curJoint=0; JpntID = calloc(numJoints, sizeof(LWPntID));//allocate mem for joint points if(!JpntID) goto Finish; local->layer(local->data, 0,"theSkelegons");//set layer 0 and name local->pivot(local->data, pos0); } //next mesh in file if (!strncmp(msgtag, "mesh",4)) curmesh++; //get mesh name if (!strncmp(msgtag, "//",2)) { msgtag = strtok(NULL,seps);//meshes: msgtag = strtok(NULL,seps);//name sLen = strlen(msgtag) - 1;//remove trailing line feed strcpy(meshName[curmesh],""); strncat(meshName[curmesh],msgtag,sLen); strcpy(uvName[curmesh],meshName[curmesh]); strcat(uvName[curmesh],"UV"); strcpy(surfName[curmesh],meshName[curmesh]); strcat(surfName[curmesh],"S"); //do layer name here local->layer(local->data, curmesh + 1,meshName[curmesh]);//set layer 0 and name local->pivot(local->data, pos0); } if (!strncmp(msgtag, "numverts",8)) sscanf(strtok(NULL,seps),"%i",&numverts); //vert if (!strncmp(msgtag, "vert",4)) { msgtag = strtok(NULL,seps);//current vert sscanf(msgtag,"%i",&curvert); msgtag = strtok(NULL,seps);//( msgtag = strtok(NULL,seps);//u sscanf(msgtag,"%f",&myU[curvert]); msgtag = strtok(NULL,seps);//v sscanf(msgtag,"%f",&myV[curvert]); msgtag = strtok(NULL,seps);//) msgtag = strtok(NULL,seps); sscanf(msgtag,"%i",&firstWeight[curvert]); msgtag = strtok(NULL,seps); sscanf(msgtag,"%i",&numVWeights[curvert]); } if (!strncmp(msgtag, "numtris",7)) sscanf(strtok(NULL,seps),"%i",&numtris);//num triangles current mesh //triangle info if (!strncmp(msgtag, "tri",3)) { msgtag = strtok(NULL,seps);//current triangle index sscanf(msgtag,"%i",&curtri); msgtag = strtok(NULL,seps); sscanf(msgtag,"%i",&trifaceI[curtri]); msgtag = strtok(NULL,seps); sscanf(msgtag,"%i",&trifaceJ[curtri]); msgtag = strtok(NULL,seps); sscanf(msgtag,"%i",&trifaceK[curtri]); } if (!strncmp(msgtag, "numweights",10)) sscanf(strtok(NULL,seps),"%i",&numweights); if (!strncmp(msgtag, "weight",6)) { msgtag = strtok(NULL,seps); sscanf(msgtag,"%i",&curweight);//weight index msgtag = strtok(NULL,seps); sscanf(msgtag,"%i",&wtJoint[curweight]);//joint associated with this point weight msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&weight[curweight]);//weight value(they add to 1.0) msgtag = strtok(NULL,seps);//( //xyz of point relative to joint local coord system msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&wtX[curweight]); msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&wtY[curweight]); msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&wtZ[curweight]); //if last weight create the lightwave mesh if(curweight==numweights-1) { pntID = calloc(numverts, sizeof(LWPntID));//temp alloc for point definition - free at end of this section //create point cloud for(vt=0;vtpoint(local->data, pos); //apply weight map for(wt=0;wtvmap( local->data, LWVMAP_WGHT, 1, myass[wt].name ); local->vmapVal( local->data, pntID[vt], myWeight ); } } //uv array local->vmap(local->data, LWVMAP_TXUV, 2, uvName[curmesh]);//create map for(vt=0;vtvmapVal(local->data, pntID[vt], myUV); // myUVmapObj.setValue(vert[vt],myuv); } // do polygons here for(polyi=0;polyipolygon( local->data, LWPOLTYPE_FACE, 0, 3, vID); //try set surface local->polTag( local->data, polID, LWPTAG_SURF, surfName[curmesh] );//can select mesh by this but nothing in surface editor } if(pntID) free(pntID); } } } local->result= LWOBJIM_OK; local->done( local->data); Finish: if(fp) fclose(fp); if(JpntID) free( JpntID); return AFUNC_OK; } //Jan 27 2007 // //do bone import here as a seperate plugin //works but null skeleton comes in gigantic but then load mesh and it looks good //weird //next build heirarchy //heirarchy is good next do real bones //29-Jan-2007 rotation of Nulls looks good next step really is the bones //NOTE: found out that autokey must be on when import otherwise roations lost // parent in place not tested yet // import mesh first so get weightmaps on bones // have mesh selected so bones are assigned to it // // looks like its working - still not sure of bone orientations // will know more when try animation import //04-March-2007 TODO: angles are wrong need to fix like animation import // i think this has been done(March 17) //16-May-2007 need -x to fix left/right //19-May-2007 done XCALL_( int ) boneLoad( long version, GlobalFunc *global, LWLayoutGeneric *local, void *serverData ) { static char name[ 256 ], path[ 256 ], node[ 256 ]; FILE *fp = NULL; char cmd[ 128 ]; int result; char lnbuffer[1024], *msgtag; char seps[] = " \"\t"; // space quote and tab characters char boneName[192][64]; int numJoints, parentIndex[192]; float jx[192], jy[192], jz[192];//joint position in world space float xq[192], yq[192], zq[192], wq[192];//joint orientation quaternion int curJoint, procJoints=0; LWFVector pos = {0.0f}; LWInterfaceInfo *intinfo; LWItemInfo *iteminfo; unsigned int genintflags,itemID[192],buildID; LWItemID *id; Quat myQuat; double hpb[3]; EulerAngles genericEul; LWBoneInfo *boneinfo; LWItemType mytype; LWObjectFuncs *objfunc; int numWeights,curwt; const char *weightName; double boneLength, lenX, lenY, lenZ; LWFileReqFunc *filereq; #define MAXFILESZ 260 static char fnode[ MAXFILESZ ] = "*.md5mesh", fpath[ MAXFILESZ ] = "", fname[ MAXFILESZ ] = ""; if ( version != LWLAYOUTGENERIC_VERSION ) return AFUNC_BADVERSION; // try older file request style filereq = global( LWFILEREQFUNC_GLOBAL, GFUSE_TRANSIENT ); result = filereq( "Load MD5 Bones", fnode, fpath, fname, MAXFILESZ ); if ( !result ) return AFUNC_OK; fp=fopen(fname,"r"); if(!fp) { return AFUNC_OK; } //check for proper header for md5 mesh file fgets(lnbuffer,1023,fp); if(strncmp(lnbuffer,"MD5Version 10",13)) { fclose(fp); return AFUNC_OK; } //get parentinplace flag and set it intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); genintflags = intinfo->generalFlags; genintflags &= LWGENF_PARENTINPLACE; if(genintflags==0) local->evaluate( local->data, "ParentInPlace" ) ; //mesh selected or create build null intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); id = intinfo->selItems; mytype = LWI_LIGHT;//initialize to non mesh if(id[0]!=NULL) { iteminfo = global( LWITEMINFO_GLOBAL, GFUSE_TRANSIENT ); mytype = iteminfo->type(id[0]); if(mytype==LWI_OBJECT) buildID = id[0]; } if(id[0]==NULL || mytype!=LWI_OBJECT) { //create world space null for building skeleton sprintf( cmd, "AddNull buildNULL"); local->evaluate( local->data, cmd ); //save item id iteminfo = global( LWITEMINFO_GLOBAL, GFUSE_TRANSIENT ); intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); if ( !iteminfo ) return AFUNC_BADGLOBAL; id = intinfo->selItems; buildID = id[0]; } //get weight maps in scene objfunc = global( LWOBJECTFUNCS_GLOBAL, GFUSE_TRANSIENT ); numWeights = objfunc->numVMaps( LWVMAP_WGHT ); //loop through file while (!feof(fp)) { fgets(lnbuffer,1023,fp); msgtag = strtok(lnbuffer,seps); if (!strncmp(msgtag, "numJoints",9)) sscanf(strtok(NULL,seps),"%i",&numJoints); //process joint line if(procJoints) { strcpy(boneName[curJoint],msgtag); msgtag = strtok(NULL,seps); sscanf(msgtag,"%i",&parentIndex[curJoint]);//parent index //joint points msgtag = strtok(NULL,seps);// ( msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&jx[curJoint]);//joint xyz msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&jy[curJoint]); msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&jz[curJoint]); msgtag = strtok(NULL,seps);// ) pos[0]=jx[curJoint]; pos[1]=jy[curJoint]; pos[2]=jz[curJoint]; sprintf( cmd, "SelectItem %x", buildID); local->evaluate( local->data, cmd ); sprintf( cmd, "AddBone %s",boneName[curJoint]); local->evaluate( local->data, cmd ); //check weight map exists in scene for(curwt=0;curwtvmapName( LWVMAP_WGHT, curwt ); if (!strncmp(weightName, boneName[curJoint],64)) { //match found sprintf( cmd, "BoneWeightMapName %s", boneName[curJoint]); local->evaluate( local->data, cmd ); sprintf( cmd, "BoneWeightMapOnly"); local->evaluate( local->data, cmd ); } } //save item id iteminfo = global( LWITEMINFO_GLOBAL, GFUSE_TRANSIENT ); intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); boneinfo = global( LWBONEINFO_GLOBAL, GFUSE_TRANSIENT ); if ( !iteminfo ) return AFUNC_BADGLOBAL; id = intinfo->selItems; itemID[curJoint] = id[0]; sprintf( cmd, "Position %f %f %f",pos[0], pos[1], pos[2]); local->evaluate( local->data, cmd ); //joint angles msgtag = strtok(NULL,seps);// ( msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&xq[curJoint]);//joint quat xyz msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&yq[curJoint]); msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&zq[curJoint]); //calculate w component of quat wq[curJoint] = 1.0 - xq[curJoint]*xq[curJoint] - yq[curJoint]*yq[curJoint] - zq[curJoint]*zq[curJoint]; if(wq[curJoint]<0.0) wq[curJoint]=0.0; else wq[curJoint] = -sqrt(wq[curJoint]);//March 4 2007 //convert quaternions to HPB(rotation in XYZ) myQuat.x = xq[curJoint]; myQuat.y = yq[curJoint]; myQuat.z = zq[curJoint]; myQuat.w = wq[curJoint]; genericEul = Eul_FromQuat(myQuat, EulOrdYXZr);//March 4 2007 hpb[0] = RADtoDEG*genericEul.x; hpb[1] = RADtoDEG*genericEul.y; hpb[2] = RADtoDEG*genericEul.z; sprintf( cmd, "Rotation %f %f %f",hpb[0], hpb[1], hpb[2]); local->evaluate( local->data, cmd ); //default rest length of 3 sprintf( cmd, "BoneRestLength 3"); local->evaluate( local->data, cmd ); //parent the item and set bone length of parent if(curJoint != 0) { //set rest length based on parent bone //ideally would be length from current to child bone lenX = jx[parentIndex[curJoint]] - jx[curJoint]; lenY = jy[parentIndex[curJoint]] - jy[curJoint]; lenZ = jz[parentIndex[curJoint]] - jz[curJoint]; boneLength = sqrt(lenX*lenX + lenY*lenY + lenZ*lenZ)*0.6;//dont use full bone length sprintf( cmd, "BoneRestLength %f", boneLength); local->evaluate( local->data, cmd ); sprintf( cmd, "ParentItem %x",itemID[parentIndex[curJoint]]);//hex number local->evaluate( local->data, cmd ); } curJoint++; if(curJoint>=numJoints) { procJoints=0;//exit process joint mode } } //enter process joint mode if (!strncmp(msgtag, "joints",6)) { procJoints=1;//1 for true curJoint=0; } } return AFUNC_OK; } //Feb 04 2007 // //do bone animation import here as a seperate plugin //will need to import existing anim to existing md5bones imported? //for now do full bone and animation import // 16-May-2007 negate x to fix left right problem // 19-May-2007 done // XCALL_( int ) md5AnimLoad( long version, GlobalFunc *global, LWLayoutGeneric *local, void *serverData ) { static char name[ 256 ], path[ 256 ], node[ 256 ]; LWFileReqLocal frloc; FILE *fp = NULL; char cmd[ 128 ]; int result; char lnbuffer[1024], *msgtag; char seps[] = " \"\t"; // space quote and tab characters char boneName[192][64]; int numJoints, parentIndex[192]; float jx[192], jy[192], jz[192];//joint position in world space float xq[192], yq[192], zq[192], wq[192];//joint orientation quaternion int curJoint, procJoints=0; LWFVector pos = {0.0f}; LWInterfaceInfo *intinfo; LWItemInfo *iteminfo; unsigned int itemID[192],buildID,veryTempHex,tempHex; LWItemID *id, *veryTempID; Quat myQuat; double hpb[3]; EulerAngles genericEul; LWItemType mytype; int numFrames, frameRate, numAnimatedComponents; int procHeir=0, procBase=0; int curframe, curframeP1, frameLine=0, jointAnimCnt; float qx, qy, qz, qw, curXj, curYj, curZj; unsigned int animFlags[192], theFlag; int loadBonesFlag = 1;//0 dont load bones only position them, 1 do load bones unsigned int tempID; LWTime theTime;//frame time at start of import so can load several anims LWSceneInfo *lwsi; int curLWframe; double framesPS; LWFileReqFunc *filereq; #define MAXFILESZ 260 static char fnode[ MAXFILESZ ] = "*.md5anim", fpath[ MAXFILESZ ] = "", fname[ MAXFILESZ ] = ""; LWMessageFuncs *msg; if ( version != LWLAYOUTGENERIC_VERSION ) return AFUNC_BADVERSION; filereq = global( LWFILEREQFUNC_GLOBAL, GFUSE_TRANSIENT ); result = filereq( "Load MD5 Animation", fnode, fpath, fname, MAXFILESZ ); if ( !result ) return AFUNC_OK; fp=fopen(fname,"r"); if(!fp) { return AFUNC_OK; } //check for proper header for md5 mesh file fgets(lnbuffer,1023,fp); if(strncmp(lnbuffer,"MD5Version 10",13)) { fclose(fp); return AFUNC_OK; } // 3 cases // 1 nothing or non mesh selected - make build null // 2 mesh selected - build inside mesh // 3 bone selected - dont add new bones //mesh selected or create build null intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); msg = global( LWMESSAGEFUNCS_GLOBAL, GFUSE_TRANSIENT ); id = intinfo->selItems; theTime = intinfo->curTime; lwsi = global( LWSCENEINFO_GLOBAL, GFUSE_TRANSIENT ); framesPS = lwsi->framesPerSecond; curLWframe = theTime * framesPS;//current lightwave frame mytype = LWI_LIGHT;//initialize to non mesh so if something selected defaults to nothing really selected //something is selected if(id[0]!=NULL) { iteminfo = global( LWITEMINFO_GLOBAL, GFUSE_TRANSIENT ); mytype = iteminfo->type(id[0]); if(mytype==LWI_BONE) { loadBonesFlag=0;//dont load new bones //find the parent tempID = iteminfo->parent(id[0]); while(tempID!=NULL) { buildID = tempID; tempID = iteminfo->parent(buildID); } } //april 29 2007 if(mytype==LWI_OBJECT) { buildID = id[0]; loadBonesFlag=0;//assume bones already exist } } //create build null if nothing selected or non object selected //if(id[0]==NULL || mytype!=LWI_OBJECT) // mytype=LWI_LIGHT means selected item is not object or bone if(id[0]==NULL || mytype==LWI_LIGHT) { //create world space null for building skeleton sprintf( cmd, "AddNull buildNULL"); local->evaluate( local->data, cmd ); //save item id iteminfo = global( LWITEMINFO_GLOBAL, GFUSE_TRANSIENT ); intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); if ( !iteminfo ) return AFUNC_BADGLOBAL; id = intinfo->selItems; buildID = id[0]; } sprintf( cmd, "AddNull veryTempNULL"); local->evaluate( local->data, cmd ); id = intinfo->selItems; veryTempID = id; veryTempHex = id[0]; //get weight maps in scene //objfunc = global( LWOBJECTFUNCS_GLOBAL, GFUSE_TRANSIENT ); //numWeights = objfunc->numVMaps( LWVMAP_WGHT ); //loop through file sprintf( cmd, "LocalCoordinateSystem"); local->evaluate( local->data, cmd ); while (!feof(fp)) { fgets(lnbuffer,1023,fp); msgtag = strtok(lnbuffer,seps); if (!strncmp(msgtag, "numFrames",9)) sscanf(strtok(NULL,seps),"%i",&numFrames); if (!strncmp(msgtag, "numJoints",9)) sscanf(strtok(NULL,seps),"%i",&numJoints); if (!strncmp(msgtag, "frameRate",9)) { sscanf(strtok(NULL,seps),"%i",&frameRate); if(framesPS != frameRate) { if(msg->okCancel( "MD5 Animation Import", "Frame rate of Lightwave doesn't match the file", "Press OK to change Lightwave's frame rate or Cancel to leave as is." )) { framesPS = frameRate; sprintf( cmd, "FramesPerSecond %i", frameRate); local->evaluate( local->data, cmd ); } } } if (!strncmp(msgtag, "numAnimatedComponents",21)) sscanf(strtok(NULL,seps),"%i",&numAnimatedComponents); //build bone heirarchy //all will be added at origin with zero roation if(procHeir) { strcpy(boneName[curJoint],msgtag); msgtag = strtok(NULL,seps); sscanf(msgtag,"%i",&parentIndex[curJoint]);//parent index msgtag = strtok(NULL,seps); sscanf(msgtag,"%i",&animFlags[curJoint]);//animation flag //load the bones in if(loadBonesFlag==1) { sprintf( cmd, "SelectItem %x", buildID); local->evaluate( local->data, cmd ); sprintf( cmd, "AddBone %s",boneName[curJoint]); local->evaluate( local->data, cmd ); //save item id for bone intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); id = intinfo->selItems; itemID[curJoint] = id[0]; //parent the bone if(curJoint != 0) { sprintf( cmd, "ParentItem %x",itemID[parentIndex[curJoint]]);//hex number local->evaluate( local->data, cmd ); } } //position existing bones else { //create and delete null so nothing selected sprintf( cmd, "SelectItem %x", veryTempHex); local->evaluate( local->data, cmd ); sprintf( cmd, "SelectByName %s",boneName[curJoint]); local->evaluate( local->data, cmd ); intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); id = intinfo->selItems; tempHex = id[0]; //bone not exist so create it if(tempHex==veryTempHex) //if(id[0]==NULL) { sprintf( cmd, "SelectItem %x", buildID); local->evaluate( local->data, cmd ); sprintf( cmd, "AddBone %s",boneName[curJoint]); local->evaluate( local->data, cmd ); intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); id = intinfo->selItems; //parent the bone if(curJoint != 0) { sprintf( cmd, "ParentItem %x",itemID[parentIndex[curJoint]]);//hex number local->evaluate( local->data, cmd ); } } // save bone id itemID[curJoint] = id[0]; } curJoint++; if(curJoint>=numJoints) { procHeir=0;//exit process joint heir mode } } //process the base frame //move and rotate bones into place if(procBase) { //select the current bone sprintf( cmd, "SelectItem %x",itemID[curJoint]);//hex number local->evaluate( local->data, cmd ); msgtag = strtok(NULL,seps);// ( sscanf(msgtag,"%f",&jx[curJoint]);//joint xyz msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&jy[curJoint]); msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&jz[curJoint]); msgtag = strtok(NULL,seps);// ) pos[0]=jx[curJoint]; pos[1]=jy[curJoint]; pos[2]=jz[curJoint]; //joint angles msgtag = strtok(NULL,seps);// ( msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&xq[curJoint]);//joint quat xyz msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&yq[curJoint]); msgtag = strtok(NULL,seps); sscanf(msgtag,"%f",&zq[curJoint]); //calculate w component of quat wq[curJoint] = 1.0 - xq[curJoint]*xq[curJoint] - yq[curJoint]*yq[curJoint] - zq[curJoint]*zq[curJoint]; if(wq[curJoint]<0.0) wq[curJoint]=0.0; else wq[curJoint] = -sqrt(wq[curJoint]); //convert quaternions to HPB(rotation in XYZ) // need quat to euler function myQuat.x = xq[curJoint]; myQuat.y = yq[curJoint]; myQuat.z = zq[curJoint]; myQuat.w = wq[curJoint]; genericEul = Eul_FromQuat(myQuat, EulOrdYXZr);//good -w NAILED IT PERFECT hpb[0] = RADtoDEG*genericEul.x; hpb[1] = RADtoDEG*genericEul.y; hpb[2] = RADtoDEG*genericEul.z; sprintf( cmd, "Rotation %f %f %f",hpb[0], hpb[1], hpb[2]); local->evaluate( local->data, cmd ); sprintf( cmd, "Position %f %f %f",pos[0], pos[1], pos[2]); local->evaluate( local->data, cmd ); curJoint++; if(curJoint>=numJoints) { procBase=0;//exit process base frame mode } } //enter process joint hierarchy mode if (!strncmp(msgtag, "hierarchy",9)) { procHeir=1;//1 for true curJoint=0; } //enter process base frame mode if (!strncmp(msgtag, "baseframe",9)) { procBase=1;//1 for true curJoint=0; } //process animation frames if(frameLine==1 && !strncmp(msgtag,"}",1)) frameLine=0;//exit frame line mode if end of frame if(frameLine==1) { //get first joint with animation(anything but zero means joint animated) while(animFlags[jointAnimCnt]==0) { jointAnimCnt++; } //initialize position to base position - all anim relative to base position curXj = jx[jointAnimCnt]; curYj = jy[jointAnimCnt]; curZj = jz[jointAnimCnt]; //current joint xyz //next process the variable number of items //at this point msgtag is already the first item theFlag = animFlags[jointAnimCnt]; //order of anim info max is tx ty tz qx qy qz //flag mask defined in reverse 1=tx 2=ty 4=tz 8=qx 16=qy 32=qz if((theFlag & 1)==1) { sscanf(msgtag,"%f",&curXj); msgtag = strtok(NULL,seps);//move to next value in line } if((theFlag & 2)==2) { sscanf(msgtag,"%f",&curYj); msgtag = strtok(NULL,seps);//move to next value in line } if((theFlag & 4)==4) { sscanf(msgtag,"%f",&curZj); msgtag = strtok(NULL,seps);//move to next value in line } pos[0]=curXj; pos[1]=curYj; pos[2]=curZj; //initialize quat to original values - animation rel to base rotation qx = xq[jointAnimCnt]; qy = yq[jointAnimCnt]; qz = zq[jointAnimCnt]; if((theFlag & 8)==8) { sscanf(msgtag,"%f",&qx); msgtag = strtok(NULL,seps);//move to next value in line } if((theFlag & 16)==16) { sscanf(msgtag,"%f",&qy); msgtag = strtok(NULL,seps);//move to next value in line } if((theFlag & 32)==32) { sscanf(msgtag,"%f",&qz); msgtag = strtok(NULL,seps);//move to next value in line } //calculate w component of quat qw = 1.0 - qx*qx - qy*qy - qz*qz; if(qw<0.0) qw=0.0; else qw = -sqrt(qw); //get euler from quat myQuat.x = qx; myQuat.y = qy; myQuat.z = qz; myQuat.w = qw; genericEul = Eul_FromQuat(myQuat, EulOrdYXZr); hpb[0] = RADtoDEG*genericEul.x; hpb[1] = RADtoDEG*genericEul.y; hpb[2] = RADtoDEG*genericEul.z; //select the current bone sprintf( cmd, "SelectItem %x",itemID[jointAnimCnt]);//hex number local->evaluate( local->data, cmd ); sprintf( cmd, "Rotation %f %f %f",hpb[0], hpb[1], hpb[2]); local->evaluate( local->data, cmd ); sprintf( cmd, "Position %f %f %f",pos[0], pos[1], pos[2]); local->evaluate( local->data, cmd ); jointAnimCnt++;//move to next joint msgtag = "blahblahblah";//needed so bad msgtag doesn't crash lightwave at next test below } // do this later after baseframe is working and math is verified //set frame flag if (!strncmp(msgtag,"frame",5) && strncmp(msgtag, "frameRate",9)) { sscanf(strtok(NULL,seps),"%i",&curframe); frameLine = 1;//start frame line mode jointAnimCnt = 0;//counter for joints //assuming autokey is on do this then move the joints curframeP1 = curframe + curLWframe;//may 18, frame zero is same as base frame sprintf( cmd, "GoToFrame %i",curframeP1); local->evaluate( local->data, cmd ); } } return AFUNC_OK; } // // *********** EXPORT MESH DATA ************** // // 19-May-2007 done LWMeshInfo *mesh; char *tagname; FILE *fp; int j,k; void *curUVmap, *curWmap; LWObjectFuncs *objfunc; LWPntID pointXref[8192]; int numverts, numweights; int countWeight[8192];//number of weights for each point LWItemInfo *iteminfo; LWInterfaceInfo *intinfo; char boneName[192][64]; LWItemID jID[192];//lw bone id save int totalBoneCount; unsigned int pointBone[8192];//no 1 point many bones GlobalFunc *myglobal; unsigned int calcNullHex; LWItemID *calcPointer; float saveweight[8192]; int goodUV; static int getSurfName( char *name, LWPolID polygonID ) { tagname = mesh->polTag( mesh, polygonID, LWPTAG_SURF ); return 0; } static int testUVname( char *name, LWPntID pointID ) { float val[2]; int ismapped; mesh->pntVSelect( mesh, curUVmap ); ismapped = mesh->pntVGet( mesh, pointID, val ); if ( ismapped ) { goodUV=1; return 1; } return 0; } static int writeUVdata( char *name, LWPntID pointID ) { float val[2], wt[1]; int vmapcount, i; float tempweight; //select the uv map mesh->pntVSelect( mesh, curUVmap ); mesh->pntVGet( mesh, pointID, val ); fprintf(fp, "\tvert %i ( %f %f ) ",k,val[0], 1.0 - val[1]);//v is reversed for doom3 // need weightmap info - first count for each point //check point against each weightmap and create a count vmapcount = objfunc->numVMaps( LWVMAP_WGHT ); countWeight[k] = 0; tempweight = 0; for(i=0;ivmapName( LWVMAP_WGHT, i ); curWmap = mesh->pntVLookup( mesh, LWVMAP_WGHT, name ); if(curWmap) { //determine if vmap assigned to this point if so increment //select the weight map mesh->pntVSelect( mesh, curWmap ); if(mesh->pntVGet( mesh, pointID, wt )) { tempweight = tempweight + wt[0]; countWeight[k]++; } } } saveweight[k] = tempweight;//save sum of weights on point for normalizing fprintf(fp, "%i %i\n",j,countWeight[k]); j=j+countWeight[k]; numweights=j; //save pointXref pointXref[k] = pointID; k++; return 0; } static int writeTris( char *name, LWPolID polygonID ) { LWPntID pointID[3]; int i,m; fprintf(fp, "\ttri %i",k); //reverse order fix inside out for(i=2;i>-1;i--) { pointID[i] = mesh->polVertex( mesh, polygonID, i ); m=0; while(mpntBasePos( mesh, pointID, pos ); sprintf( cmd, "SelectByName calcNULL"); local->evaluate( local->data, cmd ); sprintf( cmd, "Position %f %f %f",pos[0], pos[1], pos[2]); local->evaluate( local->data, cmd ); //for each weight parent the null to corresponding bone then read position in parent space //note parent in place must be on vmapcount = objfunc->numVMaps( LWVMAP_WGHT ); for(i=0;ivmapName( LWVMAP_WGHT, i ); curWmap = mesh->pntVLookup( mesh, LWVMAP_WGHT, name ); if(curWmap) { //determine if vmap assigned to this point //if so then get bone and output data //select the weight map tempInt = mesh->pntVSelect( mesh, curWmap ); if(mesh->pntVGet( mesh, pointID, wt )) { //weight found for point //get bone from name sprintf( cmd, "SelectByName %s",name); local->evaluate( local->data, cmd ); jchk=0; while(jchkevaluate( local->data, cmd ); sprintf( cmd, "ParentItem %x",itemID);//hex number local->evaluate( local->data, cmd ); //now read null rel to parent iteminfo->param( calcNullHex, LWIP_POSITION, 0, rt );//item,pos rel to parent,time,vector normweight = wt[0]/saveweight[j]; fprintf(fp, "\tweight %i %i %f ( %f %f %f ) \n",k,jchk,normweight,rt[0],rt[1],rt[2]); //child is selected and... sprintf( cmd, "ParentItem 0");//un parent local->evaluate( local->data, cmd ); k++; } } } j++; return 0; } /* ====================================================================== geomSave() Activation function for the generic. This will save the md5mesh file ====================================================================== */ // 19-May-2007 done #define MESH_FILENAME "LWmesh.md5mesh" /* output file for the generic */ XCALL_( int ) geomSave( long version, GlobalFunc *global, LWLayoutGeneric *local, void *serverData ) { LWMessageFuncs *msg; LWSceneInfo *scninfo; LWObjectInfo *objinfo; LWItemID objid, boneid,parboneid,tempid; int meshCount, numPoly, boneCount; int i, parentIndex[192]; char *name; LWDVector wpos,rot; int t;//time int vmapcount; char cmd[ 128 ]; unsigned int genintflags; EulerAngles myea; Quat myQuat; LWFileReqFunc *filereq; #define MAXFILESZ 260 static char fnode[ MAXFILESZ ] = "*.md5mesh", fpath[ MAXFILESZ ] = "", fname[ MAXFILESZ ] = ""; int result; LWItemID boneobj;//id of object that has the bones int myAlertLevel; char lnbuffer[256]; /* get some globals */ msg = global( LWMESSAGEFUNCS_GLOBAL, GFUSE_TRANSIENT ); iteminfo = global( LWITEMINFO_GLOBAL, GFUSE_TRANSIENT ); scninfo = global( LWSCENEINFO_GLOBAL, GFUSE_TRANSIENT ); objinfo = global( LWOBJECTINFO_GLOBAL, GFUSE_TRANSIENT ); intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); objfunc = global( LWOBJECTFUNCS_GLOBAL, GFUSE_TRANSIENT ); if ( !msg || !iteminfo || !scninfo || !objinfo || !intinfo || !objfunc) return AFUNC_BADGLOBAL; myglobal = global; /* make sure there are objects in the scene */ if ( scninfo->numPoints <= 0 ) { msg->error( "No objects in the scene.", NULL ); return AFUNC_OK; } if(!msg->okCancel( "MD5 Mesh Export", "Mesh Export can take over 1 minute to complete", "Press OK to continue" )) return AFUNC_OK; myAlertLevel = intinfo->alertLevel;// get alert level //sprintf( cmd, "AlertLevel 2");//set alert level low //local->evaluate( local->data, cmd ); //get parentinplace flag and set it intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); genintflags = intinfo->generalFlags; genintflags &= LWGENF_PARENTINPLACE; if(genintflags==0) local->evaluate( local->data, "ParentInPlace" ) ; //create world space null for skeleton calculations sprintf( cmd, "AddNull calcNULL"); local->evaluate( local->data, cmd ); //save item id iteminfo = global( LWITEMINFO_GLOBAL, GFUSE_TRANSIENT ); intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); if ( !iteminfo ) return AFUNC_BADGLOBAL; calcPointer = intinfo->selItems; calcNullHex = calcPointer[0]; // check for any mesh with bones meshCount = 0; objid = iteminfo->first( LWI_OBJECT, 0 );//first object while ( objid ) { numPoly = objinfo->numPolygons( objid ); if ( numPoly > 0) { boneid = iteminfo->first( LWI_BONE, objid );// get first bone of first polygon object if(boneid) { meshCount++; boneobj = objid;//save id of object with bones } } objid = iteminfo->next( objid ); } if(meshCount==0) { msg->error("No meshes in scene", NULL);//at least one mesh with bones needed return AFUNC_OK; } //get actual number of meshes in scene meshCount = 0; objid = iteminfo->first( LWI_OBJECT, 0 );//first object while ( objid ) { numPoly = objinfo->numPolygons( objid ); if ( numPoly > 0) { meshCount++; } objid = iteminfo->next( objid ); } /* open the output file */ filereq = global( LWFILEREQFUNC_GLOBAL, GFUSE_TRANSIENT ); result = filereq( "Save MD5 Mesh", fnode, fpath, fname, MAXFILESZ ); if ( !result ) return AFUNC_OK; fp = fopen( fname, "w" ); //fp = fopen( MESH_FILENAME, "w" ); if ( !fp ) { msg->error( "Couldn't open output file", fname ); //msg->error( "Couldn't open output file", MESH_FILENAME ); return AFUNC_OK; } fprintf( fp, "MD5Version 10\ncommandline \"\"\n\n"); // header //now find number of joints in skeleton totalBoneCount=0; //objid = iteminfo->first( LWI_OBJECT, 0 );//first object objid = boneobj; boneid = iteminfo->first( LWI_BONE, objid );// get first bone of first polygon object while(boneid) { totalBoneCount++; boneid = iteminfo->next( boneid ); } fprintf( fp, "numJoints %i\n",totalBoneCount); fprintf(fp, "numMeshes %i\n",meshCount); fprintf(fp,"\njoints {\n"); //build joint hierarchy info //assume 1st skeleton same for all meshes objid = boneobj; t=0; boneid = iteminfo->first( LWI_BONE, objid );// get first bone of first polygon object //save id and name boneCount=0; parentIndex[boneCount]=-1; name = iteminfo->name( boneid ); strcpy(boneName[boneCount],name);//save bone name fprintf(fp, "\t\"%s\"",name); fprintf(fp, "\t-1"); iteminfo->param( boneid, LWIP_W_POSITION, t, wpos );//get world pos of root bone fprintf(fp, " ( %f %f %f )",wpos[0],wpos[1],wpos[2]); //get world rotation local->evaluate( local->data, "ParentInPlace" ) ;//turn off sprintf( cmd, "SelectByName calcNULL");//select null local->evaluate( local->data, cmd ); sprintf( cmd, "ParentItem %x",boneid);//hex number local->evaluate( local->data, cmd ); local->evaluate( local->data, "ParentInPlace" ) ;//turn on sprintf( cmd, "ParentItem 0");//unparent to read world rotation local->evaluate( local->data, cmd ); iteminfo->param( calcNullHex, LWIP_ROTATION, 0, rot );//ROTATION in rads //reset calcNULL sprintf( cmd, "Rotation 0 0 0"); local->evaluate( local->data, cmd ); sprintf( cmd, "Position 0 0 0"); local->evaluate( local->data, cmd ); myea.x = rot[0]; myea.y = rot[1]; myea.z = rot[2]; myea.w = EulOrdYXZr; myQuat = Eul_ToQuat(myea); fprintf(fp, " ( %f %f %f )",-myQuat.x, -myQuat.y ,-myQuat.z);//negative so match doom3 fprintf(fp, "\t\t// \n"); jID[boneCount] = boneid;//save bone id //try non recursive build - maybe already "in order" boneid = iteminfo->next( boneid ); while(boneid) { boneCount++; name = iteminfo->name( boneid ); strcpy(boneName[boneCount],name);//save bone name jID[boneCount] = boneid;//save bone id //find parent parentIndex[boneCount] = -1; parboneid = iteminfo->parent(boneid); for(i=0;iparam( boneid, LWIP_W_POSITION, t, wpos );//get world pos of bone fprintf(fp, " ( %f %f %f )",wpos[0],wpos[1],wpos[2]); local->evaluate( local->data, "ParentInPlace" ) ;//turn off sprintf( cmd, "SelectByName calcNULL");//select null local->evaluate( local->data, cmd ); sprintf( cmd, "ParentItem %x",boneid);//hex number local->evaluate( local->data, cmd ); local->evaluate( local->data, "ParentInPlace" ) ;//turn on sprintf( cmd, "ParentItem 0");//unparent to read world rotation local->evaluate( local->data, cmd ); iteminfo->param( calcNullHex, LWIP_ROTATION, 0, rot );//ROTATION in rads //reset calcNULL sprintf( cmd, "Rotation 0 0 0"); local->evaluate( local->data, cmd ); sprintf( cmd, "Position 0 0 0"); local->evaluate( local->data, cmd ); myea.x = rot[0]; myea.y = rot[1]; myea.z = rot[2]; myea.w = EulOrdYXZr; myQuat = Eul_ToQuat(myea); fprintf(fp, " ( %f %f %f )", -myQuat.x, -myQuat.y , -myQuat.z);//negative so match doom3 tempid = jID[parentIndex[boneCount]];//parent id name = iteminfo->name( tempid ); fprintf(fp, "\t\t// %s\n",name); boneid = iteminfo->next( boneid ); } fprintf(fp,"}\n\n"); //now write mesh info objid = iteminfo->first( LWI_OBJECT, 0 );//first object t=0; while ( objid ) { numPoly = objinfo->numPolygons( objid ); if ( numPoly > 0) { //do mesh info here name = iteminfo->name( objid ); //get layer name for mesh split by ":" fprintf(fp,"\nmesh {\n"); tagname = strpbrk(name, ":");//check for ":" in name if(tagname==NULL) { fprintf(fp, "\t// meshes: %s\n",name); sprintf( lnbuffer, "Writing mesh %s",name); msg->info( lnbuffer, NULL ); } else { tagname = strtok(name,":");//mesh name tagname = strtok(NULL,":");//layer name fprintf(fp, "\t// meshes: %s\n",tagname); sprintf( lnbuffer, "Writing mesh %s",tagname); msg->info( lnbuffer, NULL ); } //surface name into tagname mesh = objinfo->meshInfo( objid, 1 ); mesh->scanPolys( mesh, getSurfName, name );//pass name because have to pass something(i guess) fprintf(fp, "\tshader \"%s\"\n\n",tagname); //write all mesh data //numverts numverts = objinfo->numPoints( objid ); fprintf(fp, "\tnumverts %i\n",numverts); //uv info from meshinfo? //HOW GET UVMAP FOR THE MESH /* create a list of uv map names using scene objects pntvlookup will return null except where mesh and uvmap are associated meshinfo poltag will give surface name for a polygon looks like for md5mesh only 1 surface allowed 1 uvmap = 1 surface */ //find uvmap name in mesh vmapcount = objfunc->numVMaps( LWVMAP_TXUV ); i=0; goodUV = 0;//this uv not on curmesh points while(ivmapName( LWVMAP_TXUV, i ); curUVmap = mesh->pntVLookup( mesh, LWVMAP_TXUV, name ); /* the only way to determine whether a vmap affects a given object is to test all of its vertices, using the Mesh Info pntVGet function */ mesh->scanPoints( mesh, testUVname, name ); if(goodUV==1) break; i++; } if(i==vmapcount) { fprintf(fp,"failed to find vmap\n"); break; } //note: uvmap can exist without surface assignment //uv map is independent of the surface //weight map names must match bone names //values of weight maps must be normalized for export(add to 1.0 or 100%?) //select the uvmap //mesh->pntVSelect( mesh, curUVmap ); //read uv data pntVGet( meshinfo, point, val ) j=0;//initialize index counter k=0;//point index counter numweights=0;//init count to zero mesh->scanPoints( mesh, writeUVdata, name ); fprintf(fp,"\n"); //triangles now fprintf(fp, "\tnumtris %i\n",numPoly); k=0;//tri index counter mesh->scanPolys( mesh, writeTris, name ); //weights here //"weight" weightIndex joint bias ( pos.x pos.y pos.z ) //pos rel to bone //finalPos = (weight[0].pos * weight[0].bias) + ... + (weight[N].pos * weight[N].bias) //this is the hard part //maybe not new info indicates otherwise fprintf(fp, "\n\tnumweights %i\n",numweights); k=0;//weight counter j=0;//point counter mesh->scanPoints( mesh, writeWeights, local); fprintf(fp,"}\n\n");//end of current mesh } objid = iteminfo->next( objid );//next mesh } fclose( fp ); sprintf( cmd, "SelectByName calcNULL");//select calculation null local->evaluate( local->data, cmd ); sprintf( cmd, "ClearSelected");//delete calc null local->evaluate( local->data, cmd ); sprintf( cmd, "AlertLevel %i", myAlertLevel);//reset alert level local->evaluate( local->data, cmd ); return AFUNC_OK; } /* ====================================================================== animSave() Activation function for the generic. This will save the md5 animation file when set preview range do not include frame zero frame zero reserved for baseframe ====================================================================== */ // 19-May-2007 done #define ANIM_FILENAME "LWbones.md5anim" /* output file for the generic */ XCALL_( int ) animSave( long version, GlobalFunc *global, LWLayoutGeneric *local, void *serverData ) { int numFrames, frameRate, numAnimatedComponents, boneCount, parentIndex[192],i,t; LWMessageFuncs *msg; LWItemID objid, boneid,parboneid,tempID, rootID; LWItemID *id; char *name; LWItemType mytype; char cmd[ 128 ]; LWDVector wpos,rot; EulerAngles myea; Quat myQuat; int txaf[192], tyaf[192], tzaf[192], qxaf[192], qyaf[192], qzaf[192];//anim flag(no bool type in c) float baseX[192], baseY[192], baseZ[192], baseQX[192], baseQY[192], baseQZ[192];//baseframe values int lastFrame, firstFrame, animFlags, animCcount; LWSceneInfo *lwsi; double framesPS; LWTime theTime;//in seconds float minX, minY, minZ, maxX, maxY, maxZ; LWFileReqFunc *filereq; #define MAXFILESZ 260 static char fnode[ MAXFILESZ ] = "*.md5anim", fpath[ MAXFILESZ ] = "", fname[ MAXFILESZ ] = ""; int result; iteminfo = global( LWITEMINFO_GLOBAL, GFUSE_TRANSIENT ); intinfo = global( LWINTERFACEINFO_GLOBAL, GFUSE_TRANSIENT ); msg = global( LWMESSAGEFUNCS_GLOBAL, GFUSE_TRANSIENT ); id = intinfo->selItems; //something is selected if(id[0]==NULL) return AFUNC_OK;//nothing selected rootID = NULL; objid = NULL; boneid = id[0]; mytype = iteminfo->type(id[0]); if(mytype!=LWI_OBJECT && mytype!=LWI_BONE) return AFUNC_OK;//not mesh or bone return if(mytype==LWI_OBJECT) { objid = id[0]; boneid = iteminfo->first( LWI_BONE, objid );// get first bone if(boneid==NULL) return AFUNC_OK;//mesh without bones return } rootID = boneid; //find the root bone and mesh object tempID = iteminfo->parent(boneid); while(tempID!=NULL) { mytype = iteminfo->type(tempID); if(mytype==LWI_BONE) rootID = tempID; if(mytype==LWI_OBJECT) objid = tempID; tempID = iteminfo->parent(tempID); } /* open the output file */ filereq = global( LWFILEREQFUNC_GLOBAL, GFUSE_TRANSIENT ); result = filereq( "Save MD5 Animation", fnode, fpath, fname, MAXFILESZ ); if ( !result ) return AFUNC_OK; fp = fopen( fname, "w" ); if ( !fp ) { msg->error( "Couldn't open output file", fname ); //msg->error( "Couldn't open output file", MESH_FILENAME ); return AFUNC_OK; } fprintf( fp, "MD5Version 10\ncommandline \"\"\n\n"); // header //find or get number of frames firstFrame = intinfo->previewStart; lastFrame = intinfo->previewEnd; numFrames = lastFrame - firstFrame + 1;//may 18 basepose==frame 0 fprintf(fp, "numFrames %i\n",numFrames); lwsi = global( LWSCENEINFO_GLOBAL, GFUSE_TRANSIENT ); framesPS = lwsi->framesPerSecond; frameRate = framesPS; //now find number of joints in skeleton totalBoneCount=0; boneid = iteminfo->first( LWI_BONE, objid );// get first bone of first polygon object while(boneid) { totalBoneCount++; boneid = iteminfo->next( boneid ); } fprintf( fp, "numJoints %i\n",totalBoneCount); fprintf( fp, "frameRate %i\n",frameRate); //clear animation flags and read base values sprintf( cmd, "GoToFrame 0");//first frame is base frame local->evaluate( local->data, cmd ); boneid = iteminfo->first( LWI_BONE, objid ); // //precompute values - name, parentindex, id, baseframe values // boneCount=0; parentIndex[boneCount]=-1; name = iteminfo->name( rootID ); strcpy(boneName[boneCount],name);//save bone name //rest of bones jID[boneCount] = rootID;//save bone id iteminfo->param( jID[boneCount], LWIP_POSITION, 0, wpos );//get pos of bone rel parent at frame 0 baseX[boneCount] = wpos[0]; baseY[boneCount] = wpos[1]; baseZ[boneCount] = wpos[2]; iteminfo->param( jID[boneCount], LWIP_ROTATION, 0, rot );//ROTATION in rads myea.x = rot[0]; myea.y = rot[1]; myea.z = rot[2]; myea.w = EulOrdYXZr; myQuat = Eul_ToQuat(myea); //only do doom3 negative when write to file baseQX[boneCount] = myQuat.x; baseQY[boneCount] = myQuat.y; baseQZ[boneCount] = myQuat.z; boneid = iteminfo->next( rootID ); while(boneid) { boneCount++; name = iteminfo->name( boneid ); strcpy(boneName[boneCount],name);//save bone name jID[boneCount] = boneid;//save bone id //find parent parentIndex[boneCount] = -1; parboneid = iteminfo->parent(boneid); for(i=0;iparam( jID[boneCount], LWIP_POSITION, 0, wpos );//get pos of bone rel parent at frame 0 baseX[boneCount] = wpos[0]; baseY[boneCount] = wpos[1]; baseZ[boneCount] = wpos[2]; iteminfo->param( jID[boneCount], LWIP_ROTATION, 0, rot );//ROTATION in rads myea.x = rot[0]; myea.y = rot[1]; myea.z = rot[2]; myea.w = EulOrdYXZr; myQuat = Eul_ToQuat(myea); //only do doom3 negative when write to file baseQX[boneCount] = myQuat.x; baseQY[boneCount] = myQuat.y; baseQZ[boneCount] = myQuat.z; boneid = iteminfo->next( boneid ); } // //end precompute values // //clear anim flags for(i=0;iparam( jID[i], LWIP_POSITION, theTime, wpos );//get pos of bone rel parent iteminfo->param( jID[i], LWIP_ROTATION, theTime, rot );//ROTATION in rads myea.x = rot[0]; myea.y = rot[1]; myea.z = rot[2]; myea.w = EulOrdYXZr; myQuat = Eul_ToQuat(myea); if(fabs(wpos[0]-baseX[i])>0.00001) txaf[i]=1; if(fabs(wpos[1]-baseY[i])>0.00001) tyaf[i]=1; if(fabs(wpos[2]-baseZ[i])>0.00001) tzaf[i]=1; if(fabs(myQuat.x-baseQX[i])>0.00001) qxaf[i]=1; if(fabs(myQuat.y-baseQY[i])>0.00001) qyaf[i]=1; if(fabs(myQuat.z-baseQZ[i])>0.00001) qzaf[i]=1; } } //count number of animated components numAnimatedComponents = 0; for(i=0;inext( rootID ); while(boneid) { boneCount++; fprintf(fp, "\t\"%s\"",boneName[boneCount]); fprintf(fp, "\t%i",parentIndex[boneCount]); animFlags = 32*qzaf[boneCount] + 16*qyaf[boneCount] + 8*qxaf[boneCount]; animFlags = animFlags + 4*tzaf[boneCount] + 2*tyaf[boneCount] + txaf[boneCount]; fprintf(fp, " %i",animFlags);//flag if(animFlags==0) fprintf(fp, " 0");//zero start index else fprintf(fp, " %i",animCcount);//start index animCcount = animCcount + qzaf[boneCount] + qyaf[boneCount] + qxaf[boneCount]; animCcount = animCcount + tzaf[boneCount] + tyaf[boneCount] + txaf[boneCount]; fprintf(fp, "\t// %s",boneName[parentIndex[boneCount]]);//parent name and listed animated components for the bone //human readable flags if(animFlags != 0) { fprintf(fp, " ( "); if(txaf[boneCount]==1) fprintf(fp, "Tx "); if(tyaf[boneCount]==1) fprintf(fp, "Ty "); if(tzaf[boneCount]==1) fprintf(fp, "Tz "); if(qxaf[boneCount]==1) fprintf(fp, "Qx "); if(qyaf[boneCount]==1) fprintf(fp, "Qy "); if(qzaf[boneCount]==1) fprintf(fp, "Qz "); fprintf(fp, ")"); } fprintf(fp, "\n"); boneid = iteminfo->next( boneid ); } fprintf(fp, "}\n");//end heirarchy fprintf(fp, "\nbounds {\n");//bounding box for each frame for(t=firstFrame;t<=lastFrame;t++) { minX=999999; minY=9999999; minZ=9999999; maxX=-99999; maxY=-999999; maxZ=-999999; theTime = t/framesPS; for(i=0;iparam( jID[i], LWIP_W_POSITION, theTime, wpos );//get pos of bone world? if(wpos[0]maxX) maxX = wpos[0]; if(wpos[1]>maxY) maxY = wpos[1]; if(wpos[2]>maxZ) maxZ = wpos[2]; } fprintf(fp, "\t( %f %f %f )",minX, minY, minZ); fprintf(fp, " ( %f %f %f )\n",maxX, maxY, maxZ); } fprintf(fp, "}\n");//end bounds fprintf(fp, "\nbaseframe {\n"); //t=0; //sprintf( cmd, "GoToFrame 0");//first frame is base frame //local->evaluate( local->data, cmd ); for(i=0;iparam( jID[j], LWIP_POSITION, theTime, wpos );//get pos of bone rel parent iteminfo->param( jID[j], LWIP_ROTATION, theTime, rot );//ROTATION in rads myea.x = rot[0]; myea.y = rot[1]; myea.z = rot[2]; myea.w = EulOrdYXZr; myQuat = Eul_ToQuat(myea); fprintf(fp, "\t "); if(txaf[j]==1) fprintf(fp, "%f ", wpos[0]); if(tyaf[j]==1) fprintf(fp, "%f ", wpos[1]); if(tzaf[j]==1) fprintf(fp, "%f ", wpos[2]); //flipping problem - solution? if(myQuat.w > 0) { if(qxaf[j]==1) fprintf(fp, "%f ", -myQuat.x); if(qyaf[j]==1) fprintf(fp, "%f ", -myQuat.y); if(qzaf[j]==1) fprintf(fp, "%f ", -myQuat.z); } else { if(qxaf[j]==1) fprintf(fp, "%f ", myQuat.x); if(qyaf[j]==1) fprintf(fp, "%f ", myQuat.y); if(qzaf[j]==1) fprintf(fp, "%f ", myQuat.z); } //fprintf(fp," quatZ=%f",myQuat.w);//test fprintf(fp, "\n"); } } fprintf(fp, "}\n");//end current frame } fprintf(fp, "\n");//end of file fclose( fp ); return AFUNC_OK; } //list of plugins with optional username (NULL here) ServerRecord ServerDesc[] = { { LWOBJECTIMPORT_CLASS, "MD5_MeshLoad", geomLoad }, { LWLAYOUTGENERIC_CLASS, "MD5_BoneLoad", boneLoad }, { LWLAYOUTGENERIC_CLASS, "MD5_AnimLoad", md5AnimLoad }, { LWLAYOUTGENERIC_CLASS, "MD5_MeshSave", geomSave }, { LWLAYOUTGENERIC_CLASS, "MD5_AnimSave", animSave }, { NULL } };