/* Copyright (C) 2010 Matthew Baranowski, Sander van Rossen & Raven software. 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 "system.h" #include "ndictionary.h" #include "md3gl.h" #include "md3view.h" #include "targa.h" #include #include "animation.h" #include "oddbits.h" #include "text.h" #include "matcomp.h" /* sets up an orthogonal projection matrix */ void set_windowSize( int x, int y ) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (y > 0) gluPerspective( mdview.dFOV, (double)x/(double)y, NEAR_GL_PLANE, FAR_GL_PLANE ); glViewport( 0, 0, x, y ); glMatrixMode(GL_MODELVIEW); } /* initializes the opengl state for rendering */ void initialize_glstate() { glClearColor((float)1/((float)256/(float)mdview._R), (float)1/((float)256/(float)mdview._G), (float)1/((float)256/(float)mdview._B), 0.0f); glClearDepth(1.0); glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); glEnable( GL_DEPTH_TEST ); glDepthFunc( GL_LEQUAL ); //GEFORCE glShadeModel( GL_SMOOTH ); // oglStateFlatTextured(); // glFrontFace( mdview.faceSide ); float mat_specular[] = { 1.0, 1.0, 1.0, 1.0 }; float mat_ambient[] = { 0.9f, 0.9f, 0.9f, 1.0 }; float mat_diffuse[] = { 1, 1, 1, 1 }; float mat_shininess[] = { 20.0 }; float light_position[] = { 55.0, -50.0, -5.0, 0.0 }; float light2_position[] = {-50.0, 45.0, 15.0, 0.0 }; float mat2_diffuse[] = { 0.5f, 0.5f, 1, 1 }; //GEFORCE glShadeModel (GL_SMOOTH); glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess); glLightfv(GL_LIGHT0, GL_AMBIENT, mat_ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, mat2_diffuse); glLightfv(GL_LIGHT0, GL_POSITION, light_position); glLightfv(GL_LIGHT1, GL_POSITION, light2_position); glLightfv(GL_LIGHT1, GL_DIFFUSE, mat2_diffuse); glLightfv(GL_LIGHT1, GL_AMBIENT, mat_ambient); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_LIGHT1); glEnable( GL_LIGHTING ); glEnable( GL_BLEND ); glBlendFunc(GL_ONE, GL_ZERO);//bug fix //GEFORCE glEnable( GL_POLYGON_SMOOTH ); oglStateFlatTextured(); } bool bWireFrame = false; bool bTexturesOn = true; // needed for Joe's request, now that we can have both wire & textures at once bool bIsWireFrame(void) { return bWireFrame; } bool bIsTextured(void) { return bTexturesOn; } void oglStateFlatTextured() { glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); glEnable( GL_TEXTURE_2D ); glDisable( GL_LIGHTING ); bWireFrame = false; bTexturesOn = true; } void oglStateShadedTexturedAndWireFrame() { oglStateFlatTextured(); bWireFrame = true; } void oglStateShadedTextured() { glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); glEnable( GL_TEXTURE_2D ); glEnable( GL_LIGHTING ); bWireFrame = false; bTexturesOn = true; } void oglStateShadedFlat() { glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); glDisable( GL_TEXTURE_2D ); glEnable( GL_LIGHTING ); bWireFrame = false; bTexturesOn = true; } void oglStateWireframe() { glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); glDisable( GL_TEXTURE_2D ); glDisable( GL_LIGHTING ); bWireFrame = true; bTexturesOn = false; } /* computes vector cross product */ void cross(Vec3 v1, Vec3 v2, Vec3 c) { c[0] = v1[1]*v2[2] - v1[2]*v2[1]; c[1] = v1[2]*v2[0] - v1[0]*v2[2]; c[2] = v1[0]*v2[1] - v1[1]*v2[0]; } /* makes normal unit length */ void normalize( Vec3 n ) { int i; float length = 0.0f; for (i=0 ; i< 3 ; i++) length += n[i]*n[i]; length = (float)sqrt(length); if (length == 0) return; for (i=0 ; i< 3 ; i++) n[i] /= length; } /* --------------------------------------- new gl code ------------------------------------------------- */ // Copyright 1999-200 Raven Software // shaderCommands_t tess; void RB_SurfaceAnim( md4Surface_t *surface, gl_model *pModel ) // pModel just for anim frame numbers { int i, j, k; float frontlerp, backlerp; int *triangles; int indexes; int baseIndex, baseVertex; int numVerts; md4Vertex_t *v; md4Bone_t bones[MD4_MAX_BONES]; md4Bone_t *bonePtr, *bone; md4Header_t *header; md4Frame_t *frame; md4Frame_t *oldFrame; md4CompFrame_t *cframe; md4CompFrame_t *coldFrame; int frameSize; //===================== tess.numIndexes = 0; tess.numVertexes = 0; ///////////////// tess.shader = shader; ///////////////// tess.fogNum = fogNum; ///////////////// tess.dlightBits = 0; // will be OR'd in by surface functions ///////////////// tess.xstages = shader->stages; ///////////////// tess.numPasses = shader->numUnfoggedPasses; ///////////////// tess.currentStageIteratorFunc = shader->optimalStageIteratorFunc; //===================== int iFrame = pModel->currentFrame; int iNextFrame = GetNextFrame_MultiLocked(pModel, iFrame, 1); iNextFrame = Model_EnsureCurrentFrameLegal(pModel,iNextFrame); // don't lerp if lerping off, or this is the only frame, or the last frame... // if ( !mdview.interpolate || pModel->iNumFrames == 0 || iNextFrame == pModel->iNumFrames // backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) { backlerp = 0; // if backlerp is 0, lerping is off and frontlerp is never used frontlerp = 1; } else { backlerp = mdview.frameFrac; frontlerp = 1.0f - backlerp; } header = (md4Header_t *)((byte *)surface + surface->ofsHeader); bool bCompressed = false; if (header->ofsFrames<0) { // compressed model... // bCompressed = true; frameSize = (int)( &((md4CompFrame_t *)0)->bones[ header->numBones ] ); cframe = (md4CompFrame_t *)((byte *)header + (-header->ofsFrames) + iFrame * frameSize ); coldFrame = (md4CompFrame_t *)((byte *)header + (-header->ofsFrames) + iNextFrame * frameSize ); } else { frameSize = (int)( &((md4Frame_t *)0)->bones[ header->numBones ] ); frame = (md4Frame_t *)((byte *)header + header->ofsFrames + iFrame * frameSize ); oldFrame = (md4Frame_t *)((byte *)header + header->ofsFrames + iNextFrame * frameSize ); } // RB_CheckOverflow( surface->numVerts, surface->numTriangles ); triangles = (int *) ((byte *)surface + surface->ofsTriangles); indexes = surface->numTriangles * 3; baseIndex = tess.numIndexes; baseVertex = tess.numVertexes; for (j = 0 ; j < indexes ; j++) { tess.indexes[baseIndex + j] = baseVertex + triangles[j]; } tess.numIndexes += indexes; // // lerp all the needed bones // if ( !backlerp && !bCompressed) { // no lerping needed bonePtr = frame->bones; } else { bonePtr = bones; if (bCompressed) { for ( i = 0 ; i < header->numBones ; i++ ) { if ( !backlerp ) MC_UnCompress(bonePtr[i].matrix,cframe->bones[i].Comp); else { md4Bone_t tbone[2]; MC_UnCompress(tbone[0].matrix,cframe->bones[i].Comp); MC_UnCompress(tbone[1].matrix,coldFrame->bones[i].Comp); for ( j = 0 ; j < 12 ; j++ ) ((float *)&bonePtr[i])[j] = frontlerp * ((float *)&tbone[0])[j] + backlerp * ((float *)&tbone[1])[j]; } } } else { for ( i = 0 ; i < header->numBones*12 ; i++ ) { ((float *)bonePtr)[i] = frontlerp * ((float *)frame->bones)[i] + backlerp * ((float *)oldFrame->bones)[i]; } } } // // deform the vertexes by the lerped bones // numVerts = surface->numVerts; v = (md4Vertex_t *) ((byte *)surface + surface->ofsVerts); for ( j = 0; j < numVerts; j++ ) { vec3_t tempVert, tempNormal; md4Weight_t *w; VectorClear( tempVert ); VectorClear( tempNormal ); w = v->weights; for ( k = 0 ; k < v->numWeights ; k++, w++ ) { bone = bonePtr + w->boneIndex; tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] ); tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] ); tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] ); tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal ); tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal ); tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal ); } tess.xyz[baseVertex + j][0] = tempVert[0]; tess.xyz[baseVertex + j][1] = tempVert[1]; tess.xyz[baseVertex + j][2] = tempVert[2]; tess.normal[baseVertex + j][0] = tempNormal[0]; tess.normal[baseVertex + j][1] = tempNormal[1]; tess.normal[baseVertex + j][2] = tempNormal[2]; tess.texCoords[baseVertex + j][0][0] = v->texCoords[0]; tess.texCoords[baseVertex + j][0][1] = v->texCoords[1]; v = (md4Vertex_t *)&v->weights[v->numWeights]; } //============================= // now draw it... // { glPushAttrib(GL_POLYGON_BIT); // preserves GL_CULL_FACE && GL_CULL_FACE_MODE if (bIsWireFrame()) { glEnable(GL_CULL_FACE); } for (int iPass=0; iPass<(bIsWireFrame()?2:1); iPass++) { if (bIsWireFrame()) { if (!iPass) { glCullFace(GL_BACK); glColor3f(0.5,0.5,0.5); } else { glCullFace(GL_FRONT); glColor3f( 1,1,1); } } glBegin( GL_TRIANGLES ); { for (int i=0; inumTriangles*3; i+=3) { glNormal3fv ( tess.normal [tess.indexes[i+0]] ); // OutputDebugString(va("x,y = %f,%f\n",tess.texCoords[tess.indexes[i+0]][0][0],tess.texCoords[tess.indexes[i+0]][0][1])); glTexCoord2fv( tess.texCoords[tess.indexes[i+0]][0] ); glVertex3fv ( tess.xyz [tess.indexes[i+0]] ); // OutputDebugString(va("x,y = %f,%f\n",tess.texCoords[tess.indexes[i+1]][0][0],tess.texCoords[tess.indexes[i+1]][0][1])); glTexCoord2fv( tess.texCoords[tess.indexes[i+1]][0] ); glVertex3fv ( tess.xyz [tess.indexes[i+1]] ); // OutputDebugString(va("x,y = %f,%f\n",tess.texCoords[tess.indexes[i+2]][0][0],tess.texCoords[tess.indexes[i+2]][0][1])); glTexCoord2fv( tess.texCoords[tess.indexes[i+2]][0] ); glVertex3fv ( tess.xyz [tess.indexes[i+2]] ); } } glEnd(); } glPopAttrib(); glColor3f( 1,1,1); } //============================= tess.numVertexes += surface->numVerts; pModel->iRenderedTris += surface->numTriangles; pModel->iRenderedVerts+= surface->numVerts; } #if 1 shaderCommands_t* Freeze_Surface( md4Surface_t *surface, int iFrame ) // pModel just for anim frame numbers { int i, j, k; int *triangles; int indexes; int baseIndex, baseVertex; int numVerts; md4Vertex_t *v; md4Bone_t bones[MD4_MAX_BONES]; md4Bone_t *bonePtr, *bone; md4Header_t *header; md4Frame_t *frame; md4CompFrame_t *cframe; int frameSize; //===================== tess.numIndexes = 0; tess.numVertexes = 0; //===================== // int iFrame = pModel->currentFrame; header = (md4Header_t *)((byte *)surface + surface->ofsHeader); bool bCompressed = false; if (header->ofsFrames<0) { // compressed model... // bCompressed = true; frameSize = (int)( &((md4CompFrame_t *)0)->bones[ header->numBones ] ); cframe = (md4CompFrame_t *)((byte *)header + (-header->ofsFrames) + iFrame * frameSize ); } else { frameSize = (int)( &((md4Frame_t *)0)->bones[ header->numBones ] ); frame = (md4Frame_t *)((byte *)header + header->ofsFrames + iFrame * frameSize ); } triangles = (int *) ((byte *)surface + surface->ofsTriangles); indexes = surface->numTriangles * 3; baseIndex = tess.numIndexes; baseVertex = tess.numVertexes; for (j = 0 ; j < indexes ; j++) { tess.indexes[baseIndex + j] = baseVertex + triangles[j]; } tess.numIndexes += indexes; // // lerp all the needed bones // if ( !bCompressed) { bonePtr = frame->bones; } else { bonePtr = bones; for ( i = 0 ; i < header->numBones ; i++ ) { MC_UnCompress(bonePtr[i].matrix,cframe->bones[i].Comp); } } // // deform the vertexes by the lerped bones // numVerts = surface->numVerts; v = (md4Vertex_t *) ((byte *)surface + surface->ofsVerts); for ( j = 0; j < numVerts; j++ ) { vec3_t tempVert, tempNormal; md4Weight_t *w; VectorClear( tempVert ); VectorClear( tempNormal ); w = v->weights; for ( k = 0 ; k < v->numWeights ; k++, w++ ) { bone = bonePtr + w->boneIndex; tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] ); tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] ); tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] ); tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal ); tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal ); tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal ); } tess.xyz[baseVertex + j][0] = tempVert[0]; tess.xyz[baseVertex + j][1] = tempVert[1]; tess.xyz[baseVertex + j][2] = tempVert[2]; tess.normal[baseVertex + j][0] = tempNormal[0]; tess.normal[baseVertex + j][1] = tempNormal[1]; tess.normal[baseVertex + j][2] = tempNormal[2]; tess.texCoords[baseVertex + j][0][0] = v->texCoords[0]; tess.texCoords[baseVertex + j][0][1] = v->texCoords[1]; v = (md4Vertex_t *)&v->weights[v->numWeights]; } //============================= /* // now draw it... // { glBegin( GL_TRIANGLES ); { for (int i=0; inumTriangles*3; i+=3) { glNormal3fv ( tess.normal [tess.indexes[i+0]] ); // OutputDebugString(va("x,y = %f,%f\n",tess.texCoords[tess.indexes[i+0]][0][0],tess.texCoords[tess.indexes[i+0]][0][1])); glTexCoord2fv( tess.texCoords[tess.indexes[i+0]][0] ); glVertex3fv ( tess.xyz [tess.indexes[i+0]] ); // OutputDebugString(va("x,y = %f,%f\n",tess.texCoords[tess.indexes[i+1]][0][0],tess.texCoords[tess.indexes[i+1]][0][1])); glTexCoord2fv( tess.texCoords[tess.indexes[i+1]][0] ); glVertex3fv ( tess.xyz [tess.indexes[i+1]] ); // OutputDebugString(va("x,y = %f,%f\n",tess.texCoords[tess.indexes[i+2]][0][0],tess.texCoords[tess.indexes[i+2]][0][1])); glTexCoord2fv( tess.texCoords[tess.indexes[i+2]][0] ); glVertex3fv ( tess.xyz [tess.indexes[i+2]] ); } } glEnd(); } */ //============================= tess.numVertexes += surface->numVerts; return &tess; } #endif LPCSTR vtos(vec3_t v3) { return va("%.3f %.3f %.3f",v3[0],v3[1],v3[2]); } LPCSTR v2tos(vec2_t v2) { return va("%.3f %.3f",v2[0],v2[1]); } // this is the only actual (MD3) model draw code, the one that everything else calls... // void draw_gl_mesh( gl_mesh *mesh, Vec3 *vecs, gl_model* pModel ) { TriVec *tris; int tri_num, t, v; Vec3 v1, v2, normal; TexVec *tvecs; tris = mesh->triangles; tvecs = mesh->textureCoord; tri_num = mesh->iNumTriangles; pModel->iRenderedTris += mesh->iNumTriangles; pModel->iRenderedVerts+= mesh->iNumVertices; glPushAttrib(GL_POLYGON_BIT); // preserves GL_CULL_FACE && GL_CULL_FACE_MODE if (bIsWireFrame() && !bIsTextured()) { glEnable(GL_CULL_FACE); } for (int iPass=0; iPass<((bIsWireFrame() && !bIsTextured())?2:1); iPass++) { if (bIsWireFrame() && !bIsTextured()) { // standard wireframe... // if (!iPass) { glCullFace(GL_BACK); glColor3f(0.5,0.5,0.5); } else { glCullFace(GL_FRONT); glColor3f( 1,1,1); } } glBegin( GL_TRIANGLES ); for (t=0 ; tpMD3BoundFrames[ iFrame ].bounds[0]; vec3_t &v3Maxs = pModel->pMD3BoundFrames[ iFrame ].bounds[1]; VectorSet(v3Corners[0],v3Mins[0],v3Mins[1],v3Mins[2]); VectorSet(v3Corners[1],v3Maxs[0],v3Mins[1],v3Mins[2]); VectorSet(v3Corners[2],v3Maxs[0],v3Mins[1],v3Maxs[2]); VectorSet(v3Corners[3],v3Mins[0],v3Mins[1],v3Maxs[2]); VectorSet(v3Corners[4],v3Mins[0],v3Maxs[1],v3Maxs[2]); VectorSet(v3Corners[5],v3Maxs[0],v3Maxs[1],v3Maxs[2]); VectorSet(v3Corners[6],v3Maxs[0],v3Maxs[1],v3Mins[2]); VectorSet(v3Corners[7],v3Mins[0],v3Maxs[1],v3Mins[2]); glColor3f(0.8f,0.8f,0.8f);//1,1,1); /* glBegin(GL_LINE_LOOP); { glVertex3fv(v3Corners[0]); glVertex3fv(v3Corners[1]); glVertex3fv(v3Corners[2]); glVertex3fv(v3Corners[3]); glVertex3fv(v3Corners[4]); glVertex3fv(v3Corners[5]); glVertex3fv(v3Corners[6]); glVertex3fv(v3Corners[7]); } glEnd(); */ // new version, draw the above shape, but without 3 of the lines (and therefore without GL_LINE_LOOP) // because otherwise the white lines fight with the coloured ones below... (aesthetics) // glBegin(GL_LINES); { glVertex3fv(v3Corners[2]); glVertex3fv(v3Corners[3]); glVertex3fv(v3Corners[3]); glVertex3fv(v3Corners[4]); glVertex3fv(v3Corners[4]); glVertex3fv(v3Corners[5]); glVertex3fv(v3Corners[5]); glVertex3fv(v3Corners[6]); glVertex3fv(v3Corners[6]); glVertex3fv(v3Corners[7]); } glEnd(); // draw the thicker lines for the 3 dimension axis... // glLineWidth(3); { // X... // glColor3f(1,0,0); glBegin(GL_LINES); { glVertex3fv(v3Corners[0]); glVertex3fv(v3Corners[1]); } glEnd(); // Y... // // glColor3f(0,1,0); glColor3f(0,0,1); // err.... dunno, but this comes out the right colour now. Quake axis tilt? glBegin(GL_LINES); { glVertex3fv(v3Corners[1]); glVertex3fv(v3Corners[2]); } glEnd(); // Z... // // glColor3f(0,0,1); glColor3f(0,1,0); // err.... dunno, but this comes out the right colour now. Quake axis tilt? glBegin(GL_LINES); { glVertex3fv(v3Corners[7]); glVertex3fv(v3Corners[0]); } glEnd(); } glLineWidth(1); // now draw the coords... // for (int i=0; iiNumMeshFrames < 2) || (ef == mesh->iNumMeshFrames)) { draw_gl_mesh( mesh, mesh->meshFrames[i].pVerts, model ); return; } // calculate an array of interpolated floating point vertices vecs1 = mesh->meshFrames[sf].pVerts; vecs2 = mesh->meshFrames[ef].pVerts; vecsI = mesh->iterMesh; vec_num = mesh->iNumVertices; for (t=0 ; tiRenderedTris = 0; model->iRenderedVerts= 0; if (model->modelType == MODTYPE_MD3) { gl_model* pModel = NULL; switch (mdview.iLODLevel) { case 0: pModel = model; break; case 1: pModel = model->pModel_LOD1;break; case 2: pModel = model->pModel_LOD2;break; } if (pModel) { pModel->iRenderedTris = 0; pModel->iRenderedVerts= 0; pModel->currentFrame = model->currentFrame; // since MD3 LODs are seperate models, copy what's needed int mesh_num = pModel->iNumMeshes; for (int i=0; ipMeshes[i]; int skin_num = pMesh->iNumSkins; int frame_num = pMesh->iNumMeshFrames; int cur_skin = pMesh->bindings[0]; glColor3f( 1, 1, 1 ); glBindTexture( GL_TEXTURE_2D, cur_skin ); GLenum error = glGetError(); if (mdview.interpolate) { draw_interpolated_gl_mesh( pMesh, pModel->currentFrame,pModel); } else { draw_gl_mesh( pMesh, pMesh->meshFrames[pModel->currentFrame].pVerts, pModel ); } } } } else { if (model->modelType == MODTYPE_MDR) { md4LOD_t *pLOD = (md4LOD_t *) ((byte *)model->Q3ModelStuff.md4 + model->Q3ModelStuff.md4->ofsLODs); int iWhichLod = mdview.iLODLevel; if (iWhichLod >= model->Q3ModelStuff.md4->numLODs) iWhichLod = model->Q3ModelStuff.md4->numLODs-1; for ( int i=0; iofsEnd ); } md4Surface_t * pSurface = (md4Surface_t *)( (byte *)pLOD + pLOD->ofsSurfaces ); for ( int i = 0 ; i < pLOD->numSurfaces ; i++ ) { /* if ( ent->e.customShader ) { shader = cust_shader; } else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) { skin_t *skin; int j; skin = R_GetSkinByHandle( ent->e.customSkin ); // match the surface name to something in the skin file shader = tr.defaultShader; for ( j = 0 ; j < skin->numSurfaces ; j++ ) { // the names have both been lowercased if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) { shader = skin->surfaces[j]->shader; break; } } } else { shader = R_GetShaderByHandle( surface->shaderIndex ); } */ glColor3f( 1, 1, 1 ); glBindTexture( GL_TEXTURE_2D, pSurface->shaderIndex); // OutputDebugString(va("Binding surface %d\n",pSurface->shaderIndex)); GLenum error = glGetError(); RB_SurfaceAnim( pSurface, model ); pSurface = (md4Surface_t *)( (byte *)pSurface + pSurface->ofsEnd ); } } } } // this is currently not being called -slc void draw_view() { NodePosition pos; gl_model *model; glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glLoadIdentity (); glTranslatef ( mdview.xPos, mdview.yPos , mdview.zPos ); glScalef ( (float)0.05 , (float)0.05, (float)0.05 ); glRotatef ( mdview.rotAngleX, 1.0 , 0.0 , 0.0 ); glRotatef ( mdview.rotAngleY, 0.0 , 1.0 , 0.0 ); glRotatef ( -90.f , 1.0 , 0.0 , 0.0 ); for (pos=mdview.modelList->first() ; pos!=NULL ; pos=mdview.modelList->after(pos)) { model = (gl_model *)pos->element(); draw_gl_model( model ); } } /* ------------------------------ new skeletal drawing code ---------------------------------------------- */ /* creates a matrix froma quaternion, accepts a ptr to 16 float 4x4 matrix array and a ptr to a 4 float quat array */ #define MAT( p, row, col ) (p)[((col)*3)+(row)] #define MATGL( p, row, col ) (p)[((row)*3)+(col)] #define X 0 #define Y 1 #define Z 2 #define W 3 #define DELTA 0.1 void matrix_from_quat( float *m, float *quat ) { float wx, wy, wz, xx, yy, yz, xy, xz, zz, x2, y2, z2; // calculate coefficients x2 = quat[X] + quat[X]; y2 = quat[Y] + quat[Y]; z2 = quat[Z] + quat[Z]; xx = quat[X] * x2; xy = quat[X] * y2; xz = quat[X] * z2; yy = quat[Y] * y2; yz = quat[Y] * z2; zz = quat[Z] * z2; wx = quat[W] * x2; wy = quat[W] * y2; wz = quat[W] * z2; MAT(m,0,0) = 1.f - (yy + zz); MAT(m,0,1) = xy - wz; MAT(m,0,2) = xz + wy; //MAT(m,0,3) = 0.0; MAT(m,1,0) = xy + wz; MAT(m,1,1) = 1.f - (xx + zz); MAT(m,1,2) = yz - wx; //MAT(m,1,3) = 0.0; MAT(m,2,0) = xz - wy; MAT(m,2,1) = yz + wx; MAT(m,2,2) = 1.f - (xx + yy); //MAT(m,2,3) = 0.f; //MAT(m,3,0) = 0; MAT(m,3,1) = 0; //MAT(m,3,2) = 0; MAT(m,3,3) = 1.f; } /* creates a quaternion from a matrix, parameters like above but reversed */ void quat_from_matrix( float *quat, float *m ) { float tr, s, q[4]; int i, j, k; int nxt[3] = {1, 2, 0}; tr = MAT(m, 0, 0) + MAT(m, 1, 1) + MAT(m, 2, 2); // check the diagonal if (tr > 0.0) { s = (float)sqrt (tr + 1.f); quat[W] = s / 2.0f; s = 0.5f / s; quat[X] = (MAT(m,1,2) - MAT(m,2,1)) * s; quat[Y] = (MAT(m,2,0) - MAT(m,0,2)) * s; quat[Z] = (MAT(m,0,1) - MAT(m,1,0)) * s; } else { // diagonal is negative i = 0; if (MAT(m,1,1) > MAT(m,0,0)) i = 1; if (MAT(m,2,2) > MAT(m,i,i)) i = 2; j = nxt[i]; k = nxt[j]; s = (float)sqrt ((MAT(m,i,i) - (MAT(m,j,j) + MAT(m,k,k))) + 1.0); q[i] = s * (float)0.5; if (s != 0.0f) s = 0.5f / s; q[3] = (MAT(m,j,k) - MAT(m,k,j)) * s; q[j] = (MAT(m,i,j) + MAT(m,j,i)) * s; q[k] = (MAT(m,i,k) + MAT(m,k,i)) * s; quat[X] = q[0]; quat[Y] = q[1]; quat[Z] = q[2]; quat[W] = q[3]; } } /* interpolate quaternions along unit 4d sphere */ void quat_slerp(float *from, float *to, float t, float *res) { float to1[4]; float omega, cosom, sinom, scale0, scale1; // calc cosine cosom = from[X]*to[X] + from[Y]*to[Y] + from[Z]*to[Z] + from[W]*to[W]; // adjust signs (if necessary) if ( cosom <0.0 ) { cosom = -cosom; to1[0] = - to[X]; to1[1] = - to[Y]; to1[2] = - to[Z]; to1[3] = - to[W]; } else { to1[0] = to[X]; to1[1] = to[Y]; to1[2] = to[Z]; to1[3] = to[W]; } // calculate coefficients if ( (1.0 - cosom) > DELTA ) { // standard case (slerp) omega = (float)acos(cosom); sinom = (float)sin(omega); scale0 = (float)sin((1.0 - t) * omega) / sinom; scale1 = (float)sin(t * omega) / sinom; } else { // "from" and "to" quaternions are very close // ... so we can do a linear interpolation scale0 = 1.0f - t; scale1 = t; } // calculate final values res[X] = scale0 * from[X] + scale1 * to1[0]; res[Y] = scale0 * from[Y] + scale1 * to1[1]; res[Z] = scale0 * from[Z] + scale1 * to1[2]; res[W] = scale0 * from[W] + scale1 * to1[3]; } /* draws a node of the skeleton, setting up the transformation for its child nodes */ void draw_skeleton( gl_model *model ) { // draw this model draw_gl_model( model ); // draw its children Tag *tag, *tag2; float m[16], quat1[4], quat2[4], resQuat[4], fm[9], *matrix; //float m1[9], m2[9]; gl_model *child; float *position; //, *matrix; Vec3 interPos; unsigned int j,v; float frac=mdview.frameFrac, frac1=1.f-frac; unsigned int sf=model->currentFrame, ef/*=model->currentFrame+1*/; ef = GetNextFrame_MultiLocked(model, sf, 1); ef = Model_EnsureCurrentFrameLegal(model,ef); bool doInterpolate = false; // check if we can do interpolation if ((model->iNumFrames > 1) && (ef != model->iNumFrames) && (mdview.interpolate)) { doInterpolate = true; } // draw all the attached child models for (j=0; jiNumTags ; j++) { child = model->linkedModels[j]; // if (!child) // continue; // do this later, I may want to draw tags anyway even if no model attached there tag = &model->tags[sf][j]; // if interpolating then calculate in between values... // if (doInterpolate) { tag2 = &model->tags[ef][j]; // interpolate position for (v=0 ; v<3 ; v++) interPos[v] = tag->Position[v]*frac1 + tag2->Position[v]*frac; position = interPos; // interpolate rotation matrix... // quat_from_matrix( quat1, &tag->Matrix[0][0] ); quat_from_matrix( quat2, &tag2->Matrix[0][0] ); quat_slerp( quat1, quat2, frac, resQuat ); matrix_from_quat( fm, resQuat ); matrix = fm; // quaternion code is column based, so use transposed matrix when spitting out to gl... // m[0] = MAT(matrix,0,0); m[4] = MAT(matrix,0,1); m[8] = MAT(matrix,0,2); m[12] = position[0]; m[1] = MAT(matrix,1,0); m[5] = MAT(matrix,1,1); m[9] = MAT(matrix,1,2); m[13] = position[1]; m[2] = MAT(matrix,2,0); m[6] = MAT(matrix,2,1); m[10]= MAT(matrix,2,2); m[14] = position[2]; m[3] = 0; m[7] = 0; m[11]= 0; m[15] = 1; } else { // otherwise stay with last frame... // position = tag->Position; matrix = &tag->Matrix[0][0]; m[0] = MATGL(matrix,0,0); m[4] = MATGL(matrix,0,1); m[8] = MATGL(matrix,0,2); m[12] = position[0]; m[1] = MATGL(matrix,1,0); m[5] = MATGL(matrix,1,1); m[9] = MATGL(matrix,1,2); m[13] = position[1]; m[2] = MATGL(matrix,2,0); m[6] = MATGL(matrix,2,1); m[10]= MATGL(matrix,2,2); m[14] = position[2]; m[3] = 0; m[7] = 0; m[11]= 0; m[15] = 1; } // build transformation matrix glPushMatrix(); glMultMatrixf( m ); // draw tags... // if (mdview.bAxisView) { #define TAG_LINE_LEN 10 static Vec3 v3X = { TAG_LINE_LEN,0,0}; static Vec3 v3XNeg = {-TAG_LINE_LEN,0,0}; static Vec3 v3Y = {0, TAG_LINE_LEN,0}; static Vec3 v3YNeg = {0,-TAG_LINE_LEN,0}; static Vec3 v3Z = {0,0, TAG_LINE_LEN}; static Vec3 v3ZNeg = {0,0,-TAG_LINE_LEN}; glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT); { glColor3f(1,1,1); glDisable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); glBegin(GL_LINES); { glVertex3fv(v3X); glVertex3fv(v3XNeg); glVertex3fv(v3Y); glVertex3fv(v3YNeg); glVertex3fv(v3Z); glVertex3fv(v3ZNeg); } glEnd(); #define TAG_TEXT_COLOUR 255,0,255 Text_Display( "X", v3X, TAG_TEXT_COLOUR); Text_Display("-X", v3XNeg, TAG_TEXT_COLOUR); Text_Display( "Y", v3Y, TAG_TEXT_COLOUR); Text_Display("-Y", v3YNeg, TAG_TEXT_COLOUR); Text_Display( "Z", v3Z, TAG_TEXT_COLOUR); Text_Display("-Z", v3ZNeg, TAG_TEXT_COLOUR); // now draw tag name (neat, eh?) // glColor3f(0.5,0.5,0.5); static Vec3 v3NamePos = {TAG_LINE_LEN,TAG_LINE_LEN,TAG_LINE_LEN}; glLineStipple( 8, 0xAAAA); glEnable(GL_LINE_STIPPLE); glBegin(GL_LINES); { glVertex3f(0,0,0); glVertex3fv(v3NamePos); } glEnd(); Text_Display( tag->name, v3NamePos, TAG_TEXT_COLOUR); } glPopAttrib(); } if (child) draw_skeleton( child ); glPopMatrix(); } } /* draws the entire scene starting with mdview.baseModel */ void draw_viewSkeleton() { if (!mdview.baseModel) return; glClearColor((float)1/((float)256/(float)mdview._R), (float)1/((float)256/(float)mdview._G), (float)1/((float)256/(float)mdview._B), 0.0f); float r = (float)1/((float)256/(float)mdview._R); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glLoadIdentity (); glTranslatef ( mdview.xPos, mdview.yPos , mdview.zPos ); glScalef ( (float)0.05 , (float)0.05, (float)0.05 ); glRotatef ( mdview.rotAngleX, 1.0 , 0.0 , 0.0 ); glRotatef ( mdview.rotAngleY, 0.0 , 1.0 , 0.0 ); glRotatef ( mdview.rotAngleZ, 1.0 , 0.0 , 0.0 ); if (mdview.bUseAlpha) { glEnable (GL_BLEND); glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { glDisable(GL_BLEND); } draw_skeleton( mdview.baseModel ); glDisable(GL_BLEND); if (mdview.bAxisView) { // draw origin lines... // #define ORIGIN_LINE_LEN 20 static Vec3 v3X = { ORIGIN_LINE_LEN,0,0}; static Vec3 v3XNeg = {-ORIGIN_LINE_LEN,0,0}; static Vec3 v3Y = {0, ORIGIN_LINE_LEN,0}; static Vec3 v3YNeg = {0,-ORIGIN_LINE_LEN,0}; static Vec3 v3Z = {0,0, ORIGIN_LINE_LEN}; static Vec3 v3ZNeg = {0,0,-ORIGIN_LINE_LEN}; glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT); { glColor3f(0,0,1); glDisable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); glBegin(GL_LINES); { glVertex3fv(v3X); glVertex3fv(v3XNeg); glVertex3fv(v3Y); glVertex3fv(v3YNeg); glVertex3fv(v3Z); glVertex3fv(v3ZNeg); } glEnd(); #define ORIGIN_TEXT_COLOUR 255,0,255 Text_Display( "X", v3X, ORIGIN_TEXT_COLOUR); Text_Display("-X", v3XNeg, ORIGIN_TEXT_COLOUR); Text_Display( "Y", v3Y, ORIGIN_TEXT_COLOUR); Text_Display("-Y", v3YNeg, ORIGIN_TEXT_COLOUR); Text_Display( "Z", v3Z, ORIGIN_TEXT_COLOUR); Text_Display("-Z", v3ZNeg, ORIGIN_TEXT_COLOUR); } glPopAttrib(); } } #include typedef set Strings_t; void R_ViewModelInfo( gl_model *model, char *sInfo ) { strcat(sInfo, va("Model: %s\n",model->sMDXFullPathname)); switch (model->modelType) { case MODTYPE_MDR: { for (int iLOD = 0; iLOD Q3ModelStuff.md4->numLODs; iLOD++) { strcat(sInfo, va("\nSurfaces(LOD %d/%d):\n\n",iLOD,model->Q3ModelStuff.md4->numLODs)); md4LOD_t *pLOD = (md4LOD_t *) ((byte *)model->Q3ModelStuff.md4 + model->Q3ModelStuff.md4->ofsLODs); for ( int i=0; iofsEnd ); } md4Surface_t * pSurface = (md4Surface_t *)( (byte *)pLOD + pLOD->ofsSurfaces ); for ( int i = 0 ; i < pLOD->numSurfaces ; i++ ) { strcat(sInfo, va(" %s = %s %s\n",pSurface->name,pSurface->shader,pSurface->shaderIndex?"":"( Not found!)")); pSurface = (md4Surface_t *)( (byte *)pSurface + pSurface->ofsEnd ); } } } break; case MODTYPE_MD3: { for (int iLOD=0; iLOD<3; iLOD++) { strcat(sInfo, va("\nSurfaces(LOD %d):\n\n",iLOD)); gl_model* pModel = NULL; switch (iLOD) { case 0: pModel = model; break; case 1: pModel = model->pModel_LOD1; break; case 2: pModel = model->pModel_LOD2; break; } if (pModel) { //strcat(sInfo, "Surfaces:\n"); for (int i=0; i<(int)pModel->iNumMeshes; i++) { gl_mesh *pMesh =&pModel->pMeshes[i]; strcat(sInfo, va(" %s = %s %s\n",pMesh->sName, pMesh->sTextureName,pMesh->bindings[0]?"":"( Not found!)")); } } } } break; default: { ErrorBox("Code missing for new model type in R_ViewModelInfo()"); assert(0); } break; } strcat(sInfo, "\n\n"); // get detail on any attached child models... // for (UINT j=0; jiNumTags ; j++) { gl_model *child = model->linkedModels[j]; if (child) { R_ViewModelInfo( child, sInfo ); } } } // for a popup info box... // char *GetLoadedModelInfo(void) { static char strInfo[10000]; // strInfo[0]=0; if (!mdview.baseModel) { strcpy(strInfo,"No model loaded!"); } else { R_ViewModelInfo( mdview.baseModel, strInfo ); } return strInfo; } // takes (eg) "q:\quake\baseq3\models\players\seven\lower.md3" // // and makes: "q:\quake\baseq3\models\players\seven\lower_default.skin" // LPCSTR SkinName_FromPathedModelName(LPCSTR psModelPathName) { static char sSkinName[1024]; strcpy(sSkinName,psModelPathName); strlwr(sSkinName); char *psExt = strstr(sSkinName,".md"); if (psExt) { *psExt = 0; strcat(sSkinName,"_default.skin"); } else { assert(0); } strlwr(sSkinName); return sSkinName; } // takes (eg) "q:\quake\baseq3\models\players\seven\lower_default.skin" // or "q:\quake\baseq3\models\players\seven\lower.mdr" // or "lower_blue.skin" // // and makes "lower" // LPCSTR Model_BaseNameFromFilename(LPCSTR psFileName) { static char sModelName[8][1024]; static char tmpNodelName[1024]; static int iIndex = 0; iIndex = (++iIndex)&7; strcpy(tmpNodelName, psFileName); char * psModelName = strrchr(tmpNodelName,'\\'); if (!psModelName) { psModelName = strrchr(tmpNodelName,'/'); } if (psModelName) { strcpy(sModelName[iIndex],psModelName+1); } else { strcpy(sModelName[iIndex],tmpNodelName); } psModelName = strchr(sModelName[iIndex],'.'); if (psModelName) { *psModelName = 0; } psModelName = strchr(sModelName[iIndex],'_'); if (psModelName) { *psModelName = 0; } strlwr(sModelName[iIndex]); return sModelName[iIndex]; } gl_model* R_FindModel( gl_model* model, LPCSTR psModelBaseName) // eg "upper" { gl_model* pReturnVal = NULL; // OutputDebugString(va("Scanning model \"%s\"\n",model->sMDXFullPathname)); char sThisModelName[MAX_QPATH]; strcpy(sThisModelName,Model_BaseNameFromFilename(model->sMD3BaseName)); // strip off .mdr/.md3 etc for easier compare if (!strcmp(sThisModelName, psModelBaseName)) { pReturnVal = model; } else { // try it's children... // for (UINT j=0; jiNumTags ; j++) { gl_model *child = model->linkedModels[j]; if (child) { gl_model* pFound = R_FindModel(child, psModelBaseName); if (pFound) { pReturnVal = pFound; break; } } } } return pReturnVal; } gl_model* Model_FromPathedSkinName(LPCSTR psSkinFileName) { gl_model* pModel = NULL; if (!mdview.baseModel) { ErrorBox("No models loaded!"); } else { // note that I can't put this string convert in-line, because the recursive function it calls also // uses the same string function internally, so names get overwritten! // char sModelBaseName[1024]; strcpy(sModelBaseName,Model_BaseNameFromFilename(psSkinFileName)); pModel = R_FindModel(mdview.baseModel,sModelBaseName); if (!pModel) { ErrorBox(va("Unable to find model to apply skin \"%s\" to!",psSkinFileName)); } } return pModel; } // update, if psTextureName is NULL, surface is bound to zero (the famous GL white), // and if psSurfaceName is NULL, then no strcmp is performed (so you can put one texture on all surfaces) // bool Model_ApplyTextureToSurface(gl_model* model, LPCSTR psSurfaceName, LPCSTR psTextureName) { bool bReturn = false; switch (model->modelType) { case MODTYPE_MD3: { for (int iLOD = 0; iLOD<3; iLOD++) { // ID models actually append the LOD level onto the surface names as well. Doh! // char sSurfaceNameID[1024]; strcpy(sSurfaceNameID,psSurfaceName?psSurfaceName:""); gl_model* pModel = NULL; switch (iLOD) { case 0: pModel = model; break; case 1: pModel = model->pModel_LOD1; break; case 2: pModel = model->pModel_LOD2; break; } strcat(sSurfaceNameID,iLOD?va("_%d",iLOD):""); if (pModel) { int mesh_num = pModel->iNumMeshes; for (int i=0; ipMeshes[i]; if (!psSurfaceName || !strcmp(pMesh->sName,psSurfaceName) || !strcmp(pMesh->sName, sSurfaceNameID)) { // MD3s are a bit weird, ignore the local 'iNumSkins' count, and always assume 1 here... // pMesh->bindings[0] = psTextureName?loadTexture(psTextureName):0; strcpy(pMesh->sTextureName,psTextureName?psTextureName:""); bReturn = true; if (psSurfaceName) break; // else keep looping for ALL } } } } } break; case MODTYPE_MDR: { for (int iLOD=0; iLOD < model->Q3ModelStuff.md4->numLODs; iLOD++) { md4LOD_t *pLOD = (md4LOD_t *) ((byte *)model->Q3ModelStuff.md4 + model->Q3ModelStuff.md4->ofsLODs); for ( int i=0; iofsEnd ); } md4Surface_t * pSurface = (md4Surface_t *)( (byte *)pLOD + pLOD->ofsSurfaces ); for ( int i = 0 ; i < pLOD->numSurfaces ; i++ ) { if (!psSurfaceName || !strcmp(pSurface->name,psSurfaceName)) { strcpy(pSurface->shader,psTextureName?psTextureName:""); pSurface->shaderIndex = psTextureName?loadTexture(pSurface->shader):0; bReturn = true; if (psSurfaceName) break; } pSurface = (md4Surface_t *)( (byte *)pSurface + pSurface->ofsEnd ); } } } break; default: { ErrorBox("Missing model-type case statement (Model_ApplyTextureToSurface)"); bReturn = true; // so it doesn't try and report the skin error below } break; } if (!bReturn) { if (!psSurfaceName || !psTextureName) { // forget it, the model probably had no surfaces } else { // !psSurfaceName should already have been handled by above, so crash won't happen here... // ErrorBox(va("Unable to find surface name \"%s\" in model %s",psSurfaceName,model->sMDXFullPathname)); } } return bReturn; } // if psSkinFilename==NULL, white-out the model // bool Model_ApplySkin(gl_model* model, LPCSTR psSkinFilename) { FILE *fpHandle = NULL; // default model to blank/white first, so that missing bits show up... // if (psSkinFilename) { fpHandle = fopen(psSkinFilename,"rt"); // white-out the model first if we do find the file, or we don't and it's not a default skin (which are optional) // if (fpHandle || (!strstr(String_ToLower(psSkinFilename),"_default.skin"))) Model_ApplyTextureToSurface(model, NULL, NULL); if (fpHandle) { typedef struct { char sSurfaceName[100]; char sTextureName[MAX_QPATH]; } SKINITEM, *LPSKINITEM; SKINITEM SkinItems[1024]={0}; int iSkinIndex = 0; char sLine[1024]={0}; while (fgets(sLine, sizeof(sLine), fpHandle)) { // lose CR... // char *p = strchr(sLine,'\n'); if (p) *p=0; if (strlen(sLine)) { LPCSTR psComma = strchr(sLine,','); if (psComma) { int iCommaIndex = psComma - sLine; LPSKINITEM lpSkinItem = &SkinItems[iSkinIndex]; // new bit to cope with faulty ID skin files, where they have spurious lines like "tag_torso," // if (strnicmp(sLine,"tag_",4)==0 || !strlen(&sLine[iCommaIndex+1])) { // crap line, so ignore it... } else { strncpy(lpSkinItem->sSurfaceName,sLine,iCommaIndex); strcpy (lpSkinItem->sTextureName,&sLine[iCommaIndex+1]); char *psCR = strchr(lpSkinItem->sTextureName,'\n'); if (psCR) *psCR = 0; iSkinIndex++; } } else { ErrorBox(va("Error parsing line \"%s\" in file \"%s\"!",sLine,psSkinFilename)); } sLine[0]=0; } } // apply any entries found... // for (int i=0; isSurfaceName, lpSkinItem->sTextureName); } fclose(fpHandle); } } return (!psSkinFilename)?true:!!fpHandle; } // called with filename from browser, so work out what to do with it... // bool ApplyNewSkin(LPCSTR psSkinFullPathName) { bool bReturn = false; gl_model* pModel = Model_FromPathedSkinName(psSkinFullPathName); if (pModel) { if (!strnicmp(pModel->sMD3BaseName,"head.",5) && strstr(psSkinFullPathName,"head_")) { char sSkinBase[MAX_PATH]; strcpy(sSkinBase,strchr(Filename_WithoutExt(Filename_WithoutPath(psSkinFullPathName)),'_')+1); if (strchr(sSkinBase,'-')) *strchr(sSkinBase,'-')=0; strcpy(pModel->sHeadSkinName,sSkinBase); } bReturn = Model_ApplySkin(pModel, psSkinFullPathName); } return bReturn; } // if there's a head model present then change it's skin according to: // // -1 frame down // 0 reset to default // 1 frame up // // (head frames are 6 talking frames, then two expression frames) // #define HEAD_SKIN_MAX 8 #define HEAD_SKIN_TALK_MAX 8 #define HEAD_SKIN_TALK_MIN 5 // // this is now also called every time any model is loaded (for safety reasons)) // void SetFaceSkin(int iSkin) { gl_model* pModel = R_FindModel( mdview.baseModel, "head"); // eg "upper" if (pModel) { // don't try and apply skins to something that has no default skin to restore to... // if (file_exists(SkinName_FromPathedModelName(pModel->sMDXFullPathname))) { if (iSkin == 0) mdview.iSkinNumber = 0; else { mdview.iSkinNumber += iSkin; if (iSkin>0) { // cycling forward through speech... // if (mdview.iSkinNumber > HEAD_SKIN_TALK_MAX) { mdview.iSkinNumber = HEAD_SKIN_TALK_MIN; } } else { // resetting or cycling backwards (which access the higher frames) // if (mdview.iSkinNumber < 0) { mdview.iSkinNumber = HEAD_SKIN_MAX; } } // sanity... // if (mdview.iSkinNumber<0 || mdview.iSkinNumber>HEAD_SKIN_MAX) { mdview.iSkinNumber=0; } } char sSkinFileName[MAX_PATH]; strcpy(sSkinFileName,pModel->sMDXFullPathname); // default to fullpathname of model (windows-style backslashes) *strrchr(sSkinFileName,'.')=0; // lose ".mdx" strcat(sSkinFileName,va("_%s%s.skin",pModel->sHeadSkinName,!mdview.iSkinNumber?"":va("-%1d",mdview.iSkinNumber))); if (!Model_ApplySkin(pModel, sSkinFileName)) { // error applying skin, but errors would already be either reported or visible in model now, so do nowt } } else { if (iSkin) // so it won't report on the default-set call { ErrorBox("This head model does not have a default skin file to restore to, so no expressions are available"); } } } }