//a note about dedicated servers: //In the server-side gamecode, a couple of q1 extensions require knowing something about models. //So we load models serverside, if required. //things we need: //tag/bone names and indexes so we can have reasonable modding with tags. :) //tag/bone positions so we can shoot from the actual gun or other funky stuff //vertex positions so we can trace against the mesh rather than the bbox. //we use the gl renderer's model code because it supports more sorts of models than the sw renderer. Sad but true. #include "quakedef.h" #ifdef RGLQUAKE #include "glquake.h" #endif #if defined(RGLQUAKE) || defined(SERVERONLY) #include "shader.h" #include "hash.h" #if defined(ZYMOTICMODELS) || defined(MD5MODELS) #define SKELETALMODELS #include "malloc.h" #endif #define MAX_BONES 256 static model_t *loadmodel; //FIXME typedef struct { float scale[3]; // multiply qbyte verts by this float translate[3]; // then add this char name[16]; // frame name from grabbing dtrivertx_t verts[1]; // variable sized } dmd2aliasframe_t; // entity_state_t->renderfx flags #define Q2RF_MINLIGHT 1 // always have some light (viewmodel) #define Q2RF_VIEWERMODEL 2 // don't draw through eyes, only mirrors #define Q2RF_WEAPONMODEL 4 // only draw through eyes #define Q2RF_FULLBRIGHT 8 // always draw full intensity #define Q2RF_DEPTHHACK 16 // for view weapon Z crunching #define Q2RF_TRANSLUCENT 32 #define Q2RF_FRAMELERP 64 #define Q2RF_BEAM 128 #define Q2RF_CUSTOMSKIN 256 // skin is an index in image_precache #define Q2RF_GLOW 512 // pulse lighting for bonus items #define Q2RF_SHELL_RED 1024 #define Q2RF_SHELL_GREEN 2048 #define Q2RF_SHELL_BLUE 4096 //ROGUE #define Q2RF_IR_VISIBLE 0x00008000 // 32768 #define Q2RF_SHELL_DOUBLE 0x00010000 // 65536 #define Q2RF_SHELL_HALF_DAM 0x00020000 #define Q2RF_USE_DISGUISE 0x00040000 //ROGUE extern cvar_t gl_part_flame, r_fullbrightSkins, r_fb_models; extern cvar_t r_noaliasshadows; void R_TorchEffect (vec3_t pos, int type); void GLMod_FloodFillSkin( qbyte *skin, int skinwidth, int skinheight ); extern char loadname[32]; // for hunk tags int numTempColours; byte_vec4_t *tempColours; int numTempVertexCoords; vec3_t *tempVertexCoords; int numTempNormals; vec3_t *tempNormals; extern cvar_t gl_ati_truform; extern cvar_t r_vertexdlights; extern cvar_t mod_md3flags; typedef struct { int ofs_indexes; int numindexes; int ofs_trineighbours; int numskins; #ifndef SERVERONLY int ofsskins; #endif qboolean sharesverts; //used with models with two shaders using the same vertex - use last mesh's verts qboolean sharesbones; //use last mesh's bones (please, never set this on the first mesh!) int numverts; #ifndef SERVERONLY int ofs_st_array; #endif int groups; int groupofs; int nextsurf; #ifdef SKELETALMODELS int numbones; int ofsbones; int numtransforms; int ofstransforms; #endif //these exist only in the root mesh. int numtagframes; int numtags; int ofstags; } galiasinfo_t; //frame is an index into this typedef struct { #ifdef SKELETALMODELS qboolean isheirachical; //for models with transforms, states that bones need to be transformed from thier parent. //this is actually bad, and can result in bones shortening as they interpolate. #endif qboolean loop; int numposes; float rate; int poseofs; } galiasgroup_t; typedef struct { int ofsverts; #ifndef SERVERONLY int ofsnormals; #endif vec3_t scale; vec3_t scale_origin; } galiaspose_t; #ifdef SKELETALMODELS typedef struct { char name[32]; int parent; } galiasbone_t; typedef struct { //skeletal poses refer to this. int vertexindex; int boneindex; vec4_t org; } galisskeletaltransforms_t; #endif //we can't be bothered with animating skins. //We'll load up to four of them but after that you're on your own #ifndef SERVERONLY typedef struct { int skinwidth; int skinheight; int ofstexels; //this is 8bit for frame 0 only. only valid in q1 models without replacement textures, used for colourising player skins. float skinspeed; int texnums; int ofstexnums; char name [MAX_QPATH]; } galiasskin_t; typedef struct { int base; int bump; int fullbright; shader_t *shader; } galiastexnum_t; typedef struct { char name[MAX_QPATH]; galiastexnum_t texnum; int colour; int skinnum; bucket_t bucket; } galiascolourmapped_t; #endif #ifdef SKELETALMODELS static void R_LerpBones(float *plerp, float **pose, int poses, galiasbone_t *bones, int bonecount, float bonepose[MAX_BONES][12]); static void R_TransformVerticies(float bonepose[MAX_BONES][12], galisskeletaltransforms_t *weights, int numweights, float *xyzout); #endif void Mod_DoCRC(model_t *mod, char *buffer, int buffersize) { #ifndef SERVERONLY //we've got to have this bit if (!strcmp(loadmodel->name, "progs/player.mdl") || !strcmp(loadmodel->name, "progs/eyes.mdl")) { unsigned short crc; qbyte *p; int len; char st[40]; QCRC_Init(&crc); for (len = buffersize, p = buffer; len; len--, p++) QCRC_ProcessByte(&crc, *p); sprintf(st, "%d", (int) crc); Info_SetValueForKey (cls.userinfo, !strcmp(loadmodel->name, "progs/player.mdl") ? pmodel_name : emodel_name, st, MAX_INFO_STRING); if (cls.state >= ca_connected) { CL_SendClientCommand(true, "setinfo %s %d", !strcmp(loadmodel->name, "progs/player.mdl") ? pmodel_name : emodel_name, (int)crc); } } #endif } qboolean GLMod_Trace(model_t *model, int forcehullnum, int frame, vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, trace_t *trace) { galiasinfo_t *mod = Mod_Extradata(model); galiasgroup_t *group; galiaspose_t *pose; int i; float *p1, *p2, *p3; vec3_t edge1, edge2, edge3; vec3_t normal; vec3_t edgenormal; float planedist; float diststart, distend; float frac; // float temp; vec3_t impactpoint; float *posedata; int *indexes; while(mod) { indexes = (int*)((char*)mod + mod->ofs_indexes); group = (galiasgroup_t*)((char*)mod + mod->groupofs); pose = (galiaspose_t*)((char*)&group[0] + group[0].poseofs); posedata = (float*)((char*)pose + pose->ofsverts); #ifdef SKELETALMODELS if (mod->numbones && !mod->sharesverts) { float bonepose[MAX_BONES][12]; posedata = alloca(mod->numverts*sizeof(vec3_t)); frac = 1; if (group->isheirachical) { if (!mod->sharesbones) R_LerpBones(&frac, (float**)posedata, 1, (galiasbone_t*)((char*)mod + mod->ofsbones), mod->numbones, bonepose); R_TransformVerticies(bonepose, (galisskeletaltransforms_t*)((char*)mod + mod->ofstransforms), mod->numtransforms, posedata); } else R_TransformVerticies((void*)posedata, (galisskeletaltransforms_t*)((char*)mod + mod->ofstransforms), mod->numtransforms, posedata); } #endif for (i = 0; i < mod->numindexes; i+=3) { p1 = posedata + 3*indexes[i+0]; p2 = posedata + 3*indexes[i+1]; p3 = posedata + 3*indexes[i+2]; VectorSubtract(p1, p2, edge1); VectorSubtract(p3, p2, edge2); CrossProduct(edge1, edge2, normal); planedist = DotProduct(p1, normal); diststart = DotProduct(start, normal); if (diststart <= planedist) continue; //start on back side. distend = DotProduct(end, normal); if (distend >= planedist) continue; //end on front side (as must start - doesn't cross). frac = (diststart - planedist) / (diststart-distend); if (frac >= trace->fraction) //already found one closer. continue; impactpoint[0] = start[0] + frac*(end[0] - start[0]); impactpoint[1] = start[1] + frac*(end[1] - start[1]); impactpoint[2] = start[2] + frac*(end[2] - start[2]); // temp = DotProduct(impactpoint, normal)-planedist; CrossProduct(edge1, normal, edgenormal); // temp = DotProduct(impactpoint, edgenormal)-DotProduct(p2, edgenormal); if (DotProduct(impactpoint, edgenormal) > DotProduct(p2, edgenormal)) continue; CrossProduct(normal, edge2, edgenormal); if (DotProduct(impactpoint, edgenormal) > DotProduct(p3, edgenormal)) continue; VectorSubtract(p1, p3, edge3); CrossProduct(normal, edge3, edgenormal); if (DotProduct(impactpoint, edgenormal) > DotProduct(p1, edgenormal)) continue; trace->fraction = frac; VectorCopy(impactpoint, trace->endpos); VectorCopy(normal, trace->plane.normal); } if (mod->nextsurf) mod = (galiasinfo_t*)((char*)mod + mod->nextsurf); else mod = NULL; } trace->allsolid = false; return trace->fraction != 1; } #ifndef SERVERONLY static hashtable_t skincolourmapped; static vec3_t shadevector; static vec3_t shadelight, ambientlight; static void R_LerpFrames(mesh_t *mesh, galiaspose_t *p1, galiaspose_t *p2, float lerp, qbyte alpha, float expand) { extern cvar_t r_nolerp, r_nolightdir; float blerp = 1-lerp; int i; float l; int temp; vec3_t *p1v, *p2v; vec3_t *p1n, *p2n; p1v = (vec3_t *)((char *)p1 + p1->ofsverts); p2v = (vec3_t *)((char *)p2 + p2->ofsverts); p1n = (vec3_t *)((char *)p1 + p1->ofsnormals); p2n = (vec3_t *)((char *)p2 + p2->ofsnormals); if (p1v == p2v || r_nolerp.value) { mesh->normals_array = (vec3_t*)((char *)p1 + p1->ofsnormals); mesh->xyz_array = p1v; if (r_nolightdir.value) { mesh->colors_array = NULL; } else { for (i = 0; i < mesh->numvertexes; i++) { l = DotProduct(mesh->normals_array[i], shadevector); temp = l*ambientlight[0]+shadelight[0]; if (temp < 0) temp = 0; else if (temp > 255) temp = 255; mesh->colors_array[i][0] = temp; temp = l*ambientlight[1]+shadelight[1]; if (temp < 0) temp = 0; else if (temp > 255) temp = 255; mesh->colors_array[i][1] = temp; temp = l*ambientlight[2]+shadelight[2]; if (temp < 0) temp = 0; else if (temp > 255) temp = 255; mesh->colors_array[i][2] = temp; mesh->colors_array[i][3] = alpha; } } } else { if (r_nolightdir.value) { for (i = 0; i < mesh->numvertexes; i++) { mesh->normals_array[i][0] = p1n[i][0]*lerp + p2n[i][0]*blerp; mesh->normals_array[i][1] = p1n[i][1]*lerp + p2n[i][1]*blerp; mesh->normals_array[i][2] = p1n[i][2]*lerp + p2n[i][2]*blerp; mesh->xyz_array[i][0] = p1v[i][0]*lerp + p2v[i][0]*blerp; mesh->xyz_array[i][1] = p1v[i][1]*lerp + p2v[i][1]*blerp; mesh->xyz_array[i][2] = p1v[i][2]*lerp + p2v[i][2]*blerp; mesh->colors_array[i][0] = ambientlight[0]; mesh->colors_array[i][1] = ambientlight[1]; mesh->colors_array[i][2] = ambientlight[2]; mesh->colors_array[i][3] = alpha; } } else { for (i = 0; i < mesh->numvertexes; i++) { mesh->normals_array[i][0] = p1n[i][0]*lerp + p2n[i][0]*blerp; mesh->normals_array[i][1] = p1n[i][1]*lerp + p2n[i][1]*blerp; mesh->normals_array[i][2] = p1n[i][2]*lerp + p2n[i][2]*blerp; mesh->xyz_array[i][0] = p1v[i][0]*lerp + p2v[i][0]*blerp; mesh->xyz_array[i][1] = p1v[i][1]*lerp + p2v[i][1]*blerp; mesh->xyz_array[i][2] = p1v[i][2]*lerp + p2v[i][2]*blerp; l = DotProduct(mesh->normals_array[i], shadevector); temp = l*ambientlight[0]+shadelight[0]; if (temp < 0) temp = 0; else if (temp > 255) temp = 255; mesh->colors_array[i][0] = temp; temp = l*ambientlight[1]+shadelight[1]; if (temp < 0) temp = 0; else if (temp > 255) temp = 255; mesh->colors_array[i][1] = temp; temp = l*ambientlight[2]+shadelight[2]; if (temp < 0) temp = 0; else if (temp > 255) temp = 255; mesh->colors_array[i][2] = temp; mesh->colors_array[i][3] = alpha; } } } if (expand) { if (mesh->xyz_array == p1v) { mesh->xyz_array = tempVertexCoords; for (i = 0; i < mesh->numvertexes; i++) { mesh->xyz_array[i][0] = p1v[i][0] + mesh->normals_array[i][0]*expand; mesh->xyz_array[i][1] = p1v[i][1] + mesh->normals_array[i][1]*expand; mesh->xyz_array[i][2] = p1v[i][2] + mesh->normals_array[i][2]*expand; } } else { for (i = 0; i < mesh->numvertexes; i++) { mesh->xyz_array[i][0] += mesh->normals_array[i][0]*expand; mesh->xyz_array[i][1] += mesh->normals_array[i][1]*expand; mesh->xyz_array[i][2] += mesh->normals_array[i][2]*expand; } } } } #endif #ifdef SKELETALMODELS static void R_LerpBones(float *plerp, float **pose, int poses, galiasbone_t *bones, int bonecount, float bonepose[MAX_BONES][12]) { int i, k, b; float *matrix, m[12]; if (poses == 1) { // vertex weighted skeletal // interpolate matrices and concatenate them to their parents for (i = 0;i < bonecount;i++) { matrix = pose[0] + i*12; if (bones[i].parent >= 0) R_ConcatTransforms((void*)bonepose[bones[i].parent], (void*)matrix, (void*)bonepose[i]); else for (k = 0;k < 12;k++) //parentless bonepose[i][k] = matrix[k]; } } else { // vertex weighted skeletal // interpolate matrices and concatenate them to their parents for (i = 0;i < bonecount;i++) { for (k = 0;k < 12;k++) m[k] = 0; for (b = 0;b < poses;b++) { matrix = pose[b] + i*12; for (k = 0;k < 12;k++) m[k] += matrix[k] * plerp[b]; } if (bones[i].parent >= 0) R_ConcatTransforms((void*)bonepose[bones[i].parent], (void*)m, (void*)bonepose[i]); else for (k = 0;k < 12;k++) //parentless bonepose[i][k] = m[k]; } } } static void R_TransformVerticies(float bonepose[MAX_BONES][12], galisskeletaltransforms_t *weights, int numweights, float *xyzout) { int i; float *out, *matrix; galisskeletaltransforms_t *v = weights; for (i = 0;i < numweights;i++, v++) { out = xyzout + v->vertexindex * 3; matrix = bonepose[v->boneindex]; // FIXME: this can very easily be optimized with SSE or 3DNow out[0] += v->org[0] * matrix[0] + v->org[1] * matrix[1] + v->org[2] * matrix[ 2] + v->org[3] * matrix[ 3]; out[1] += v->org[0] * matrix[4] + v->org[1] * matrix[5] + v->org[2] * matrix[ 6] + v->org[3] * matrix[ 7]; out[2] += v->org[0] * matrix[8] + v->org[1] * matrix[9] + v->org[2] * matrix[10] + v->org[3] * matrix[11]; } } #ifndef SERVERONLY static void R_BuildSkeletalMesh(mesh_t *mesh, float *plerp, float **pose, int poses, galiasbone_t *bones, int bonecount, galisskeletaltransforms_t *weights, int numweights, qboolean usehierarchy) { float bonepose[MAX_BONES][12]; int i, k, l; if (usehierarchy) R_LerpBones(plerp, pose, poses, bones, bonecount, bonepose); else { if (poses == 1) memcpy(bonepose, pose[0], sizeof(float)*12*bonecount); else if (poses == 2) { for (i = 0; i < bonecount*12; i++) { ((float*)bonepose)[i] = pose[0][i]*plerp[0] + pose[1][i]*plerp[1]; } } else { for (i = 0; i < bonecount; i++) { for (l = 0; l < 12; l++) bonepose[i][l] = 0; for (k = 0; k < poses; k++) { for (l = 0; l < 12; l++) bonepose[i][l] += pose[k][i*12+l] * plerp[k]; } } } } // blend the vertex bone weights // memset(outhead, 0, mesh->numvertexes * sizeof(mesh->xyz_array[0])); for (i = 0; i < mesh->numvertexes; i++) { mesh->normals_array[i][0] = 0; mesh->normals_array[i][1] = 0; mesh->normals_array[i][2] = 1; mesh->colors_array[i][0] = ambientlight[0]; mesh->colors_array[i][1] = ambientlight[1]; mesh->colors_array[i][2] = ambientlight[2]; mesh->colors_array[i][3] = 255;//alpha; /* mesh->xyz_array[i][0] = 0; mesh->xyz_array[i][1] = 0; mesh->xyz_array[i][2] = 0; mesh->xyz_array[i][3] = 1; */ } memset(mesh->xyz_array, 0, mesh->numvertexes*sizeof(vec3_t)); R_TransformVerticies(bonepose, weights, numweights, (float*)mesh->xyz_array); #if 0 //draws the bones qglColor3f(1, 0, 0); { int i; int p; vec3_t org, dest; qglBegin(GL_LINES); for (i = 0; i < bonecount; i++) { p = bones[i].parent; if (p < 0) p = 0; qglVertex3f(bonepose[i][3], bonepose[i][7], bonepose[i][11]); qglVertex3f(bonepose[p][3], bonepose[p][7], bonepose[p][11]); } qglEnd(); qglBegin(GL_LINES); for (i = 0; i < bonecount; i++) { p = bones[i].parent; if (p < 0) p = 0; org[0] = bonepose[i][3]; org[1] = bonepose[i][7]; org[2] = bonepose[i][11]; qglVertex3fv(org); qglVertex3f(bonepose[p][3], bonepose[p][7], bonepose[p][11]); dest[0] = org[0]+bonepose[i][0];dest[1] = org[1]+bonepose[i][1];dest[2] = org[2]+bonepose[i][2]; qglVertex3fv(org); qglVertex3fv(dest); qglVertex3fv(dest); qglVertex3f(bonepose[p][3], bonepose[p][7], bonepose[p][11]); dest[0] = org[0]+bonepose[i][4];dest[1] = org[1]+bonepose[i][5];dest[2] = org[2]+bonepose[i][6]; qglVertex3fv(org); qglVertex3fv(dest); qglVertex3fv(dest); qglVertex3f(bonepose[p][3], bonepose[p][7], bonepose[p][11]); dest[0] = org[0]+bonepose[i][8];dest[1] = org[1]+bonepose[i][9];dest[2] = org[2]+bonepose[i][10]; qglVertex3fv(org); qglVertex3fv(dest); qglVertex3fv(dest); qglVertex3f(bonepose[p][3], bonepose[p][7], bonepose[p][11]); } qglEnd(); // mesh->numindexes = 0; //don't draw this mesh, as that would obscure the bones. :( } #endif } #endif #endif #ifndef SERVERONLY //changes vertex lighting values static void R_GAliasAddDlights(mesh_t *mesh, vec3_t org, vec3_t angles) { int l, v; vec3_t rel; vec3_t dir; float dot, d, a, f; for (l=0 ; lcl_dlights[l].radius+mesh->radius) //far out man! continue; rel[0] = -DotProduct(dir, currententity->axis[0]); rel[1] = -DotProduct(dir, currententity->axis[1]); //quake's crazy. rel[2] = -DotProduct(dir, currententity->axis[2]); /* glBegin(GL_LINES); glVertex3f(0,0,0); glVertex3f(rel[0],rel[1],rel[2]); glEnd(); */ for (v = 0; v < mesh->numvertexes; v++) { VectorSubtract(mesh->xyz_array[v], rel, dir); dot = DotProduct(dir, mesh->normals_array[v]); if (dot>0) { d = DotProduct(dir, dir); a = 1/d; if (a>0) { a *= 10000000*dot/sqrt(d); f = mesh->colors_array[v][0] + a*cl_dlights[l].color[0]; if (f > 255) f = 255; else if (f < 0) f = 0; mesh->colors_array[v][0] = f; f = mesh->colors_array[v][1] + a*cl_dlights[l].color[1]; if (f > 255) f = 255; else if (f < 0) f = 0; mesh->colors_array[v][1] = f; f = mesh->colors_array[v][2] + a*cl_dlights[l].color[2]; if (f > 255) f = 255; else if (f < 0) f = 0; mesh->colors_array[v][2] = f; } // else // mesh->colors_array[v][1] =255; } // else // mesh->colors_array[v][2] =255; } } } } static qboolean R_GAliasBuildMesh(mesh_t *mesh, galiasinfo_t *inf, int frame1, int frame2, float lerp, float alpha, float fg1time, float fg2time) { galiasgroup_t *g1, *g2; if (!inf->groups) { Con_DPrintf("Model with no frames (%s)\n", currententity->model->name); return false; } if (frame1 < 0) { Con_DPrintf("Negative frame (%s)\n", currententity->model->name); frame1 = 0; } if (frame2 < 0) { Con_DPrintf("Negative frame (%s)\n", currententity->model->name); frame2 = frame1; } if (frame1 >= inf->groups) { Con_DPrintf("Too high frame %i (%s)\n", frame1, currententity->model->name); frame1 %= inf->groups; } if (frame2 >= inf->groups) { Con_DPrintf("Too high frame %i (%s)\n", frame2, currententity->model->name); frame2 = frame1; } if (lerp <= 0) frame2 = frame1; else if (lerp >= 1) frame1 = frame2; if (numTempColours < inf->numverts) { if (tempColours) BZ_Free(tempColours); tempColours = BZ_Malloc(sizeof(*tempColours)*inf->numverts); numTempColours = inf->numverts; } if (numTempNormals < inf->numverts) { if (tempNormals) BZ_Free(tempNormals); tempNormals = BZ_Malloc(sizeof(*tempNormals)*inf->numverts); numTempNormals = inf->numverts; } if (numTempVertexCoords < inf->numverts) { if (tempVertexCoords) BZ_Free(tempVertexCoords); tempVertexCoords = BZ_Malloc(sizeof(*tempVertexCoords)*inf->numverts); numTempVertexCoords = inf->numverts; } mesh->numvertexes = inf->numverts; mesh->indexes = (index_t*)((char *)inf + inf->ofs_indexes); mesh->numindexes = inf->numindexes; if (inf->sharesverts) return false; //don't generate the new vertex positions. We still have them all. #ifndef SERVERONLY mesh->st_array = (vec2_t*)((char *)inf + inf->ofs_st_array); mesh->lmst_array = NULL; mesh->colors_array = tempColours; mesh->trneighbors = (int *)((char *)inf + inf->ofs_trineighbours); mesh->normals_array = tempNormals; #endif mesh->xyz_array = tempVertexCoords; g1 = (galiasgroup_t*)((char *)inf + inf->groupofs + sizeof(galiasgroup_t)*frame1); g2 = (galiasgroup_t*)((char *)inf + inf->groupofs + sizeof(galiasgroup_t)*frame2); //we don't support meshes with one pose skeletal and annother not. //we don't support meshes with one group skeletal and annother not. #ifdef SKELETALMODELS if (inf->numbones) { int l=0; float plerp[4]; float *pose[4]; float mlerp; //minor lerp, poses within a group. qboolean hirachy; if (g1->isheirachical != g2->isheirachical) lerp = 0; hirachy = g1->isheirachical; mlerp = (fg1time)*g1->rate; frame1=mlerp; frame2=frame1+1; mlerp-=frame1; if (g1->loop) { frame1=frame1%g1->numposes; frame2=frame2%g1->numposes; } else { frame1=(frame1>g1->numposes-1)?g1->numposes-1:frame1; frame2=(frame2>g1->numposes-1)?g1->numposes-1:frame2; } plerp[l] = (1-mlerp)*(1-lerp); if (plerp[l]>0) pose[l++] = (float *)((char *)g1 + g1->poseofs + sizeof(float)*inf->numbones*12*frame1); plerp[l] = (mlerp)*(1-lerp); if (plerp[l]>0) pose[l++] = (float *)((char *)g1 + g1->poseofs + sizeof(float)*inf->numbones*12*frame2); if (lerp) { mlerp = (fg2time)*g2->rate; frame1=mlerp; frame2=frame1+1; mlerp-=frame1; if (g2->loop) { frame1=frame1%g2->numposes; frame2=frame2%g2->numposes; } else { frame1=(frame1>g2->numposes-1)?g2->numposes-1:frame1; frame2=(frame2>g2->numposes-1)?g2->numposes-1:frame2; } plerp[l] = (1-mlerp)*(lerp); if (plerp[l]>0) pose[l++] = (float *)((char *)g2 + g2->poseofs + sizeof(float)*inf->numbones*12*frame1); plerp[l] = (mlerp)*(lerp); if (plerp[l]>0) pose[l++] = (float *)((char *)g2 + g2->poseofs + sizeof(float)*inf->numbones*12*frame2); } /* pose[0] = (float *)((char *)g1 + g1->poseofs); plerp[0] = 1; plerp[1] = 0; plerp[3] = 0; plerp[4] = 0; l = 1; */ R_BuildSkeletalMesh(mesh, plerp, pose, l, (galiasbone_t *)((char*)inf+inf->ofsbones), inf->numbones, (galisskeletaltransforms_t *)((char*)inf+inf->ofstransforms), inf->numtransforms, hirachy); return false; } #endif if (g1 == g2) //lerping within group is only done if not changing group { lerp = fg1time*g1->rate; if (lerp < 0) lerp = 0; //hrm frame1=lerp; frame2=frame1+1; lerp-=frame1; if (g1->loop) { frame1=frame1%g1->numposes; frame2=frame2%g1->numposes; } else { frame1=(frame1>g1->numposes-1)?g1->numposes-1:frame1; frame2=(frame2>g1->numposes-1)?g1->numposes-1:frame2; } } else //don't bother with a four way lerp. Yeah, this will produce jerkyness with models with just framegroups. { frame1=0; frame2=0; } R_LerpFrames(mesh, (galiaspose_t *)((char *)g1 + g1->poseofs + sizeof(galiaspose_t)*frame1), (galiaspose_t *)((char *)g2 + g2->poseofs + sizeof(galiaspose_t)*frame2), 1-lerp, (qbyte)(alpha*255), currententity->fatness);//20*sin(cl.time)); return true; //to allow the mesh to be dlighted. } void GL_GAliasFlushSkinCache(void) { int i; bucket_t *b; for (i = 0; i < skincolourmapped.numbuckets; i++) { while((b = skincolourmapped.bucket[i])) { skincolourmapped.bucket[i] = b->next; BZ_Free(b->data); } } if (skincolourmapped.bucket) BZ_Free(skincolourmapped.bucket); skincolourmapped.bucket = NULL; skincolourmapped.numbuckets = 0; } static galiastexnum_t *GL_ChooseSkin(galiasinfo_t *inf, char *modelname, int surfnum, entity_t *e) { galiasskin_t *skins; galiastexnum_t *texnums; int frame; int tc, bc; int local; if (!gl_nocolors.value) { if (e->scoreboard) { if (!e->scoreboard->skin) Skin_Find(e->scoreboard); tc = e->scoreboard->topcolor; bc = e->scoreboard->bottomcolor; //colour forcing if (cl.splitclients<2 && !(cl.fpd & FPD_NO_FORCE_COLOR)) //no colour/skin forcing in splitscreen. { if (cl.teamplay && cl.spectator) { local = Cam_TrackNum(0); if (local < 0) local = cl.playernum[0]; } else local = cl.playernum[0]; if (cl.teamplay && !strcmp(e->scoreboard->team, cl.players[local].team)) { if (cl_teamtopcolor>=0) tc = cl_teamtopcolor; if (cl_teambottomcolor>=0) bc = cl_teambottomcolor; } else { if (cl_enemytopcolor>=0) tc = cl_enemytopcolor; if (cl_enemybottomcolor>=0) bc = cl_enemybottomcolor; } } } else { tc = 1; bc = 1; } if (tc != 1 || bc != 1 || (e->scoreboard && e->scoreboard->skin)) { int inwidth, inheight; int tinwidth, tinheight; char *skinname; qbyte *original; int cc; galiascolourmapped_t *cm; char hashname[512]; cc = (tc<<4)|bc; if (e->scoreboard && e->scoreboard->skin && !gl_nocolors.value) { sprintf(hashname, "%s$%s$%i", modelname, e->scoreboard->skin->name, surfnum); skinname = hashname; } else if (surfnum) { sprintf(hashname, "%s$%i", modelname, surfnum); skinname = hashname; } else skinname = modelname; if (!skincolourmapped.numbuckets) Hash_InitTable(&skincolourmapped, 256, BZ_Malloc(Hash_BytesForBuckets(256))); for (cm = Hash_Get(&skincolourmapped, skinname); cm; cm = Hash_GetNext(&skincolourmapped, skinname, cm)) { if (cm->colour == cc && cm->skinnum == e->skinnum) { return &cm->texnum; } } if (!inf->numskins) { skins = NULL; texnums = NULL; } else { skins = (galiasskin_t*)((char *)inf + inf->ofsskins); if (!skins->texnums) return NULL; if (e->skinnum >= 0 && e->skinnum < inf->numskins) skins += e->skinnum; texnums = (galiastexnum_t*)((char *)skins + skins->ofstexnums); } //colourmap isn't present yet. cm = BZ_Malloc(sizeof(*cm)); Q_strncpyz(cm->name, skinname, sizeof(cm->name)); Hash_Add(&skincolourmapped, cm->name, cm, &cm->bucket); cm->colour = cc; cm->skinnum = e->skinnum; cm->texnum.fullbright = 0; cm->texnum.base = 0; if (!texnums) { //load just the skin if (e->scoreboard && e->scoreboard->skin) { original = Skin_Cache8(e->scoreboard->skin); if (!original) return NULL; inwidth = e->scoreboard->skin->width; inheight = e->scoreboard->skin->height; cm->texnum.base = cm->texnum.fullbright = GL_LoadTexture(e->scoreboard->skin->name, inwidth, inheight, original, true, false); return &cm->texnum; } return NULL; } cm->texnum.bump = texnums[cm->skinnum].bump; //can't colour bumpmapping if ((!texnums || !strcmp(modelname, "progs/player.mdl")) && e->scoreboard && e->scoreboard->skin) { original = Skin_Cache8(e->scoreboard->skin); inwidth = e->scoreboard->skin->width; inheight = e->scoreboard->skin->height; } else { original = NULL; inwidth = 0; inheight = 0; } if (!original) { if (skins->ofstexels) { original = (qbyte *)skins + skins->ofstexels; inwidth = skins->skinwidth; inheight = skins->skinheight; } else { original = NULL; inwidth = 0; inheight = 0; } } tinwidth = skins->skinwidth; tinheight = skins->skinheight; if (original) { int i, j; qbyte translate[256]; unsigned translate32[256]; static unsigned pixels[512*512]; unsigned *out; unsigned frac, fracstep; unsigned scaled_width, scaled_height; qbyte *inrow; texnums = &cm->texnum; texnums->base = 0; texnums->fullbright = 0; scaled_width = gl_max_size.value < 512 ? gl_max_size.value : 512; scaled_height = gl_max_size.value < 512 ? gl_max_size.value : 512; for (i=0 ; i<256 ; i++) translate[i] = i; tc<<=4; bc<<=4; for (i=0 ; i<16 ; i++) { if (tc < 128) // the artists made some backwards ranges. sigh. translate[TOP_RANGE+i] = tc+i; else translate[TOP_RANGE+i] = tc+15-i; if (bc < 128) translate[BOTTOM_RANGE+i] = bc+i; else translate[BOTTOM_RANGE+i] = bc+15-i; } for (i=0 ; i<256 ; i++) translate32[i] = d_8to24rgbtable[translate[i]]; out = pixels; fracstep = tinwidth*0x10000/scaled_width; for (i=0 ; i> 1; for (j=0 ; j>16]]; frac += fracstep; out[j+1] = translate32[inrow[frac>>16]]; frac += fracstep; out[j+2] = translate32[inrow[frac>>16]]; frac += fracstep; out[j+3] = translate32[inrow[frac>>16]]; frac += fracstep; } } texnums->base = texture_extension_number++; GL_Bind(texnums->base); qglTexImage2D (GL_TEXTURE_2D, 0, gl_solid_format, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //now do the fullbrights. out = pixels; fracstep = tinwidth*0x10000/scaled_width; for (i=0 ; i> 1; for (j=0 ; j>16] < 255-vid.fullbright) ((char *) (&out[j]))[3] = 0; //alpha 0 frac += fracstep; } } texnums->fullbright = texture_extension_number++; GL_Bind(texnums->fullbright); qglTexImage2D (GL_TEXTURE_2D, 0, gl_alpha_format, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { skins = (galiasskin_t*)((char *)inf + inf->ofsskins); if (e->skinnum >= 0 && e->skinnum < inf->numskins) skins += e->skinnum; if (!skins->texnums) return NULL; frame = cl.time*skins->skinspeed; frame = frame%skins->texnums; texnums = (galiastexnum_t*)((char *)skins + skins->ofstexnums + frame*sizeof(galiastexnum_t)); memcpy(&cm->texnum, texnums, sizeof(cm->texnum)); } return &cm->texnum; } } if (!inf->numskins) return NULL; skins = (galiasskin_t*)((char *)inf + inf->ofsskins); if (e->skinnum >= 0 && e->skinnum < inf->numskins) skins += e->skinnum; else Con_DPrintf("Skin number out of range\n"); if (!skins->texnums) return NULL; frame = cl.time*skins->skinspeed; frame = frame%skins->texnums; texnums = (galiastexnum_t*)((char *)skins + skins->ofstexnums + frame*sizeof(galiastexnum_t)); return texnums; } static int numFacing; static qbyte *triangleFacing; static void R_CalcFacing(mesh_t *mesh, vec3_t lightpos) { float *v1, *v2, *v3; vec3_t d1, d2, norm; int i; index_t *indexes = mesh->indexes; int numtris = mesh->numindexes/3; if (numFacing < numtris) { if (triangleFacing) BZ_Free(triangleFacing); triangleFacing = BZ_Malloc(sizeof(*triangleFacing)*numtris); numFacing = numtris; } for (i = 0; i < numtris; i++, indexes+=3) { v1 = (float *)(mesh->xyz_array + indexes[0]); v2 = (float *)(mesh->xyz_array + indexes[1]); v3 = (float *)(mesh->xyz_array + indexes[2]); VectorSubtract(v1, v2, d1); VectorSubtract(v3, v2, d2); CrossProduct(d1, d2, norm); triangleFacing[i] = (( lightpos[0] - v1[0] ) * norm[0] + ( lightpos[1] - v1[1] ) * norm[1] + ( lightpos[2] - v1[2] ) * norm[2]) > 0; } } #define PROJECTION_DISTANCE 30000 static int numProjectedShadowVerts; static vec3_t *ProjectedShadowVerts; static void R_ProjectShadowVolume(mesh_t *mesh, vec3_t lightpos) { int numverts = mesh->numvertexes; int i; vec3_t *input = mesh->xyz_array; vec3_t *projected; if (numProjectedShadowVerts < numverts) { if (ProjectedShadowVerts) BZ_Free(ProjectedShadowVerts); ProjectedShadowVerts = BZ_Malloc(sizeof(*ProjectedShadowVerts)*numverts); numProjectedShadowVerts = numverts; } projected = ProjectedShadowVerts; for (i = 0; i < numverts; i++) { projected[i][0] = input[i][0] + (input[i][0]-lightpos[0])*PROJECTION_DISTANCE; projected[i][1] = input[i][1] + (input[i][1]-lightpos[1])*PROJECTION_DISTANCE; projected[i][2] = input[i][2] + (input[i][2]-lightpos[2])*PROJECTION_DISTANCE; } } static void R_DrawShadowVolume(mesh_t *mesh) { int t; vec3_t *proj = ProjectedShadowVerts; vec3_t *verts = mesh->xyz_array; index_t *indexes = mesh->indexes; int *neighbours = mesh->trneighbors; int numtris = mesh->numindexes/3; qglBegin(GL_TRIANGLES); for (t = 0; t < numtris; t++) { if (triangleFacing[t]) { //draw front qglVertex3fv(verts[indexes[t*3+0]]); qglVertex3fv(verts[indexes[t*3+1]]); qglVertex3fv(verts[indexes[t*3+2]]); //draw back qglVertex3fv(proj[indexes[t*3+1]]); qglVertex3fv(proj[indexes[t*3+0]]); qglVertex3fv(proj[indexes[t*3+2]]); //draw side caps if (neighbours[t*3+0] < 0 || !triangleFacing[neighbours[t*3+0]]) { qglVertex3fv(verts[indexes[t*3+1]]); qglVertex3fv(verts[indexes[t*3+0]]); qglVertex3fv(proj [indexes[t*3+0]]); qglVertex3fv(verts[indexes[t*3+1]]); qglVertex3fv(proj [indexes[t*3+0]]); qglVertex3fv(proj [indexes[t*3+1]]); } if (neighbours[t*3+1] < 0 || !triangleFacing[neighbours[t*3+1]]) { qglVertex3fv(verts[indexes[t*3+2]]); qglVertex3fv(verts[indexes[t*3+1]]); qglVertex3fv(proj [indexes[t*3+1]]); qglVertex3fv(verts[indexes[t*3+2]]); qglVertex3fv(proj [indexes[t*3+1]]); qglVertex3fv(proj [indexes[t*3+2]]); } if (neighbours[t*3+2] < 0 || !triangleFacing[neighbours[t*3+2]]) { qglVertex3fv(verts[indexes[t*3+0]]); qglVertex3fv(verts[indexes[t*3+2]]); qglVertex3fv(proj [indexes[t*3+2]]); qglVertex3fv(verts[indexes[t*3+0]]); qglVertex3fv(proj [indexes[t*3+2]]); qglVertex3fv(proj [indexes[t*3+0]]); } } } qglEnd(); } void GL_DrawAliasMesh_Sketch (mesh_t *mesh) { int i; extern int gldepthfunc; #ifdef Q3SHADERS R_UnlockArrays(); #endif qglDepthFunc(gldepthfunc); qglDepthMask(1); if (gldepthmin == 0.5) qglCullFace ( GL_BACK ); else qglCullFace ( GL_FRONT ); GL_TexEnv(GL_MODULATE); qglDisable(GL_TEXTURE_2D); qglVertexPointer(3, GL_FLOAT, 0, mesh->xyz_array); qglEnableClientState( GL_VERTEX_ARRAY ); if (mesh->normals_array && qglNormalPointer) //d3d wrapper doesn't support normals, and this is only really needed for truform { qglNormalPointer(GL_FLOAT, 0, mesh->normals_array); qglEnableClientState( GL_NORMAL_ARRAY ); } qglColor3f(1,1,1); /* if (mesh->colors_array) { qglColorPointer(4, GL_UNSIGNED_BYTE, 0, mesh->colors_array); qglEnableClientState( GL_COLOR_ARRAY ); } else */ qglDisableClientState( GL_COLOR_ARRAY ); qglDrawElements(GL_TRIANGLES, mesh->numindexes, GL_UNSIGNED_INT, mesh->indexes); qglDisableClientState( GL_VERTEX_ARRAY ); qglDisableClientState( GL_COLOR_ARRAY ); qglDisableClientState( GL_NORMAL_ARRAY ); if (mesh->colors_array) qglColor4ub(0, 0, 0, mesh->colors_array[0][3]); else qglColor3f(0, 0, 0); qglBegin(GL_LINES); for (i = 0; i < mesh->numindexes; i+=3) { float *v1, *v2, *v3; int n; v1 = mesh->xyz_array[mesh->indexes[i+0]]; v2 = mesh->xyz_array[mesh->indexes[i+1]]; v3 = mesh->xyz_array[mesh->indexes[i+2]]; for (n = 0; n < 3; n++) //rember we do this triangle AND the neighbours { qglVertex3f(v1[0]+0.5*(rand()/(float)RAND_MAX-0.5), v1[1]+0.5*(rand()/(float)RAND_MAX-0.5), v1[2]+0.5*(rand()/(float)RAND_MAX-0.5)); qglVertex3f(v2[0]+0.5*(rand()/(float)RAND_MAX-0.5), v2[1]+0.5*(rand()/(float)RAND_MAX-0.5), v2[2]+0.5*(rand()/(float)RAND_MAX-0.5)); qglVertex3f(v2[0]+0.5*(rand()/(float)RAND_MAX-0.5), v2[1]+0.5*(rand()/(float)RAND_MAX-0.5), v2[2]+0.5*(rand()/(float)RAND_MAX-0.5)); qglVertex3f(v3[0]+0.5*(rand()/(float)RAND_MAX-0.5), v3[1]+0.5*(rand()/(float)RAND_MAX-0.5), v3[2]+0.5*(rand()/(float)RAND_MAX-0.5)); qglVertex3f(v3[0]+0.5*(rand()/(float)RAND_MAX-0.5), v3[1]+0.5*(rand()/(float)RAND_MAX-0.5), v3[2]+0.5*(rand()/(float)RAND_MAX-0.5)); qglVertex3f(v1[0]+0.5*(rand()/(float)RAND_MAX-0.5), v1[1]+0.5*(rand()/(float)RAND_MAX-0.5), v1[2]+0.5*(rand()/(float)RAND_MAX-0.5)); } } qglEnd(); #ifdef Q3SHADERS R_IBrokeTheArrays(); #endif } //called from sprite code. /* void GL_KnownState(void) { extern int gldepthfunc; qglDepthFunc(gldepthfunc); qglDepthMask(1); if (gldepthmin == 0.5) qglCullFace ( GL_BACK ); else qglCullFace ( GL_FRONT ); GL_TexEnv(GL_MODULATE); qglEnable (GL_BLEND); qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } */ void GL_DrawAliasMesh (mesh_t *mesh, int texnum) { extern int gldepthfunc; #ifdef Q3SHADERS R_UnlockArrays(); #endif qglDepthFunc(gldepthfunc); qglDepthMask(1); GL_Bind(texnum); if (gldepthmin == 0.5) qglCullFace ( GL_BACK ); else qglCullFace ( GL_FRONT ); GL_TexEnv(GL_MODULATE); qglVertexPointer(3, GL_FLOAT, 0, mesh->xyz_array); qglEnableClientState( GL_VERTEX_ARRAY ); if (mesh->normals_array && qglNormalPointer) //d3d wrapper doesn't support normals, and this is only really needed for truform { qglNormalPointer(GL_FLOAT, 0, mesh->normals_array); qglEnableClientState( GL_NORMAL_ARRAY ); } if (mesh->colors_array) { qglColorPointer(4, GL_UNSIGNED_BYTE, 0, mesh->colors_array); qglEnableClientState( GL_COLOR_ARRAY ); } else qglDisableClientState( GL_COLOR_ARRAY ); qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); qglTexCoordPointer(2, GL_FLOAT, 0, mesh->st_array); qglDrawRangeElements(GL_TRIANGLES, 0, mesh->numvertexes, mesh->numindexes, GL_UNSIGNED_INT, mesh->indexes); qglDisableClientState( GL_VERTEX_ARRAY ); qglDisableClientState( GL_COLOR_ARRAY ); qglDisableClientState( GL_NORMAL_ARRAY ); qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); #ifdef Q3SHADERS R_IBrokeTheArrays(); #endif } qboolean R_CullEntityBox(entity_t *e, vec3_t modmins, vec3_t modmaxs) { int i; vec3_t wmin, wmax; float fmin, fmax; //convert the model's bbox to the expanded maximum size of the entity, as drawn with this model. //The result is an axial box, which we pass to R_CullBox for (i = 0; i < 3; i++) { fmin = DotProduct(modmins, e->axis[i]); fmax = DotProduct(modmaxs, e->axis[i]); if (fmin > -16) fmin = -16; if (fmax < 16) fmax = 16; if (fmin < fmax) { wmin[i] = e->origin[i]+fmin; wmax[i] = e->origin[i]+fmax; } else { //box went inside out wmin[i] = e->origin[i]+fmax; wmax[i] = e->origin[i]+fmin; } } return R_CullBox(wmin, wmax); } void R_DrawGAliasModel (entity_t *e) { extern cvar_t r_drawflat; model_t *clmodel; vec3_t dist; vec_t add; int i; galiasinfo_t *inf; mesh_t mesh; galiastexnum_t *skin; float entScale; vec3_t lightdir; vec3_t saveorg; #ifdef Q3SHADERS mfog_t *fog; #endif int surfnum; float tmatrix[3][4]; currententity = e; // if (e->flags & Q2RF_VIEWERMODEL && e->keynum == cl.playernum[r_refdef.currentplayernum]+1) // return; if (r_secondaryview && e->flags & Q2RF_WEAPONMODEL) return; { extern int cl_playerindex; if (e->scoreboard && e->model == cl.model_precache[cl_playerindex]) { clmodel = e->scoreboard->model; if (!clmodel || clmodel->type != mod_alias) clmodel = e->model; } else clmodel = e->model; } if (!(e->flags & Q2RF_WEAPONMODEL)) if (R_CullEntityBox (e, clmodel->mins, clmodel->maxs)) return; if (!(r_refdef.flags & Q2RDF_NOWORLDMODEL)) { if (e->flags & Q2RF_WEAPONMODEL) cl.worldmodel->funcs.LightPointValues(r_refdef.vieworg, shadelight, ambientlight, lightdir); else cl.worldmodel->funcs.LightPointValues(e->origin, shadelight, ambientlight, lightdir); } else { ambientlight[0] = ambientlight[1] = ambientlight[2] = shadelight[0] = shadelight[1] = shadelight[2] = 255; lightdir[0] = 0; lightdir[1] = 1; lightdir[2] = 1; } if (!r_vertexdlights.value) { for (i=0 ; iorigin, cl_dlights[i].origin, dist); add = cl_dlights[i].radius - Length(dist); if (add > 0) { add*=5; ambientlight[0] += add * cl_dlights[i].color[0]; ambientlight[1] += add * cl_dlights[i].color[1]; ambientlight[2] += add * cl_dlights[i].color[2]; //ZOID models should be affected by dlights as well shadelight[0] += add * cl_dlights[i].color[0]; shadelight[1] += add * cl_dlights[i].color[1]; shadelight[2] += add * cl_dlights[i].color[2]; } } } } else { } for (i = 0; i < 3; i++) //clamp light so it doesn't get vulgar. { if (ambientlight[i] > 128) ambientlight[i] = 128; if (ambientlight[i] + shadelight[i] > 192) shadelight[i] = 192 - ambientlight[i]; } if (e->flags & Q2RF_WEAPONMODEL) { for (i = 0; i < 3; i++) { if (ambientlight[i] < 24) ambientlight[i] = shadelight[i] = 24; } } //MORE HUGE HACKS! WHEN WILL THEY CEASE! // clamp lighting so it doesn't overbright as much // ZOID: never allow players to go totally black if (!strcmp(clmodel->name, "progs/player.mdl")) { float fb = r_fullbrightSkins.value; if (fb > cls.allow_fbskins) fb = cls.allow_fbskins; if (fb < 0) fb = 0; if (fb) { for (i = 0; i < 3; i++) { ambientlight[i] = max(ambientlight[i], 8 + fb * 120); shadelight[i] = max(shadelight[i], 8 + fb * 120); } } for (i = 0; i < 3; i++) { if (ambientlight[i] < 8) ambientlight[i] = shadelight[i] = 8; } } for (i = 0; i < 3; i++) { if (ambientlight[i] > 128) ambientlight[i] = 128; shadelight[i] /= 200.0/255; ambientlight[i] /= 200.0/255; } if ((e->drawflags & MLS_MASKIN) == MLS_ABSLIGHT) { shadelight[0] = shadelight[1] = shadelight[2] = e->abslight; ambientlight[0] = ambientlight[1] = ambientlight[2] = 0; } if ((e->drawflags & MLS_MASKIN) == MLS_FULLBRIGHT || e->flags & Q2RF_FULLBRIGHT) { shadelight[0] = shadelight[1] = shadelight[2] = 255; ambientlight[0] = ambientlight[1] = ambientlight[2] = 0; } //#define SHOWLIGHTDIR { //lightdir is absolute, shadevector is relative shadevector[0] = DotProduct(lightdir, e->axis[0]); shadevector[1] = DotProduct(lightdir, e->axis[1]); shadevector[2] = DotProduct(lightdir, e->axis[2]); VectorNormalize(shadevector); VectorCopy(shadevector, mesh.lightaxis[2]); VectorVectors(mesh.lightaxis[2], mesh.lightaxis[1], mesh.lightaxis[0]); VectorInverse(mesh.lightaxis[1]); } if (e->flags & Q2RF_GLOW) { shadelight[0] += sin(cl.time)*0.25; shadelight[1] += sin(cl.time)*0.25; shadelight[2] += sin(cl.time)*0.25; } /* VectorClear(ambientlight); VectorClear(shadelight); */ /* an = e->angles[1]/180*M_PI; shadevector[0] = cos(-an); shadevector[1] = sin(-an); shadevector[2] = 1; VectorNormalize (shadevector); */ GL_DisableMultitexture(); GL_TexEnv(GL_MODULATE); if (gl_smoothmodels.value) qglShadeModel (GL_SMOOTH); if (gl_affinemodels.value) qglHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); qglDisable (GL_ALPHA_TEST); if (e->flags & Q2RF_DEPTHHACK) qglDepthRange (gldepthmin, gldepthmin + 0.3*(gldepthmax-gldepthmin)); // glColor3f( 1,1,1); if (e->flags & Q2RF_ADDATIVE) { qglEnable (GL_BLEND); qglBlendFunc(GL_ONE, GL_ONE); } else if ((e->model->flags & EF_SPECIAL_TRANS)) //hexen2 flags. { qglEnable (GL_BLEND); qglBlendFunc (GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA); // glColor3f( 1,1,1); qglDisable( GL_CULL_FACE ); } else if (e->drawflags & DRF_TRANSLUCENT) { qglEnable (GL_BLEND); qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); e->alpha = r_wateralpha.value; } else if ((e->model->flags & EF_TRANSPARENT)) { qglEnable (GL_BLEND); qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else if ((e->model->flags & EF_HOLEY)) { qglEnable (GL_ALPHA_TEST); // qglEnable (GL_BLEND); qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else if (e->alpha < 1) { qglEnable(GL_BLEND); qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { qglDisable(GL_BLEND); qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } // qglEnable (GL_ALPHA_TEST); qglPushMatrix(); R_RotateForEntity(e); if (e->scale != 1 && e->scale != 0) //hexen 2 stuff { vec3_t scale; vec3_t scale_origin; float xyfact, zfact; scale[0] = (clmodel->maxs[0]-clmodel->mins[0])/255; scale[1] = (clmodel->maxs[1]-clmodel->mins[1])/255; scale[2] = (clmodel->maxs[2]-clmodel->mins[2])/255; scale_origin[0] = clmodel->mins[0]; scale_origin[1] = clmodel->mins[1]; scale_origin[2] = clmodel->mins[2]; /* qglScalef( 1/scale[0], 1/scale[1], 1/scale[2]); qglTranslatef ( -scale_origin[0], -scale_origin[1], -scale_origin[2]); */ if(e->scale != 0 && e->scale != 1) { entScale = (float)e->scale; switch(e->drawflags&SCALE_TYPE_MASKIN) { default: case SCALE_TYPE_UNIFORM: tmatrix[0][0] = scale[0]*entScale; tmatrix[1][1] = scale[1]*entScale; tmatrix[2][2] = scale[2]*entScale; xyfact = zfact = (entScale-1.0)*127.95; break; case SCALE_TYPE_XYONLY: tmatrix[0][0] = scale[0]*entScale; tmatrix[1][1] = scale[1]*entScale; tmatrix[2][2] = scale[2]; xyfact = (entScale-1.0)*127.95; zfact = 1.0; break; case SCALE_TYPE_ZONLY: tmatrix[0][0] = scale[0]; tmatrix[1][1] = scale[1]; tmatrix[2][2] = scale[2]*entScale; xyfact = 1.0; zfact = (entScale-1.0)*127.95; break; } switch(currententity->drawflags&SCALE_ORIGIN_MASKIN) { default: case SCALE_ORIGIN_CENTER: tmatrix[0][3] = scale_origin[0]-scale[0]*xyfact; tmatrix[1][3] = scale_origin[1]-scale[1]*xyfact; tmatrix[2][3] = scale_origin[2]-scale[2]*zfact; break; case SCALE_ORIGIN_BOTTOM: tmatrix[0][3] = scale_origin[0]-scale[0]*xyfact; tmatrix[1][3] = scale_origin[1]-scale[1]*xyfact; tmatrix[2][3] = scale_origin[2]; break; case SCALE_ORIGIN_TOP: tmatrix[0][3] = scale_origin[0]-scale[0]*xyfact; tmatrix[1][3] = scale_origin[1]-scale[1]*xyfact; tmatrix[2][3] = scale_origin[2]-scale[2]*zfact*2.0; break; } } else { tmatrix[0][0] = scale[0]; tmatrix[1][1] = scale[1]; tmatrix[2][2] = scale[2]; tmatrix[0][3] = scale_origin[0]; tmatrix[1][3] = scale_origin[1]; tmatrix[2][3] = scale_origin[2]; } /* if(clmodel->flags&EF_ROTATE) { // Floating motion tmatrix[2][3] += sin(currententity->origin[0] +currententity->origin[1]+(cl.time*3))*5.5; }*/ qglTranslatef (tmatrix[0][3],tmatrix[1][3],tmatrix[2][3]); qglScalef (tmatrix[0][0],tmatrix[1][1],tmatrix[2][2]); qglScalef( 1/scale[0], 1/scale[1], 1/scale[2]); qglTranslatef ( -scale_origin[0], -scale_origin[1], -scale_origin[2]); } inf = GLMod_Extradata (clmodel); if (qglPNTrianglesfATI && gl_ati_truform.value) qglEnable(GL_PN_TRIANGLES_ATI); if (e->flags & Q2RF_WEAPONMODEL) { VectorCopy(currententity->origin, saveorg); VectorCopy(r_refdef.vieworg, currententity->origin); } #ifdef Q3SHADERS fog = CM_FogForOrigin(currententity->origin); #endif qglColor4f(shadelight[0]/255, shadelight[1]/255, shadelight[2]/255, e->alpha); memset(&mesh, 0, sizeof(mesh)); for(surfnum=0; inf; ((inf->nextsurf)?(inf = (galiasinfo_t*)((char *)inf + inf->nextsurf)):(inf=NULL)), surfnum++) { if (R_GAliasBuildMesh(&mesh, inf, e->frame, e->oldframe, e->lerpfrac, e->alpha, e->frame1time, e->frame2time) && r_vertexdlights.value) if (mesh.colors_array) R_GAliasAddDlights(&mesh, e->origin, e->angles); c_alias_polys += mesh.numindexes/3; if (r_drawflat.value == 2) { GL_DrawAliasMesh_Sketch(&mesh); continue; } #ifdef Q3SHADERS else if (currententity->forcedshader) { meshbuffer_t mb; R_IBrokeTheArrays(); mb.entity = &r_worldentity; mb.shader = currententity->forcedshader; mb.fog = fog; mb.mesh = &mesh; mb.infokey = -1;//currententity->keynum; mb.dlightbits = 0; R_PushMesh(&mesh, mb.shader->features | MF_NONBATCHED | MF_COLORS); R_RenderMeshBuffer ( &mb, false ); continue; } #endif skin = GL_ChooseSkin(inf, clmodel->name, surfnum, e); if (!skin) { GL_DrawAliasMesh_Sketch(&mesh); } #ifdef Q3SHADERS else if (skin->shader) { meshbuffer_t mb; int olddst = skin->shader->numpasses?skin->shader->passes[0].blenddst:0; if (e->flags & Q2RF_ADDATIVE && skin->shader->numpasses) { //hack the shader into submition. skin->shader->passes[0].blenddst = GL_ONE; skin->shader->passes[0].flags &= ~SHADER_PASS_DEPTHWRITE; } mb.entity = &r_worldentity; mb.shader = skin->shader; mb.fog = fog; mb.mesh = &mesh; mb.infokey = -1;//currententity->keynum; mb.dlightbits = 0; R_IBrokeTheArrays(); R_PushMesh(&mesh, skin->shader->features | MF_NONBATCHED | MF_COLORS); R_RenderMeshBuffer ( &mb, false ); if (e->flags & Q2RF_ADDATIVE && skin->shader->numpasses) { //hack the shader into submition. skin->shader->passes[0].blenddst = olddst; } } #endif else { qglEnable(GL_TEXTURE_2D); // if (skin->bump) // GL_DrawMeshBump(&mesh, skin->base, 0, skin->bump, 0); // else GL_DrawAliasMesh(&mesh, skin->base); if (skin->fullbright && r_fb_models.value && cls.allow_luma) { mesh.colors_array = NULL; qglEnable(GL_BLEND); qglColor4f(1, 1, 1, e->alpha*r_fb_models.value); c_alias_polys += mesh.numindexes/3; qglBlendFunc (GL_SRC_ALPHA, GL_ONE); GL_DrawAliasMesh(&mesh, skin->fullbright); qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } } } if (e->flags & Q2RF_WEAPONMODEL) VectorCopy(saveorg, currententity->origin); if (qglPNTrianglesfATI && gl_ati_truform.value) qglDisable(GL_PN_TRIANGLES_ATI); #ifdef SHOWLIGHTDIR //testing qglDisable(GL_TEXTURE_2D); qglBegin(GL_LINES); qglColor3f(1,0,0); qglVertex3f( 0, 0, 0); qglVertex3f( 100*mesh.lightaxis[0][0], 100*mesh.lightaxis[0][1], 100*mesh.lightaxis[0][2]); qglColor3f(0,1,0); qglVertex3f( 0, 0, 0); qglVertex3f( 100*mesh.lightaxis[1][0], 100*mesh.lightaxis[1][1], 100*mesh.lightaxis[1][2]); qglColor3f(0,0,1); qglVertex3f( 0, 0, 0); qglVertex3f( 100*mesh.lightaxis[2][0], 100*mesh.lightaxis[2][1], 100*mesh.lightaxis[2][2]); qglEnd(); qglEnable(GL_TEXTURE_2D); #endif qglPopMatrix(); qglDisable(GL_BLEND); qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_TexEnv(GL_REPLACE); qglEnable(GL_TEXTURE_2D); qglShadeModel (GL_FLAT); if (gl_affinemodels.value) qglHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); if (e->flags & Q2RF_DEPTHHACK) qglDepthRange (gldepthmin, gldepthmax); if ((currententity->model->flags & EF_SPECIAL_TRANS) && gl_cull.value) qglEnable( GL_CULL_FACE ); if ((currententity->model->flags & EF_HOLEY)) qglDisable( GL_ALPHA_TEST ); #ifdef SHOWLIGHTDIR //testing qglDisable(GL_TEXTURE_2D); qglColor3f(1,1,1); qglBegin(GL_LINES); qglVertex3f( currententity->origin[0], currententity->origin[1], currententity->origin[2]); qglVertex3f( currententity->origin[0]+100*lightdir[0], currententity->origin[1]+100*lightdir[1], currententity->origin[2]+100*lightdir[2]); qglEnd(); qglEnable(GL_TEXTURE_2D); #endif } //returns result in the form of the result vector void RotateLightVector(vec3_t *axis, vec3_t origin, vec3_t lightpoint, vec3_t result) { vec3_t offs; offs[0] = lightpoint[0] - origin[0]; offs[1] = lightpoint[1] - origin[1]; offs[2] = lightpoint[2] - origin[2]; result[0] = DotProduct (offs, axis[0]); result[1] = DotProduct (offs, axis[1]); result[2] = DotProduct (offs, axis[2]); } void GL_LightMesh (mesh_t *mesh, vec3_t lightpos, vec3_t colours, float radius) { vec3_t dir; int i; float dot, d, f, a; vec3_t bcolours; vec3_t *xyz = mesh->xyz_array; vec3_t *normals = mesh->normals_array; byte_vec4_t *out = mesh->colors_array; bcolours[0] = colours[0]*255; bcolours[1] = colours[1]*255; bcolours[2] = colours[2]*255; if (!out) return; //urm.. if (normals) { for (i = 0; i < mesh->numvertexes; i++) { VectorSubtract(lightpos, xyz[i], dir); dot = DotProduct(dir, normals[i]); if (dot > 0) { d = DotProduct(dir, dir)/radius; a = 1/d; if (a>0) { a *= dot/sqrt(d); f = a*bcolours[0]; if (f > 255) f = 255; else if (f < 0) f = 0; out[i][0] = f; f = a*bcolours[1]; if (f > 255) f = 255; else if (f < 0) f = 0; out[i][1] = f; f = a*bcolours[2]; if (f > 255) f = 255; else if (f < 0) f = 0; out[i][2] = f; } else { out[i][0] = 0; out[i][1] = 0; out[i][2] = 0; } } else { out[i][0] = 0; out[i][1] = 0; out[i][2] = 0; } out[i][3] = 255; } } else { if (bcolours[0] > 255) bcolours[0] = 255; if (bcolours[1] > 255) bcolours[1] = 255; if (bcolours[2] > 255) bcolours[2] = 255; for (i = 0; i < mesh->numvertexes; i++) { VectorSubtract(lightpos, xyz[i], dir); out[i][0] = bcolours[0]; out[i][1] = bcolours[1]; out[i][2] = bcolours[2]; out[i][3] = 255; } } } //courtesy of DP void R_BuildBumpVectors(const float *v0, const float *v1, const float *v2, const float *tc0, const float *tc1, const float *tc2, float *svector3f, float *tvector3f, float *normal3f) { float f, tangentcross[3], v10[3], v20[3], tc10[2], tc20[2]; // 79 add/sub/negate/multiply (1 cycle), 1 compare (3 cycle?), total cycles not counting load/store/exchange roughly 82 cycles // 6 add, 28 subtract, 39 multiply, 1 compare, 50% chance of 6 negates // 6 multiply, 9 subtract VectorSubtract(v1, v0, v10); VectorSubtract(v2, v0, v20); normal3f[0] = v10[1] * v20[2] - v10[2] * v20[1]; normal3f[1] = v10[2] * v20[0] - v10[0] * v20[2]; normal3f[2] = v10[0] * v20[1] - v10[1] * v20[0]; // 12 multiply, 10 subtract tc10[1] = tc1[1] - tc0[1]; tc20[1] = tc2[1] - tc0[1]; svector3f[0] = tc10[1] * v20[0] - tc20[1] * v10[0]; svector3f[1] = tc10[1] * v20[1] - tc20[1] * v10[1]; svector3f[2] = tc10[1] * v20[2] - tc20[1] * v10[2]; tc10[0] = tc1[0] - tc0[0]; tc20[0] = tc2[0] - tc0[0]; tvector3f[0] = tc10[0] * v20[0] - tc20[0] * v10[0]; tvector3f[1] = tc10[0] * v20[1] - tc20[0] * v10[1]; tvector3f[2] = tc10[0] * v20[2] - tc20[0] * v10[2]; // 12 multiply, 4 add, 6 subtract f = DotProduct(svector3f, normal3f); svector3f[0] -= f * normal3f[0]; svector3f[1] -= f * normal3f[1]; svector3f[2] -= f * normal3f[2]; f = DotProduct(tvector3f, normal3f); tvector3f[0] -= f * normal3f[0]; tvector3f[1] -= f * normal3f[1]; tvector3f[2] -= f * normal3f[2]; // if texture is mapped the wrong way (counterclockwise), the tangents // have to be flipped, this is detected by calculating a normal from the // two tangents, and seeing if it is opposite the surface normal // 9 multiply, 2 add, 3 subtract, 1 compare, 50% chance of: 6 negates CrossProduct(tvector3f, svector3f, tangentcross); if (DotProduct(tangentcross, normal3f) < 0) { VectorNegate(svector3f, svector3f); VectorNegate(tvector3f, tvector3f); } } //courtesy of DP void R_AliasGenerateTextureVectors(mesh_t *mesh, float *normal3f, float *svector3f, float *tvector3f) { int i; float sdir[3], tdir[3], normal[3], *v; int *e; float *vertex3f = (float*)mesh->xyz_array; float *texcoord2f = (float*)mesh->st_array; // clear the vectors // if (svector3f) memset(svector3f, 0, mesh->numvertexes * sizeof(float[3])); // if (tvector3f) memset(tvector3f, 0, mesh->numvertexes * sizeof(float[3])); // if (normal3f) memset(normal3f, 0, mesh->numvertexes * sizeof(float[3])); // process each vertex of each triangle and accumulate the results for (e = mesh->indexes; e < mesh->indexes+mesh->numindexes; e += 3) { R_BuildBumpVectors(vertex3f + e[0] * 3, vertex3f + e[1] * 3, vertex3f + e[2] * 3, texcoord2f + e[0] * 2, texcoord2f + e[1] * 2, texcoord2f + e[2] * 2, sdir, tdir, normal); // if (!areaweighting) // { // VectorNormalize(sdir); // VectorNormalize(tdir); // VectorNormalize(normal); // } // if (svector3f) for (i = 0;i < 3;i++) VectorAdd(svector3f + e[i]*3, sdir, svector3f + e[i]*3); // if (tvector3f) for (i = 0;i < 3;i++) VectorAdd(tvector3f + e[i]*3, tdir, tvector3f + e[i]*3); // if (normal3f) for (i = 0;i < 3;i++) VectorAdd(normal3f + e[i]*3, normal, normal3f + e[i]*3); } // now we could divide the vectors by the number of averaged values on // each vertex... but instead normalize them // 4 assignments, 1 divide, 1 sqrt, 2 adds, 6 multiplies if (svector3f) for (i = 0, v = svector3f;i < mesh->numvertexes;i++, v += 3) VectorNormalize(v); // 4 assignments, 1 divide, 1 sqrt, 2 adds, 6 multiplies if (tvector3f) for (i = 0, v = tvector3f;i < mesh->numvertexes;i++, v += 3) VectorNormalize(v); // 4 assignments, 1 divide, 1 sqrt, 2 adds, 6 multiplies if (normal3f) for (i = 0, v = normal3f;i < mesh->numvertexes;i++, v += 3) VectorNormalize(v); } void R_AliasGenerateVertexLightDirs(mesh_t *mesh, vec3_t lightdir, vec3_t *results, vec3_t *normal3f, vec3_t *svector3f, vec3_t *tvector3f) { int i; R_AliasGenerateTextureVectors(mesh, (float*)normal3f, (float*)svector3f, (float*)tvector3f); for (i = 0; i < mesh->numvertexes; i++) { results[i][0] = -DotProduct(lightdir, tvector3f[i]); results[i][1] = -DotProduct(lightdir, svector3f[i]); results[i][2] = -DotProduct(lightdir, normal3f[i]); } } void R_DrawMeshBumpmap(mesh_t *mesh, galiastexnum_t *skin, vec3_t lightdir) { extern int gldepthfunc; static vec3_t *lightdirs; static int maxlightdirs; extern int normalisationCubeMap; #ifdef Q3SHADERS R_UnlockArrays(); #endif //(bumpmap dot cubemap)*texture //why no luma? //that's thrown on last. //why a cubemap? //we need to pass colours as a normal somehow //we could use the fragment colour for it, however, we then wouldn't be able to colour the light. //so we use a cubemap, which has the added advantage of normalizing the light dir for us. //the bumpmap we use is tangent-space (so I'm told) qglDepthFunc(gldepthfunc); qglDepthMask(0); if (gldepthmin == 0.5) qglCullFace ( GL_BACK ); else qglCullFace ( GL_FRONT ); qglEnable(GL_BLEND); qglVertexPointer(3, GL_FLOAT, 0, mesh->xyz_array); qglEnableClientState( GL_VERTEX_ARRAY ); if (mesh->normals_array && qglNormalPointer) //d3d wrapper doesn't support normals, and this is only really needed for truform { qglNormalPointer(GL_FLOAT, 0, mesh->normals_array); qglEnableClientState( GL_NORMAL_ARRAY ); } if (mesh->colors_array) { qglColorPointer(4, GL_UNSIGNED_BYTE, 0, mesh->colors_array); qglEnableClientState( GL_COLOR_ARRAY ); } else qglDisableClientState( GL_COLOR_ARRAY ); if (maxlightdirs < mesh->numvertexes) { maxlightdirs = mesh->numvertexes; lightdirs = BZ_Malloc(sizeof(vec3_t)*maxlightdirs*4); } R_AliasGenerateVertexLightDirs(mesh, lightdir, lightdirs + maxlightdirs*0, lightdirs + maxlightdirs*1, lightdirs + maxlightdirs*2, lightdirs + maxlightdirs*3); GL_MBind(mtexid0, skin->bump); GL_TexEnv(GL_REPLACE); qglEnableClientState(GL_TEXTURE_COORD_ARRAY); qglTexCoordPointer(2, GL_FLOAT, 0, mesh->st_array); qglEnable(GL_TEXTURE_2D); GL_SelectTexture(mtexid1); GL_BindType(GL_TEXTURE_CUBE_MAP_ARB, normalisationCubeMap); qglEnable(GL_TEXTURE_CUBE_MAP_ARB); qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE); qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB); qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_DOT3_RGB_ARB); GL_TexEnv(GL_COMBINE_ARB); qglEnableClientState(GL_TEXTURE_COORD_ARRAY); qglTexCoordPointer(3, GL_FLOAT, 0, lightdirs); if (gl_mtexarbable>=3) { GL_MBind(mtexid0+2, skin->base); qglEnable(GL_TEXTURE_2D); } else { //we don't support 3tmus, so draw the bumps, and multiply the rest over the top qglDrawElements(GL_TRIANGLES, mesh->numindexes, GL_UNSIGNED_INT, mesh->indexes); qglDisable(GL_TEXTURE_CUBE_MAP_ARB); GL_MBind(mtexid0, skin->base); } GL_TexEnv(GL_MODULATE); qglEnableClientState(GL_TEXTURE_COORD_ARRAY); qglTexCoordPointer(2, GL_FLOAT, 0, mesh->st_array); qglDrawElements(GL_TRIANGLES, mesh->numindexes, GL_UNSIGNED_INT, mesh->indexes); // GL_SelectTexture(mtexid2); qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); qglDisable(GL_TEXTURE_2D); GL_SelectTexture(mtexid1); qglDisable(GL_TEXTURE_CUBE_MAP_ARB); qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); GL_TexEnv(GL_MODULATE); GL_SelectTexture(mtexid0); qglEnable(GL_TEXTURE_2D); qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); qglDisableClientState( GL_VERTEX_ARRAY ); qglDisableClientState( GL_COLOR_ARRAY ); qglDisableClientState( GL_NORMAL_ARRAY ); #ifdef Q3SHADERS R_IBrokeTheArrays(); #endif } void R_DrawGAliasModelLighting (entity_t *e, vec3_t lightpos, vec3_t colours, float radius) { #if 0 //glitches, no attenuation... :( model_t *clmodel = e->model; vec3_t mins, maxs; vec3_t lightdir; galiasinfo_t *inf; galiastexnum_t *tex; mesh_t mesh; int surfnum; extern cvar_t r_nolightdir; if (e->flags & Q2RF_VIEWERMODEL) return; if (r_nolightdir.value) //are you crazy? return; //Total insanity with r_shadows 2... // if (!strcmp (clmodel->name, "progs/flame2.mdl")) // CL_NewDlight (e, e->origin[0]-1, e->origin[1]+1, e->origin[2]+24, 200 + (rand()&31), host_frametime*2, 3); // if (!strcmp (clmodel->name, "progs/armor.mdl")) // CL_NewDlight (e->keynum, e->origin[0]-1, e->origin[1]+1, e->origin[2]+25, 200 + (rand()&31), host_frametime*2, 3); VectorAdd (e->origin, clmodel->mins, mins); VectorAdd (e->origin, clmodel->maxs, maxs); // if (!(e->flags & Q2RF_WEAPONMODEL)) // if (R_CullBox (mins, maxs)) // return; RotateLightVector(e->axis, e->origin, lightpos, lightdir); GL_DisableMultitexture(); GL_TexEnv(GL_MODULATE); if (gl_smoothmodels.value) qglShadeModel (GL_SMOOTH); if (gl_affinemodels.value) qglHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); if (e->flags & Q2RF_DEPTHHACK) qglDepthRange (gldepthmin, gldepthmin + 0.3*(gldepthmax-gldepthmin)); qglColor3f(colours[0], colours[1], colours[2]); qglColor4f(1, 1, 1, 1); qglPushMatrix(); R_RotateForEntity(e); inf = GLMod_Extradata (clmodel); if (gl_ati_truform.value) qglEnable(GL_PN_TRIANGLES_ATI); qglEnable(GL_TEXTURE_2D); qglEnable(GL_POLYGON_OFFSET_FILL); GL_TexEnv(GL_REPLACE); // qglDisable(GL_STENCIL_TEST); qglEnable(GL_BLEND); qglDisable(GL_ALPHA_TEST); //if you used an alpha channel where you shouldn't have, more fool you. qglBlendFunc(GL_ONE, GL_ONE); // qglDepthFunc(GL_ALWAYS); for(surfnum=0;inf;surfnum++) { R_GAliasBuildMesh(&mesh, inf, e->frame, e->oldframe, e->lerpfrac, e->alpha, e->frame1time, e->frame2time); mesh.colors_array = tempColours; tex = GL_ChooseSkin(inf, clmodel->name, surfnum, e); if (tex->bump && e->alpha==1) { R_DrawMeshBumpmap(&mesh, tex, lightdir); } else { GL_LightMesh(&mesh, lightdir, colours, radius); GL_DrawAliasMesh(&mesh, tex->base); } if (inf->nextsurf) inf = (galiasinfo_t*)((char *)inf + inf->nextsurf); else inf = NULL; } currententity->fatness=0; qglPopMatrix(); if (gl_ati_truform.value) qglDisable(GL_PN_TRIANGLES_ATI); GL_TexEnv(GL_REPLACE); qglShadeModel (GL_FLAT); if (gl_affinemodels.value) qglHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); qglDisable(GL_POLYGON_OFFSET_FILL); if (e->flags & Q2RF_DEPTHHACK) qglDepthRange (gldepthmin, gldepthmax); qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); qglDisable(GL_BLEND); qglDisable(GL_TEXTURE_2D); R_IBrokeTheArrays(); #endif } //FIXME: Be less agressive. //This function will have to be called twice (for geforce cards), with the same data, so do the building once and rendering twice. void R_DrawGAliasShadowVolume(entity_t *e, vec3_t lightpos, float radius) { model_t *clmodel = e->model; galiasinfo_t *inf; mesh_t mesh; vec3_t lightorg; if (!strcmp (clmodel->name, "progs/flame2.mdl")) return; if (!strncmp (clmodel->name, "progs/bolt", 10)) return; if (r_noaliasshadows.value) return; RotateLightVector(e->axis, e->origin, lightpos, lightorg); if (Length(lightorg) > radius + clmodel->radius) return; qglPushMatrix(); R_RotateForEntity(e); inf = GLMod_Extradata (clmodel); while(inf) { if (inf->ofs_trineighbours) { R_GAliasBuildMesh(&mesh, inf, e->frame, e->oldframe, e->lerpfrac, e->alpha, e->frame1time, e->frame2time); R_CalcFacing(&mesh, lightorg); R_ProjectShadowVolume(&mesh, lightorg); R_DrawShadowVolume(&mesh); } if (inf->nextsurf) inf = (galiasinfo_t*)((char *)inf + inf->nextsurf); else inf = NULL; } qglPopMatrix(); } static int R_FindTriangleWithEdge ( int *indexes, int numtris, int start, int end, int ignore) { int i; int match, count; count = 0; match = -1; for (i = 0; i < numtris; i++, indexes += 3) { if ( (indexes[0] == start && indexes[1] == end) || (indexes[1] == start && indexes[2] == end) || (indexes[2] == start && indexes[0] == end) ) { if (i != ignore) match = i; count++; } else if ( (indexes[1] == start && indexes[0] == end) || (indexes[2] == start && indexes[1] == end) || (indexes[0] == start && indexes[2] == end) ) { count++; } } // detect edges shared by three triangles and make them seams if (count > 2) match = -1; return match; } static void R_BuildTriangleNeighbours ( int *neighbours, int *indexes, int numtris ) { int i, *n; int *index; for (i = 0, index = indexes, n = neighbours; i < numtris; i++, index += 3, n += 3) { n[0] = R_FindTriangleWithEdge (indexes, numtris, index[1], index[0], i); n[1] = R_FindTriangleWithEdge (indexes, numtris, index[2], index[1], i); n[2] = R_FindTriangleWithEdge (indexes, numtris, index[0], index[2], i); } } void GL_GenerateNormals(float *orgs, float *normals, int *indicies, int numtris, int numverts) { vec3_t d1, d2; vec3_t norm; int t, i, v1, v2, v3; int tricounts[MD2MAX_VERTS]; vec3_t combined[MD2MAX_VERTS]; int triremap[MD2MAX_VERTS]; if (numverts > MD2MAX_VERTS) return; //not an issue, you just loose the normals. memset(triremap, 0, numverts*sizeof(triremap[0])); v2=0; for (i = 0; i < numverts; i++) //weld points { for (v1 = 0; v1 < v2; v1++) { if (orgs[i*3+0] == combined[v1][0] && orgs[i*3+1] == combined[v1][1] && orgs[i*3+2] == combined[v1][2]) { triremap[i] = v1; break; } } if (v1 == v2) { combined[v1][0] = orgs[i*3+0]; combined[v1][1] = orgs[i*3+1]; combined[v1][2] = orgs[i*3+2]; v2++; triremap[i] = v1; } } memset(tricounts, 0, v2*sizeof(tricounts[0])); memset(combined, 0, v2*sizeof(*combined)); for (t = 0; t < numtris; t++) { v1 = triremap[indicies[t*3]]; v2 = triremap[indicies[t*3+1]]; v3 = triremap[indicies[t*3+2]]; VectorSubtract((orgs+v2*3), (orgs+v1*3), d1); VectorSubtract((orgs+v3*3), (orgs+v1*3), d2); CrossProduct(d1, d2, norm); VectorNormalize(norm); VectorAdd(norm, combined[v1], combined[v1]); VectorAdd(norm, combined[v2], combined[v2]); VectorAdd(norm, combined[v3], combined[v3]); tricounts[v1]++; tricounts[v2]++; tricounts[v3]++; } for (i = 0; i < numverts; i++) { if (tricounts[triremap[i]]) { VectorScale(combined[triremap[i]], 1.0f/tricounts[triremap[i]], normals+i*3); } } } char **skinfilelist; int skinfilecount; static qboolean VARGS TryAddSkin(char *skinname, ...) { va_list argptr; char string[MAX_QPATH]; //make sure we don't add it twice int i; va_start (argptr, skinname); _vsnprintf (string,sizeof(string)-1, skinname,argptr); va_end (argptr); string[MAX_QPATH-1] = '\0'; for (i = 0; i < skinfilecount; i++) { if (!strcmp(skinfilelist[i], string)) return true; //already added } if (!COM_FCheckExists(string)) return false; skinfilelist = BZ_Realloc(skinfilelist, sizeof(*skinfilelist)*(skinfilecount+1)); skinfilelist[skinfilecount] = Z_Malloc(strlen(string)+1); strcpy(skinfilelist[skinfilecount], string); skinfilecount++; return true; } int GL_EnumerateSkins(char *name, int size, void *param) { TryAddSkin(name); return true; } int GL_BuildSkinFileList(char *modelname) { int i; char skinfilename[MAX_QPATH]; //flush the old list for (i = 0; i < skinfilecount; i++) { Z_Free(skinfilelist[i]); skinfilelist[i] = NULL; } skinfilecount=0; COM_StripExtension(modelname, skinfilename); //try and add numbered skins, and then try fixed names. for (i = 0; ; i++) { if (!TryAddSkin("%s_%i.skin", modelname, i)) { if (i == 0) { if (!TryAddSkin("%s_default.skin", skinfilename, i)) break; } else if (i == 1) { if (!TryAddSkin("%s_blue.skin", skinfilename, i)) break; } else if (i == 2) { if (!TryAddSkin("%s_red.skin", skinfilename, i)) break; } else if (i == 3) { if (!TryAddSkin("%s_green.skin", skinfilename, i)) break; } else if (i == 4) { if (!TryAddSkin("%s_yellow.skin", skinfilename, i)) break; } else break; } } COM_EnumerateFiles(va("%s_*.skin", modelname), GL_EnumerateSkins, NULL); COM_EnumerateFiles(va("%s_*.skin", skinfilename), GL_EnumerateSkins, NULL); return skinfilecount; } //This is a hack. It uses an assuption about q3 player models. void GL_ParseQ3SkinFile(char *out, char *surfname, char *modelname, int skinnum, char *skinfilename) { const char *f = NULL, *p; int len; if (skinnum >= skinfilecount) return; if (skinfilename) strcpy(skinfilename, skinfilelist[skinnum]); f = COM_LoadTempFile2(skinfilelist[skinnum]); while(f) { f = COM_ParseToken(f,NULL); if (!f) return; if (!strcmp(com_token, "replace")) { f = COM_ParseToken(f, NULL); len = strlen(com_token); //copy surfname -> out, until we meet the part we need to replace while(*surfname) { if (!strncmp(com_token, surfname, len)) //found it { surfname+=len; f = COM_ParseToken(f, NULL); p = com_token; while(*p) //copy the replacement *out++ = *p++; while(*surfname) //copy the remaining *out++ = *surfname++; *out++ = '\0'; //we didn't find it. return; } *out++ = *surfname++; } *out++ = '\0'; //we didn't find it. return; } else { while(*f == ' ' || *f == '\t') f++; if (*f == ',') { if (!strcmp(com_token, surfname)) { f++; COM_ParseToken(f, NULL); strcpy(out, com_token); return; } } } p = strchr(f, '\n'); if (!p) f = f+strlen(f); else f = p+1; if (!*f) break; } } void GL_LoadSkinFile(galiastexnum_t *texnum, char *surfacename, int skinnumber, unsigned char *rawdata, int width, int height, unsigned char *palette) { char shadername[MAX_QPATH]; Q_strncpyz(shadername, surfacename, sizeof(shadername)); GL_ParseQ3SkinFile(shadername, surfacename, loadmodel->name, skinnumber, NULL); texnum->shader = R_RegisterSkin(shadername); texnum->base = Mod_LoadHiResTexture(shadername, "models", true, true, true); } #endif //SERVERONLY //Q1 model loading #if 1 static galiasinfo_t *galias; static dmdl_t *pq1inmodel; #define NUMVERTEXNORMALS 162 extern float r_avertexnormals[NUMVERTEXNORMALS][3]; static void *Q1_LoadFrameGroup (daliasframetype_t *pframetype, int *seamremaps) { galiaspose_t *pose; galiasgroup_t *frame; dtrivertx_t *pinframe; int i, j, k; daliasgroup_t *ingroup; daliasinterval_t *intervals; vec3_t *normals; vec3_t *verts; frame = (galiasgroup_t*)((char *)galias + galias->groupofs); frame->loop = true; for (i = 0; i < pq1inmodel->numframes; i++) { switch(LittleLong(pframetype->type)) { case ALIAS_SINGLE: pinframe = (dtrivertx_t*)((char *)(pframetype+1)+sizeof(daliasframe_t)); pose = (galiaspose_t *)Hunk_Alloc(sizeof(galiaspose_t) + sizeof(vec3_t)*2*galias->numverts); frame->poseofs = (char *)pose - (char *)frame; frame->numposes = 1; galias->groups++; verts = (vec3_t *)(pose+1); normals = &verts[galias->numverts]; pose->ofsverts = (char *)verts - (char *)pose; #ifndef SERVERONLY pose->ofsnormals = (char *)normals - (char *)pose; #endif for (j = 0; j < pq1inmodel->numverts; j++) { verts[j][0] = pinframe[j].v[0]*pq1inmodel->scale[0]+pq1inmodel->scale_origin[0]; verts[j][1] = pinframe[j].v[1]*pq1inmodel->scale[1]+pq1inmodel->scale_origin[1]; verts[j][2] = pinframe[j].v[2]*pq1inmodel->scale[2]+pq1inmodel->scale_origin[2]; #ifndef SERVERONLY VectorCopy(r_avertexnormals[pinframe[j].lightnormalindex], normals[j]); #endif if (seamremaps[j] != j) { VectorCopy(verts[j], verts[seamremaps[j]]); VectorCopy(normals[j], normals[seamremaps[j]]); } } // GL_GenerateNormals((float*)verts, (float*)normals, (int *)((char *)galias + galias->ofs_indexes), galias->numindexes/3, galias->numverts); pframetype = (daliasframetype_t *)&pinframe[pq1inmodel->numverts]; break; case ALIAS_GROUP: case ALIAS_GROUP_SWAPPED: // prerelease ingroup = (daliasgroup_t *)(pframetype+1); pose = (galiaspose_t *)Hunk_Alloc(LittleLong(ingroup->numframes)*(sizeof(galiaspose_t) + sizeof(vec3_t)*2*galias->numverts)); frame->poseofs = (char *)pose - (char *)frame; frame->numposes = LittleLong(ingroup->numframes); galias->groups++; verts = (vec3_t *)(pose+frame->numposes); normals = &verts[galias->numverts]; intervals = (daliasinterval_t *)(ingroup+1); frame->rate = 1/LittleFloat(intervals->interval); pinframe = (dtrivertx_t *)(intervals+frame->numposes); for (k = 0; k < frame->numposes; k++) { pose->ofsverts = (char *)verts - (char *)pose; #ifndef SERVERONLY pose->ofsnormals = (char *)normals - (char *)pose; #endif pinframe = (dtrivertx_t *)((char *)pinframe + sizeof(daliasframe_t)); for (j = 0; j < pq1inmodel->numverts; j++) { verts[j][0] = pinframe[j].v[0]*pq1inmodel->scale[0]+pq1inmodel->scale_origin[0]; verts[j][1] = pinframe[j].v[1]*pq1inmodel->scale[1]+pq1inmodel->scale_origin[1]; verts[j][2] = pinframe[j].v[2]*pq1inmodel->scale[2]+pq1inmodel->scale_origin[2]; #ifndef SERVERONLY VectorCopy(r_avertexnormals[pinframe[j].lightnormalindex], normals[j]); #endif if (seamremaps[j] != j) { VectorCopy(verts[j], verts[seamremaps[j]]); VectorCopy(normals[j], normals[seamremaps[j]]); } } verts = &normals[galias->numverts]; normals = &verts[galias->numverts]; pose++; pinframe += pq1inmodel->numverts; } // GL_GenerateNormals((float*)verts, (float*)normals, (int *)((char *)galias + galias->ofs_indexes), galias->numindexes/3, galias->numverts); pframetype = (daliasframetype_t *)pinframe; break; default: Sys_Error("Bad frame type\n"); } frame++; } return pframetype; } #ifdef SERVERONLY //greatly reduced version of Q1_LoadSkins (too many #ifdefs static void *Q1_LoadSkins (daliasskintype_t *pskintype, qboolean alpha) { int i; int s; int *count; float *intervals; qbyte *data; s = pq1inmodel->skinwidth*pq1inmodel->skinheight; for (i = 0; i < pq1inmodel->numskins; i++) { switch(LittleLong(pskintype->type)) { case ALIAS_SKIN_SINGLE: pskintype = (daliasskintype_t *)((char *)(pskintype+1)+s); break; default: count = (int *)(pskintype+1); intervals = (float *)(count+1); data = (qbyte *)(intervals + LittleLong(*count)); data += s*LittleLong(*count); pskintype = (daliasskintype_t *)data; break; } } galias->numskins=pq1inmodel->numskins; return pskintype; } #else static void *Q1_LoadSkins (daliasskintype_t *pskintype, qboolean alpha) { extern cvar_t gl_bump; extern int gl_bumpmappingpossible; galiastexnum_t *texnums; char skinname[MAX_QPATH]; int i; int s, t; int *count; float *intervals; qbyte *data, *saved; galiasskin_t *outskin = (galiasskin_t *)((char *)galias + galias->ofsskins); int texture; int fbtexture; int bumptexture; s = pq1inmodel->skinwidth*pq1inmodel->skinheight; for (i = 0; i < pq1inmodel->numskins; i++) { switch(LittleLong(pskintype->type)) { case ALIAS_SKIN_SINGLE: outskin->skinwidth = pq1inmodel->skinwidth; outskin->skinheight = pq1inmodel->skinheight; //LH's naming scheme ("models" is likly to be ignored) fbtexture = 0; bumptexture = 0; _snprintf(skinname, sizeof(skinname), "%s_%i.", loadmodel->name, i); texture = Mod_LoadReplacementTexture(skinname, "models", true, false, true); if (texture) { _snprintf(skinname, sizeof(skinname), "%s_%i_luma.", loadmodel->name, i); fbtexture = Mod_LoadReplacementTexture(skinname, "models", true, false, true); if (gl_bump.value) { sprintf(skinname, "%s_%i_bump", loadmodel->name, i); bumptexture = Mod_LoadBumpmapTexture(skinname, "models"); } } else { sprintf(skinname, "%s_%i", loadname, i); texture = Mod_LoadReplacementTexture(skinname, "models", true, false, true); if (texture && r_fb_models.value) { sprintf(skinname, "%s_%i_luma", loadname, i); fbtexture = Mod_LoadReplacementTexture(skinname, "models", true, true, true); } if (texture && gl_bump.value) { sprintf(skinname, "%s_%i_bump", loadname, i); bumptexture = Mod_LoadBumpmapTexture(skinname, "models"); } } //but only preload it if we have no replacement. if (!texture) { //we're not using 24bits texnums = Hunk_Alloc(sizeof(*texnums)+s); saved = (qbyte*)(texnums+1); outskin->ofstexels = (qbyte *)(saved) - (qbyte *)outskin; memcpy(saved, pskintype+1, s); GLMod_FloodFillSkin(saved, outskin->skinwidth, outskin->skinheight); //the extra underscore is to stop sprintf(skinname, "%s__%i", loadname, i); texture = GL_LoadTexture(skinname, outskin->skinwidth, outskin->skinheight, saved, true, alpha); if (r_fb_models.value) { sprintf(skinname, "%s__%i_luma", loadname, i); fbtexture = GL_LoadTextureFB(skinname, outskin->skinwidth, outskin->skinheight, saved, true, true); } if (gl_bump.value) { sprintf(skinname, "%s__%i_bump", loadname, i); bumptexture = GL_LoadTexture8Bump(skinname, outskin->skinwidth, outskin->skinheight, saved, true, true); } } else texnums = Hunk_Alloc(sizeof(*texnums)); outskin->texnums=1; outskin->ofstexnums = (char *)texnums - (char *)outskin; /* #ifdef Q3SHADERS sprintf(skinname, "%s_%i", loadname, i); texnums->shader = R_RegisterSkin(skinname); #endif */ texnums->base = texture; texnums->fullbright = fbtexture; texnums->bump = bumptexture; pskintype = (daliasskintype_t *)((char *)(pskintype+1)+s); break; default: outskin->skinwidth = pq1inmodel->skinwidth; outskin->skinheight = pq1inmodel->skinheight; count = (int *)(pskintype+1); intervals = (float *)(count+1); outskin->texnums = LittleLong(*count); data = (qbyte *)(intervals + outskin->texnums); texnums = Hunk_Alloc(sizeof(*texnums)*outskin->texnums); outskin->ofstexnums = (char *)texnums - (char *)outskin; outskin->ofstexels = 0; for (t = 0; t < outskin->texnums; t++,data+=s, texnums++) { texture = 0; fbtexture = 0; //LH naming scheme if (!texture) { sprintf(skinname, "%s_%i_%i.", loadmodel->name, i, t); texture = Mod_LoadReplacementTexture(skinname, "models", true, false, true); } if (!fbtexture && r_fb_models.value) { sprintf(skinname, "%s_%i_%i_luma.", loadmodel->name, i, t); fbtexture = Mod_LoadReplacementTexture(skinname, "models", true, true, true); } //Fuhquake naming scheme if (!texture) { sprintf(skinname, "%s_%i_%i", loadname, i, t); texture = Mod_LoadReplacementTexture(skinname, "models", true, false, true); } if (!fbtexture && r_fb_models.value) { sprintf(skinname, "%s_%i_%i_luma", loadname, i, t); fbtexture = Mod_LoadReplacementTexture(skinname, "models", true, true, true); } if (!texture || (!fbtexture && r_fb_models.value)) { if (t == 0) { saved = Hunk_Alloc(s); outskin->ofstexels = (qbyte *)(saved) - (qbyte *)outskin; } else saved = BZ_Malloc(s); memcpy(saved, pskintype+1, s); GLMod_FloodFillSkin(saved, outskin->skinwidth, outskin->skinheight); if (!texture) { sprintf(skinname, "%s_%i_%i", loadname, i, t); texture = GL_LoadTexture(skinname, outskin->skinwidth, outskin->skinheight, saved, true, alpha); } if (!fbtexture && r_fb_models.value) { sprintf(skinname, "%s_%i_%i_luma", loadname, i, t); fbtexture = GL_LoadTextureFB(skinname, outskin->skinwidth, outskin->skinheight, saved, true, true); } if (t != 0) //only keep the first. BZ_Free(saved); } /* #ifdef Q3SHADERS sprintf(skinname, "%s_%i_%i", loadname, i, t); texnums->shader = R_RegisterSkin(skinname); #endif */ texnums->base = texture; texnums->fullbright = fbtexture; } pskintype = (daliasskintype_t *)data; break; } outskin++; } galias->numskins=pq1inmodel->numskins; return pskintype; } #endif void GL_LoadQ1Model (model_t *mod, void *buffer) { #ifndef SERVERONLY vec2_t *st_array; int j; #endif int hunkstart, hunkend, hunktotal; int version; int i, onseams; dstvert_t *pinstverts; dtriangle_t *pintriangles; int *seamremap; index_t *indexes; int size; loadmodel=mod; Mod_DoCRC(loadmodel, buffer, com_filesize); hunkstart = Hunk_LowMark (); pq1inmodel = (dmdl_t *)buffer; seamremap = (int*)pq1inmodel; //I like overloading locals. for (i = 0; i < sizeof(dmdl_t)/4; i++) seamremap[i] = LittleLong(seamremap[i]); version = pq1inmodel->version; if (version != ALIAS_VERSION) Sys_Error ("%s has wrong version number (%i should be %i)", mod->name, version, ALIAS_VERSION); if (pq1inmodel->numframes < 1 || pq1inmodel->numskins < 1 || pq1inmodel->numtris < 1 || pq1inmodel->numverts < 3 || pq1inmodel->skinheight < 1 || pq1inmodel->skinwidth < 1) Sys_Error("Model %s has an invalid quantity\n", mod->name); mod->flags = pq1inmodel->flags; size = sizeof(galiasinfo_t) #ifndef SERVERONLY + pq1inmodel->numskins*sizeof(galiasskin_t) #endif + pq1inmodel->numframes*sizeof(galiasgroup_t); galias = Hunk_Alloc(size); galias->groupofs = sizeof(*galias); #ifndef SERVERONLY galias->ofsskins = sizeof(*galias)+pq1inmodel->numframes*sizeof(galiasgroup_t); #endif galias->nextsurf = 0; //skins if( mod->flags & EF_HOLEY ) pinstverts = (dstvert_t *)Q1_LoadSkins((daliasskintype_t *)(pq1inmodel+1), 3); else if( mod->flags & EF_TRANSPARENT ) pinstverts = (dstvert_t *)Q1_LoadSkins((daliasskintype_t *)(pq1inmodel+1), 2); else if( mod->flags & EF_SPECIAL_TRANS ) pinstverts = (dstvert_t *)Q1_LoadSkins((daliasskintype_t *)(pq1inmodel+1), 4); else pinstverts = (dstvert_t *)Q1_LoadSkins((daliasskintype_t *)(pq1inmodel+1), 0); // pinstverts = (dstvert_t *)Q1_LoadSkins((daliasskintype_t *)(pq1inmodel+1)); //count number of verts that are onseam. for (onseams=0,i = 0; i < pq1inmodel->numverts; i++) { if (pinstverts[i].onseam) onseams++; } seamremap = BZ_Malloc(sizeof(int)*pq1inmodel->numverts); galias->numverts = pq1inmodel->numverts+onseams; //st #ifndef SERVERONLY st_array = Hunk_Alloc(sizeof(*st_array)*(pq1inmodel->numverts+onseams)); galias->ofs_st_array = (char *)st_array - (char *)galias; for (j=pq1inmodel->numverts,i = 0; i < pq1inmodel->numverts; i++) { st_array[i][0] = LittleLong(pinstverts[i].s)/(float)pq1inmodel->skinwidth; st_array[i][1] = LittleLong(pinstverts[i].t)/(float)pq1inmodel->skinheight; if (pinstverts[i].onseam) { st_array[j][0] = st_array[i][0]+0.5; st_array[j][1] = st_array[i][1]; seamremap[i] = j; j++; } else seamremap[i] = i; } #endif //trianglelists; pintriangles = (dtriangle_t *)&pinstverts[pq1inmodel->numverts]; galias->numindexes = pq1inmodel->numtris*3; indexes = Hunk_Alloc(galias->numindexes*sizeof(*indexes)); galias->ofs_indexes = (char *)indexes - (char *)galias; for (i=0 ; inumtris ; i++) { if (!pintriangles[i].facesfront) { indexes[i*3+0] = seamremap[LittleLong(pintriangles[i].vertindex[0])]; indexes[i*3+1] = seamremap[LittleLong(pintriangles[i].vertindex[1])]; indexes[i*3+2] = seamremap[LittleLong(pintriangles[i].vertindex[2])]; } else { indexes[i*3+0] = LittleLong(pintriangles[i].vertindex[0]); indexes[i*3+1] = LittleLong(pintriangles[i].vertindex[1]); indexes[i*3+2] = LittleLong(pintriangles[i].vertindex[2]); } } //frames Q1_LoadFrameGroup((daliasframetype_t *)&pintriangles[pq1inmodel->numtris], seamremap); BZ_Free(seamremap); #ifndef SERVERONLY if (r_shadows.value) { int *neighbours; neighbours = Hunk_Alloc(sizeof(int)*3*pq1inmodel->numtris); galias->ofs_trineighbours = (qbyte *)neighbours - (qbyte *)galias; R_BuildTriangleNeighbours(neighbours, indexes, pq1inmodel->numtris); } #endif VectorCopy (pq1inmodel->scale_origin, mod->mins); VectorMA (mod->mins, 255, pq1inmodel->scale, mod->maxs); // // move the complete, relocatable alias model to the cache // hunkend = Hunk_LowMark (); Hunk_Alloc(0); hunktotal = hunkend - hunkstart; Cache_Alloc (&mod->cache, hunktotal, loadname); mod->type = mod_alias; if (!mod->cache.data) { Hunk_FreeToLowMark (hunkstart); return; } memcpy (mod->cache.data, galias, hunktotal); Hunk_FreeToLowMark (hunkstart); mod->funcs.Trace = GLMod_Trace; } #endif int Mod_ReadFlagsFromMD1(char *name, int md3version) { dmdl_t *pinmodel; char fname[MAX_QPATH]; COM_StripExtension(name, fname); COM_DefaultExtension(fname, ".mdl"); if (strcmp(name, fname)) //md3 renamed as mdl { COM_StripExtension(name, fname); //seeing as the md3 is named over the mdl, COM_DefaultExtension(fname, ".md1");//read from a file with md1 (one, not an ell) return 0; } pinmodel = (dmdl_t *)COM_LoadTempFile(fname); if (!pinmodel) //not found return 0; if (LittleLong(pinmodel->ident) != IDPOLYHEADER) return 0; if (LittleLong(pinmodel->version) != ALIAS_VERSION) return 0; return LittleLong(pinmodel->flags); } #ifdef MD2MODELS //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Q2 model loading static galiasinfo_t *galias; static model_t *loadmodel; static md2_t *pq2inmodel; #define Q2NUMVERTEXNORMALS 162 extern vec3_t bytedirs[Q2NUMVERTEXNORMALS]; static void Q2_LoadSkins(char *skins) { #ifndef SERVERONLY int i; galiastexnum_t *texnums; galiasskin_t *outskin = (galiasskin_t *)((char *)galias + galias->ofsskins); for (i = 0; i < LittleLong(pq2inmodel->num_skins); i++, outskin++) { texnums = Hunk_Alloc(sizeof(*texnums)); outskin->ofstexnums = (char *)texnums - (char *)outskin; outskin->texnums=1; COM_CleanUpPath(skins); //blooming tanks. texnums->base = Mod_LoadReplacementTexture(skins, "models", true, false, true); outskin->skinwidth = 0; outskin->skinheight = 0; outskin->skinspeed = 0; skins += MD2MAX_SKINNAME; } #endif galias->numskins = LittleLong(pq2inmodel->num_skins); } #define MD2_MAX_TRIANGLES 4096 void GL_LoadQ2Model (model_t *mod, void *buffer) { #ifndef SERVERONLY dmd2stvert_t *pinstverts; vec2_t *st_array; vec3_t *normals; #endif int hunkstart, hunkend, hunktotal; int version; int i, j; dmd2triangle_t *pintri; index_t *indexes; int numindexes; vec3_t min; vec3_t max; galiaspose_t *pose; galiasgroup_t *poutframe; dmd2aliasframe_t *pinframe; int framesize; vec3_t *verts; int indremap[MD2_MAX_TRIANGLES*3]; unsigned short ptempindex[MD2_MAX_TRIANGLES*3], ptempstindex[MD2_MAX_TRIANGLES*3]; int numverts; int size; loadmodel=mod; Mod_DoCRC(mod, buffer, com_filesize); hunkstart = Hunk_LowMark (); pq2inmodel = (md2_t *)buffer; version = LittleLong (pq2inmodel->version); if (version != MD2ALIAS_VERSION) Sys_Error ("%s has wrong version number (%i should be %i)", mod->name, version, MD2ALIAS_VERSION); if (LittleLong(pq2inmodel->num_frames) < 1 || LittleLong(pq2inmodel->num_skins) < 0 || LittleLong(pq2inmodel->num_tris) < 1 || LittleLong(pq2inmodel->num_xyz) < 3 || LittleLong(pq2inmodel->num_st) < 3 || LittleLong(pq2inmodel->skinheight) < 1 || LittleLong(pq2inmodel->skinwidth) < 1) Sys_Error("Model %s has an invalid quantity\n", mod->name); mod->flags = 0; loadmodel->numframes = LittleLong(pq2inmodel->num_frames); size = sizeof(galiasinfo_t) #ifndef SERVERONLY + LittleLong(pq2inmodel->num_skins)*sizeof(galiasskin_t) #endif + LittleLong(pq2inmodel->num_frames)*sizeof(galiasgroup_t); galias = Hunk_Alloc(size); galias->groupofs = sizeof(*galias); #ifndef SERVERONLY galias->ofsskins = sizeof(*galias)+LittleLong(pq2inmodel->num_frames)*sizeof(galiasgroup_t); #endif galias->nextsurf = 0; //skins Q2_LoadSkins(((char *)pq2inmodel+LittleLong(pq2inmodel->ofs_skins))); //trianglelists; pintri = (dmd2triangle_t *)((char *)pq2inmodel + LittleLong(pq2inmodel->ofs_tris)); for (i=0 ; inum_tris) ; i++, pintri++) { for (j=0 ; j<3 ; j++) { ptempindex[i*3+j] = ( unsigned short )LittleShort ( pintri->xyz_index[j] ); ptempstindex[i*3+j] = ( unsigned short )LittleShort ( pintri->st_index[j] ); } } numindexes = galias->numindexes = LittleLong(pq2inmodel->num_tris)*3; indexes = Hunk_Alloc(galias->numindexes*sizeof(*indexes)); galias->ofs_indexes = (char *)indexes - (char *)galias; memset ( indremap, -1, sizeof(indremap) ); numverts=0; for ( i = 0; i < numindexes; i++ ) { if ( indremap[i] != -1 ) { continue; } for ( j = 0; j < numindexes; j++ ) { if ( j == i ) { continue; } if ( (ptempindex[i] == ptempindex[j]) && (ptempstindex[i] == ptempstindex[j]) ) { indremap[j] = i; } } } // count unique vertexes for ( i = 0; i < numindexes; i++ ) { if ( indremap[i] != -1 ) { continue; } indexes[i] = numverts++; indremap[i] = i; } Con_DPrintf ( "%s: remapped %i verts to %i\n", mod->name, LittleLong(pq2inmodel->num_xyz), numverts ); galias->numverts = numverts; // remap remaining indexes for ( i = 0; i < numindexes; i++ ) { if ( indremap[i] != i ) { indexes[i] = indexes[indremap[i]]; } } // s and t vertices #ifndef SERVERONLY pinstverts = ( dmd2stvert_t * ) ( ( qbyte * )pq2inmodel + LittleLong (pq2inmodel->ofs_st) ); st_array = Hunk_Alloc(sizeof(*st_array)*(numverts)); galias->ofs_st_array = (char *)st_array - (char *)galias; for (j=0 ; jskinwidth)); st_array[indexes[j]][1] = (float)(((double)LittleShort (pinstverts[ptempstindex[indremap[j]]].t) + 0.5f) /LittleLong(pq2inmodel->skinheight)); } #endif //frames ClearBounds ( mod->mins, mod->maxs ); poutframe = (galiasgroup_t*)((char *)galias + galias->groupofs); framesize = LittleLong (pq2inmodel->framesize); for (i=0 ; inum_frames) ; i++) { pose = (galiaspose_t *)Hunk_Alloc(sizeof(galiaspose_t) + sizeof(vec3_t)*numverts #ifndef SERVERONLY + sizeof(vec3_t)*numverts #endif ); poutframe->poseofs = (char *)pose - (char *)poutframe; poutframe->numposes = 1; galias->groups++; verts = (vec3_t *)(pose+1); pose->ofsverts = (char *)verts - (char *)pose; #ifndef SERVERONLY normals = &verts[galias->numverts]; pose->ofsnormals = (char *)normals - (char *)pose; #endif pinframe = ( dmd2aliasframe_t * )( ( qbyte * )pq2inmodel + LittleLong (pq2inmodel->ofs_frames) + i * framesize ); for (j=0 ; j<3 ; j++) { pose->scale[j] = LittleFloat (pinframe->scale[j]); pose->scale_origin[j] = LittleFloat (pinframe->translate[j]); } for (j=0 ; jscale_origin[0]+pose->scale[0]*pinframe->verts[ptempindex[indremap[j]]].v[0]; verts[indexes[j]][1] = pose->scale_origin[1]+pose->scale[1]*pinframe->verts[ptempindex[indremap[j]]].v[1]; verts[indexes[j]][2] = pose->scale_origin[2]+pose->scale[2]*pinframe->verts[ptempindex[indremap[j]]].v[2]; #ifndef SERVERONLY VectorCopy(bytedirs[pinframe->verts[ptempindex[indremap[j]]].lightnormalindex], normals[indexes[j]]); #endif } // Mod_AliasCalculateVertexNormals ( numindexes, poutindex, numverts, poutvertex, qfalse ); VectorCopy ( pose->scale_origin, min ); VectorMA ( pose->scale_origin, 255, pose->scale, max ); // poutframe->radius = RadiusFromBounds ( min, max ); // mod->radius = max ( mod->radius, poutframe->radius ); AddPointToBounds ( min, mod->mins, mod->maxs ); AddPointToBounds ( max, mod->mins, mod->maxs ); // GL_GenerateNormals((float*)verts, (float*)normals, indexes, numindexes/3, numverts); poutframe++; } #ifndef SERVERONLY if (r_shadows.value) { int *neighbours; neighbours = Hunk_Alloc(sizeof(int)*3*LittleLong(pq2inmodel->num_tris)); galias->ofs_trineighbours = (qbyte *)neighbours - (qbyte *)galias; R_BuildTriangleNeighbours(neighbours, indexes, LittleLong(pq2inmodel->num_tris)); } #endif /* VectorCopy (pq2inmodel->scale_origin, mod->mins); VectorMA (mod->mins, 255, pq2inmodel->scale, mod->maxs); */ // // move the complete, relocatable alias model to the cache // hunkend = Hunk_LowMark (); Hunk_Alloc(0); hunktotal = hunkend - hunkstart; Cache_Alloc (&mod->cache, hunktotal, loadname); mod->type = mod_alias; if (!mod->cache.data) { Hunk_FreeToLowMark (hunkstart); return; } memcpy (mod->cache.data, galias, hunktotal); Hunk_FreeToLowMark (hunkstart); mod->funcs.Trace = GLMod_Trace; } #endif typedef struct { char name[MAX_QPATH]; vec3_t org; float ang[3][3]; } md3tag_t; #ifndef SERVERONLY qboolean GLMod_GetTag(model_t *model, int tagnum, int frame1, int frame2, float f2ness, float f1time, float f2time, float *result) #else qboolean Mod_GetTag(model_t *model, int tagnum, int frame1, int frame2, float f2ness, float f1time, float f2time, float *result) #endif { galiasinfo_t *inf; if (!model || model->type != mod_alias) return false; inf = Mod_Extradata(model); #ifdef SKELETALMODELS if (inf->numbones) { galiasbone_t *bone; galiasgroup_t *g1, *g2; float tempmatrix[12]; //flipped between this and bonematrix float *matrix; //the matrix for a single bone in a single pose. float m[12]; //combined interpolated version of 'matrix'. int b, k; //counters float *pose[4]; //the per-bone matricies (one for each pose) float plerp[4]; //the ammount of that pose to use (must combine to 1) int numposes = 0; if (tagnum <= 0 || tagnum > inf->numbones) return false; tagnum--; //tagnum 0 is 'use my angles/org' if (frame1 < 0 || frame1 >= inf->groups) return false; if (frame2 < 0 || frame2 >= inf->groups) { f2ness = 0; frame2 = frame1; } bone = (galiasbone_t*)((char*)inf + inf->ofsbones); //the higher level merges old/new anims, but we still need to blend between automated frame-groups. g1 = (galiasgroup_t*)((char *)inf + inf->groupofs + sizeof(galiasgroup_t)*frame1); g2 = (galiasgroup_t*)((char *)inf + inf->groupofs + sizeof(galiasgroup_t)*frame2); f1time *= g1->rate; frame1 = (int)f1time%g1->numposes; frame2 = ((int)f1time+1)%g1->numposes; f1time = f1time - (int)f1time; pose[numposes] = (float *)((char *)g1 + g1->poseofs + sizeof(float)*inf->numbones*12*frame1); plerp[numposes] = (1-f1time) * (1-f2ness); numposes++; if (frame1 != frame2) { pose[numposes] = (float *)((char *)g1 + g1->poseofs + sizeof(float)*inf->numbones*12*frame2); plerp[numposes] = f1time * (1-f2ness); numposes++; } if (f2ness) { f2time *= g2->rate; frame1 = (int)f2time%g2->numposes; frame2 = ((int)f2time+1)%g2->numposes; f2time = f2time - (int)f2time; pose[numposes] = (float *)((char *)g2 + g2->poseofs + sizeof(float)*inf->numbones*12*frame1); plerp[numposes] = (1-f2time) * f2ness; numposes++; if (frame1 != frame2) { pose[numposes] = (float *)((char *)g2 + g2->poseofs + sizeof(float)*inf->numbones*12*frame2); plerp[numposes] = f2time * f2ness; numposes++; } } //set up the identity matrix for (k = 0;k < 12;k++) result[k] = 0; result[0] = 1; result[5] = 1; result[10] = 1; while(tagnum >= 0) { //set up the per-bone transform matrix for (k = 0;k < 12;k++) m[k] = 0; for (b = 0;b < numposes;b++) { matrix = pose[b] + tagnum*12; for (k = 0;k < 12;k++) m[k] += matrix[k] * plerp[b]; } memcpy(tempmatrix, result, sizeof(tempmatrix)); R_ConcatTransforms((void*)m, (void*)tempmatrix, (void*)result); tagnum = bone[tagnum].parent; } return true; } #endif if (inf->numtags) { md3tag_t *t1, *t2; if (tagnum <= 0 || tagnum > inf->numtags) return false; if (frame1 < 0) return false; if (frame1 >= inf->numtagframes) frame1 = inf->numtagframes - 1; if (frame2 < 0 || frame2 >= inf->numtagframes) frame2 = frame1; tagnum--; //tagnum 0 is 'use my angles/org' t1 = (md3tag_t*)((char*)inf + inf->ofstags); t1 += tagnum; t1 += inf->numtags*frame1; t2 = (md3tag_t*)((char*)inf + inf->ofstags); t2 += tagnum; t2 += inf->numtags*frame2; if (t1 == t2) { result[0] = t1->ang[0][0]; result[1] = t1->ang[0][1]; result[2] = t1->ang[0][2]; result[3] = t1->org[0]; result[4] = t1->ang[1][0]; result[5] = t1->ang[1][1]; result[6] = t1->ang[1][2]; result[7] = t1->org[1]; result[8] = t1->ang[2][0]; result[9] = t1->ang[2][1]; result[10] = t1->ang[2][2]; result[11] = t1->org[2]; } else { float f1ness = 1-f2ness; result[0] = t1->ang[0][0]*f1ness + t2->ang[0][0]*f2ness; result[1] = t1->ang[0][1]*f1ness + t2->ang[0][1]*f2ness; result[2] = t1->ang[0][2]*f1ness + t2->ang[0][2]*f2ness; result[3] = t1->org[0]*f1ness + t2->org[0]*f2ness; result[4] = t1->ang[1][0]*f1ness + t2->ang[1][0]*f2ness; result[5] = t1->ang[1][1]*f1ness + t2->ang[1][1]*f2ness; result[6] = t1->ang[1][2]*f1ness + t2->ang[1][2]*f2ness; result[7] = t1->org[1]*f1ness + t2->org[1]*f2ness; result[8] = t1->ang[2][0]*f1ness + t2->ang[2][0]*f2ness; result[9] = t1->ang[2][1]*f1ness + t2->ang[2][1]*f2ness; result[10] = t1->ang[2][2]*f1ness + t2->ang[2][2]*f2ness; result[11] = t1->org[2]*f1ness + t2->org[2]*f2ness; } return true; } return false; } #ifndef SERVERONLY int GLMod_TagNumForName(model_t *model, char *name) #else int Mod_TagNumForName(model_t *model, char *name) #endif { int i; galiasinfo_t *inf; md3tag_t *t; if (!model || model->type != mod_alias) return 0; inf = Mod_Extradata(model); #ifdef SKELETALMODELS if (inf->numbones) { galiasbone_t *b; b = (galiasbone_t*)((char*)inf + inf->ofsbones); for (i = 0; i < inf->numbones; i++) { if (!strcmp(b[i].name, name)) return i+1; } } #endif t = (md3tag_t*)((char*)inf + inf->ofstags); for (i = 0; i < inf->numtags; i++) { if (!strcmp(t[i].name, name)) return i+1; } return 0; } #ifndef SERVERONLY int GLMod_SkinNumForName(model_t *model, char *name) { int i; galiasinfo_t *inf; galiasskin_t *skin; if (!model || model->type != mod_alias) return -1; inf = Mod_Extradata(model); skin = (galiasskin_t*)((char*)inf+inf->ofsskins); for (i = 0; i < inf->numskins; i++, skin++) { if (!strcmp(skin->name, name)) return i; } return -1; } #endif #ifdef MD3MODELS //structures from Tenebrae typedef struct { int ident; int version; char name[MAX_QPATH]; int flags; //Does anyone know what these are? int numFrames; int numTags; int numSurfaces; int numSkins; int ofsFrames; int ofsTags; int ofsSurfaces; int ofsEnd; } md3Header_t; //then has header->numFrames of these at header->ofs_Frames typedef struct md3Frame_s { vec3_t bounds[2]; vec3_t localOrigin; float radius; char name[16]; } md3Frame_t; //there are header->numSurfaces of these at header->ofsSurfaces, following from ofsEnd typedef struct { int ident; // char name[MAX_QPATH]; // polyset name int flags; int numFrames; // all surfaces in a model should have the same int numShaders; // all surfaces in a model should have the same int numVerts; int numTriangles; int ofsTriangles; int ofsShaders; // offset from start of md3Surface_t int ofsSt; // texture coords are common for all frames int ofsXyzNormals; // numVerts * numFrames int ofsEnd; // next surface follows } md3Surface_t; //at surf+surf->ofsXyzNormals typedef struct { short xyz[3]; qbyte latlong[2]; } md3XyzNormal_t; //surf->numTriangles at surf+surf->ofsTriangles typedef struct { int indexes[3]; } md3Triangle_t; //surf->numVerts at surf+surf->ofsSt typedef struct { float s; float t; } md3St_t; typedef struct { char name[MAX_QPATH]; int shaderIndex; } md3Shader_t; //End of Tenebrae 'assistance' void GL_LoadQ3Model(model_t *mod, void *buffer) { #ifndef SERVERONLY galiasskin_t *skin; galiastexnum_t *texnum; float lat, lng; md3St_t *inst; vec3_t *normals; vec2_t *st_array; md3Shader_t *inshader; #endif int hunkstart, hunkend, hunktotal; // int version; int s, i, j, d; index_t *indexes; vec3_t min; vec3_t max; galiaspose_t *pose; galiasinfo_t *parent, *root; galiasgroup_t *group; vec3_t *verts; md3Triangle_t *intris; md3XyzNormal_t *invert; int size; int externalskins; md3Header_t *header; md3Surface_t *surf; loadmodel=mod; Mod_DoCRC(mod, buffer, com_filesize); hunkstart = Hunk_LowMark (); header = buffer; // if (header->version != sdfs) // Sys_Error("GL_LoadQ3Model: Bad version\n"); parent = NULL; root = NULL; #ifndef SERVERONLY externalskins = GL_BuildSkinFileList(mod->name); #else externalskins = 0; #endif min[0] = min[1] = min[2] = 0; max[0] = max[1] = max[2] = 0; surf = (md3Surface_t *)((qbyte *)header + LittleLong(header->ofsSurfaces)); for (s = 0; s < LittleLong(header->numSurfaces); s++) { if (LittleLong(surf->ident) != MD3_IDENT) Con_Printf("Warning: md3 sub-surface doesn't match ident\n"); size = sizeof(galiasinfo_t) + sizeof(galiasgroup_t)*LittleLong(header->numFrames); galias = Hunk_Alloc(size); galias->groupofs = sizeof(*galias); //frame groups galias->groups = LittleLong(header->numFrames); galias->numverts = LittleLong(surf->numVerts); galias->numindexes = LittleLong(surf->numTriangles)*3; if (parent) parent->nextsurf = (qbyte *)galias - (qbyte *)parent; else root = galias; parent = galias; #ifndef SERVERONLY st_array = Hunk_Alloc(sizeof(vec2_t)*galias->numindexes); galias->ofs_st_array = (qbyte*)st_array - (qbyte*)galias; inst = (md3St_t*)((qbyte*)surf + LittleLong(surf->ofsSt)); for (i = 0; i < galias->numverts; i++) { st_array[i][0] = LittleFloat(inst[i].s); st_array[i][1] = LittleFloat(inst[i].t); } #endif indexes = Hunk_Alloc(sizeof(*indexes)*galias->numindexes); galias->ofs_indexes = (qbyte*)indexes - (qbyte*)galias; intris = (md3Triangle_t *)((qbyte*)surf + LittleLong(surf->ofsTriangles)); for (i = 0; i < LittleLong(surf->numTriangles); i++) { indexes[i*3+0] = LittleLong(intris[i].indexes[0]); indexes[i*3+1] = LittleLong(intris[i].indexes[1]); indexes[i*3+2] = LittleLong(intris[i].indexes[2]); } group = (galiasgroup_t *)(galias+1); invert = (md3XyzNormal_t *)((qbyte*)surf + LittleLong(surf->ofsXyzNormals)); for (i = 0; i < LittleLong(surf->numFrames); i++) { pose = (galiaspose_t *)Hunk_Alloc(sizeof(galiaspose_t) + sizeof(vec3_t)*LittleLong(surf->numVerts) #ifndef SERVERONLY + sizeof(vec3_t)*LittleLong(surf->numVerts) #endif ); verts = (vec3_t*)(pose+1); pose->ofsverts = (qbyte*)verts - (qbyte*)pose; #ifndef SERVERONLY normals = verts + LittleLong(surf->numVerts); pose->ofsnormals = (qbyte*)normals - (qbyte*)pose; #endif for (j = 0; j < LittleLong(surf->numVerts); j++) { #ifndef SERVERONLY lat = (float)invert[j].latlong[0] * (2 * M_PI)*(1.0 / 255.0); lng = (float)invert[j].latlong[1] * (2 * M_PI)*(1.0 / 255.0); normals[j][0] = cos ( lng ) * sin ( lat ); normals[j][1] = sin ( lng ) * sin ( lat ); normals[j][2] = cos ( lat ); #endif for (d = 0; d < 3; d++) { verts[j][d] = LittleShort(invert[j].xyz[d])/64.0f; if (verts[j][d]max[d]) max[d] = verts[j][d]; } } pose->scale[0] = 1; pose->scale[1] = 1; pose->scale[2] = 1; pose->scale_origin[0] = 0; pose->scale_origin[1] = 0; pose->scale_origin[2] = 0; group->numposes = 1; group->rate = 1; group->poseofs = (qbyte*)pose - (qbyte*)group; group++; invert += LittleLong(surf->numVerts); } #ifndef SERVERONLY if (externalskinsnumShaders)) externalskins = LittleLong(surf->numShaders); if (externalskins) { #ifndef Q3SHADERS char name[1024]; #endif extern int gl_bumpmappingpossible; char shadname[1024]; skin = Hunk_Alloc((LittleLong(surf->numShaders)+externalskins)*((sizeof(galiasskin_t)+sizeof(galiastexnum_t)))); galias->ofsskins = (qbyte *)skin - (qbyte *)galias; texnum = (galiastexnum_t *)(skin + LittleLong(surf->numShaders)+externalskins); inshader = (md3Shader_t *)((qbyte *)surf + LittleLong(surf->ofsShaders)); for (i = 0; i < externalskins; i++) { skin->texnums = 1; skin->ofstexnums = (qbyte *)texnum - (qbyte *)skin; skin->ofstexels = 0; skin->skinwidth = 0; skin->skinheight = 0; skin->skinspeed = 0; shadname[0] = '\0'; GL_ParseQ3SkinFile(shadname, surf->name, loadmodel->name, i, skin->name); if (!*shadname) { if (i >= LittleLong(surf->numShaders)) strcpy(shadname, "missingskin"); //this shouldn't be possible else strcpy(shadname, inshader->name); Q_strncpyz(skin->name, shadname, sizeof(skin->name)); } #ifdef Q3SHADERS if (qrenderer) { texnum->shader = R_RegisterSkin(shadname); if (r_shadows.value) //real-time shadows requires a texture to lighten the model with, even if it has a shader. //fixme: this should be read from the shader. texnum->base = Mod_LoadHiResTexture(shadname, "models", true, true, true); } #else texnum->base = Mod_LoadHiResTexture(shadname, "models", true, true, true); if (!texnum->base) { strcpy(name, loadmodel->name); strcpy(COM_SkipPath(name), COM_SkipPath(shadname)); //eviile eh? texnum->base = Mod_LoadHiResTexture(name, "models", true, true, true); } texnum->bump = 0; if (gl_bumpmappingpossible) { COM_StripExtension(shadname, name); //go for the normalmap strcat(name, "_norm"); texnum->bump = Mod_LoadHiResTexture(name, "models", true, true, false); if (!texnum->bump) { strcpy(name, loadmodel->name); COM_StripExtension(COM_SkipPath(shadname), COM_SkipPath(name)); strcat(name, "_norm"); texnum->bump = Mod_LoadHiResTexture(name, "models", true, true, false); if (!texnum->bump) { COM_StripExtension(shadname, name); //bother, go for heightmap and convert strcat(name, "_bump"); texnum->bump = Mod_LoadBumpmapTexture(name, "models"); if (!texnum->bump) { strcpy(name, loadmodel->name); strcpy(COM_SkipPath(name), COM_SkipPath(shadname)); //eviile eh? COM_StripExtension(name, name); strcat(name, "_bump"); texnum->bump = Mod_LoadBumpmapTexture(name, "models"); } } } } if (r_fb_models.value) { COM_StripExtension(shadname, name); //go for the normalmap strcat(name, "_luma"); texnum->fullbright = Mod_LoadHiResTexture(name, "models", true, true, true); if (!texnum->base) { strcpy(name, loadmodel->name); strcpy(COM_SkipPath(name), COM_SkipPath(shadname)); //eviile eh? COM_StripExtension(name, name); strcat(name, "_luma"); texnum->fullbright = Mod_LoadBumpmapTexture(name, "models"); } } #endif inshader++; skin++; texnum++; } galias->numskins = i; } #endif VectorCopy(min, loadmodel->mins); VectorCopy(max, loadmodel->maxs); #ifndef SERVERONLY if (r_shadows.value) { int *neighbours; neighbours = Hunk_Alloc(sizeof(int)*3*LittleLong(surf->numTriangles)); galias->ofs_trineighbours = (qbyte *)neighbours - (qbyte *)galias; R_BuildTriangleNeighbours(neighbours, indexes, LittleLong(surf->numTriangles)); } #endif surf = (md3Surface_t *)((qbyte *)surf + LittleLong(surf->ofsEnd)); } if (!root) root = Hunk_Alloc(sizeof(galiasinfo_t)); root->numtagframes = LittleLong(header->numFrames); root->numtags = LittleLong(header->numTags); root->ofstags = (char*)Hunk_Alloc(LittleLong(header->numTags)*sizeof(md3tag_t)*LittleLong(header->numFrames)) - (char*)root; { md3tag_t *src; md3tag_t *dst; src = (md3tag_t *)(((unsigned int)header)+LittleLong(header->ofsTags)); dst = (md3tag_t *)(((unsigned int)root)+root->ofstags); for(i=0;inumTags)*LittleLong(header->numFrames);i++) { memcpy(dst->name, src->name, sizeof(dst->name)); for(j=0;j<3;j++) { dst->org[j] = LittleFloat(src->org[j]); } for(j=0;j<3;j++) { for(s=0;s<3;s++) { dst->ang[j][s] = LittleFloat(src->ang[j][s]); } } src++; dst++; } } // // move the complete, relocatable alias model to the cache // hunkend = Hunk_LowMark (); #ifndef SERVERONLY if (mod_md3flags.value) mod->flags = LittleLong(header->flags); else #endif mod->flags = 0; if (!mod->flags) mod->flags = Mod_ReadFlagsFromMD1(mod->name, 0); Hunk_Alloc(0); hunktotal = hunkend - hunkstart; Cache_Alloc (&mod->cache, hunktotal, loadname); mod->type = mod_alias; if (!mod->cache.data) { Hunk_FreeToLowMark (hunkstart); return; } memcpy (mod->cache.data, root, hunktotal); Hunk_FreeToLowMark (hunkstart); mod->funcs.Trace = GLMod_Trace; } #endif #ifdef ZYMOTICMODELS typedef struct zymlump_s { int start; int length; } zymlump_t; typedef struct zymtype1header_s { char id[12]; // "ZYMOTICMODEL", length 12, no termination int type; // 0 (vertex morph) 1 (skeletal pose) or 2 (skeletal scripted) int filesize; // size of entire model file float mins[3], maxs[3], radius; // for clipping uses int numverts; int numtris; int numsurfaces; int numbones; // this may be zero in the vertex morph format (undecided) int numscenes; // 0 in skeletal scripted models // skeletal pose header // lump offsets are relative to the file zymlump_t lump_scenes; // zymscene_t scene[numscenes]; // name and other information for each scene (see zymscene struct) zymlump_t lump_poses; // float pose[numposes][numbones][6]; // animation data zymlump_t lump_bones; // zymbone_t bone[numbones]; zymlump_t lump_vertbonecounts; // int vertbonecounts[numvertices]; // how many bones influence each vertex (separate mainly to make this compress better) zymlump_t lump_verts; // zymvertex_t vert[numvertices]; // see vertex struct zymlump_t lump_texcoords; // float texcoords[numvertices][2]; zymlump_t lump_render; // int renderlist[rendersize]; // sorted by shader with run lengths (int count), shaders are sequentially used, each run can be used with glDrawElements (each triangle is 3 int indices) zymlump_t lump_surfnames; // char shadername[numsurfaces][32]; // shaders used on this model zymlump_t lump_trizone; // byte trizone[numtris]; // see trizone explanation } zymtype1header_t; typedef struct zymbone_s { char name[32]; int flags; int parent; // parent bone number } zymbone_t; typedef struct zymscene_s { char name[32]; float mins[3], maxs[3], radius; // for clipping float framerate; // the scene will animate at this framerate (in frames per second) int flags; int start, length; // range of poses } zymscene_t; #define ZYMSCENEFLAG_NOLOOP 1 typedef struct zymvertex_s { int bonenum; float origin[3]; } zymvertex_t; //this can generate multiple meshes (one for each shader). //but only one set of transforms are ever generated. void GLMod_LoadZymoticModel(model_t *mod, void *buffer) { #ifndef SERVERONLY galiasskin_t *skin; galiastexnum_t *texnum; int skinfiles; int j; #endif int i; int hunkstart, hunkend, hunktotal; zymtype1header_t *header; galiasinfo_t *root; galisskeletaltransforms_t *transforms; zymvertex_t *intrans; galiasbone_t *bone; zymbone_t *inbone; int v; float multiplier; float *matrix, *inmatrix; vec2_t *stcoords; vec2_t *inst; int *vertbonecounts; galiasgroup_t *grp; zymscene_t *inscene; int *renderlist, count; index_t *indexes; char *surfname; loadmodel=mod; Mod_DoCRC(mod, buffer, com_filesize); hunkstart = Hunk_LowMark (); header = buffer; if (memcmp(header->id, "ZYMOTICMODEL", 12)) Sys_Error("GLMod_LoadZymoticModel: doesn't appear to BE a zymotic!\n"); if (BigLong(header->type) != 1) Sys_Error("GLMod_LoadZymoticModel: only type 1 is supported\n"); for (i = 0; i < sizeof(zymtype1header_t)/4; i++) ((int*)header)[i] = BigLong(((int*)header)[i]); if (!header->numverts) Sys_Error("GLMod_LoadZymoticModel: no vertexes\n"); if (!header->numsurfaces) Sys_Error("GLMod_LoadZymoticModel: no surfaces\n"); VectorCopy(header->mins, mod->mins); VectorCopy(header->maxs, mod->maxs); root = Hunk_AllocName(sizeof(galiasinfo_t)*header->numsurfaces, loadname); root->numtransforms = header->lump_verts.length/sizeof(zymvertex_t); transforms = Hunk_Alloc(root->numtransforms*sizeof(*transforms)); root->ofstransforms = (char*)transforms - (char*)root; vertbonecounts = (int *)((char*)header + header->lump_vertbonecounts.start); intrans = (zymvertex_t *)((char*)header + header->lump_verts.start); vertbonecounts[0] = BigLong(vertbonecounts[0]); multiplier = 1.0f / vertbonecounts[0]; for (i = 0, v=0; i < root->numtransforms; i++) { while(!vertbonecounts[v]) { v++; if (v == header->numverts) Sys_Error("GLMod_LoadZymoticModel: Too many transformations\n"); vertbonecounts[v] = BigLong(vertbonecounts[v]); multiplier = 1.0f / vertbonecounts[v]; } transforms[i].vertexindex = v; transforms[i].boneindex = BigLong(intrans[i].bonenum); transforms[i].org[0] = multiplier*BigFloat(intrans[i].origin[0]); transforms[i].org[1] = multiplier*BigFloat(intrans[i].origin[1]); transforms[i].org[2] = multiplier*BigFloat(intrans[i].origin[2]); transforms[i].org[3] = multiplier*1; vertbonecounts[v]--; } if (intrans != (zymvertex_t *)((char*)header + header->lump_verts.start)) Sys_Error("Vertex transforms list appears corrupt."); if (vertbonecounts != (int *)((char*)header + header->lump_vertbonecounts.start)) Sys_Error("Vertex bone counts list appears corrupt."); root->numverts = v+1; root->numbones = header->numbones; bone = Hunk_Alloc(root->numtransforms*sizeof(*transforms)); inbone = (zymbone_t*)((char*)header + header->lump_bones.start); for (i = 0; i < root->numbones; i++) { Q_strncpyz(bone[i].name, inbone[i].name, sizeof(bone[i].name)); bone[i].parent = BigLong(inbone[i].parent); } root->ofsbones = (char *)bone - (char *)root; renderlist = (int*)((char*)header + header->lump_render.start); for (i = 0;i < header->numsurfaces; i++) { count = BigLong(*renderlist++); count *= 3; indexes = Hunk_Alloc(count*sizeof(*indexes)); root[i].ofs_indexes = (char *)indexes - (char*)&root[i]; root[i].numindexes = count; while(count) { //invert indexes[count-1] = BigLong(renderlist[count-3]); indexes[count-2] = BigLong(renderlist[count-2]); indexes[count-3] = BigLong(renderlist[count-1]); count-=3; } renderlist += root[i].numindexes; } if (renderlist != (int*)((char*)header + header->lump_render.start + header->lump_render.length)) Sys_Error("Render list appears corrupt."); grp = Hunk_Alloc(sizeof(*grp)*header->numscenes*header->numsurfaces); matrix = Hunk_Alloc(header->lump_poses.length); inmatrix = (float*)((char*)header + header->lump_poses.start); for (i = 0; i < header->lump_poses.length/4; i++) matrix[i] = BigFloat(inmatrix[i]); inscene = (zymscene_t*)((char*)header + header->lump_scenes.start); surfname = ((char*)header + header->lump_surfnames.start); stcoords = Hunk_Alloc(root[0].numverts*sizeof(vec2_t)); inst = (vec2_t *)((char *)header + header->lump_texcoords.start); for (i = 0; i < header->lump_texcoords.length/8; i++) { stcoords[i][0] = BigFloat(inst[i][0]); stcoords[i][1] = 1-BigFloat(inst[i][1]); //hmm. upside down skin coords? } #ifndef SERVERONLY skinfiles = GL_BuildSkinFileList(loadmodel->name); if (skinfiles < 1) skinfiles = 1; #endif for (i = 0; i < header->numsurfaces; i++, surfname+=32) { root[i].groups = header->numscenes; root[i].groupofs = (char*)grp - (char*)&root[i]; #ifdef SERVERONLY root[i].numskins = 1; #else root[i].ofs_st_array = (char*)stcoords - (char*)&root[i]; root[i].numskins = skinfiles; skin = Hunk_Alloc((sizeof(galiasskin_t)+sizeof(galiastexnum_t))*skinfiles); texnum = (galiastexnum_t*)(skin+skinfiles); for (j = 0; j < skinfiles; j++, texnum++) { skin[j].texnums = 1; //non-sequenced skins. skin[j].ofstexnums = (char *)texnum - (char *)&skin[j]; GL_LoadSkinFile(texnum, surfname, j, NULL, 0, 0, NULL); } root[i].ofsskins = (char *)skin - (char *)&root[i]; #endif } for (i = 0; i < header->numscenes; i++, grp++, inscene++) { grp->isheirachical = 1; grp->rate = BigFloat(inscene->framerate); grp->loop = !(BigLong(inscene->flags) & ZYMSCENEFLAG_NOLOOP); grp->numposes = BigLong(inscene->length); grp->poseofs = (char*)matrix - (char*)grp; grp->poseofs += BigLong(inscene->start)*12*sizeof(float)*root->numbones; } if (inscene != (zymscene_t*)((char*)header + header->lump_scenes.start+header->lump_scenes.length)) Sys_Error("scene list appears corrupt."); for (i = 0; i < header->numsurfaces-1; i++) root[i].nextsurf = sizeof(galiasinfo_t); for (i = 1; i < header->numsurfaces; i++) { root[i].sharesverts = true; root[i].numbones = root[0].numbones; root[i].numverts = root[0].numverts; root[i].ofsbones = root[0].ofsbones; root[i-1].nextsurf = sizeof(*root); } // // move the complete, relocatable alias model to the cache // hunkend = Hunk_LowMark (); mod->flags = Mod_ReadFlagsFromMD1(mod->name, 0); //file replacement - inherit flags from any defunc mdl files. Hunk_Alloc(0); hunktotal = hunkend - hunkstart; Cache_Alloc (&mod->cache, hunktotal, loadname); mod->type = mod_alias; if (!mod->cache.data) { Hunk_FreeToLowMark (hunkstart); return; } memcpy (mod->cache.data, root, hunktotal); Hunk_FreeToLowMark (hunkstart); mod->funcs.Trace = GLMod_Trace; } ////////////////////////////////////////////////////////////// //dpm // header for the entire file typedef struct dpmheader_s { char id[16]; // "DARKPLACESMODEL\0", length 16 unsigned int type; // 2 (hierarchical skeletal pose) unsigned int filesize; // size of entire model file float mins[3], maxs[3], yawradius, allradius; // for clipping uses // these offsets are relative to the file unsigned int num_bones; unsigned int num_meshs; unsigned int num_frames; unsigned int ofs_bones; // dpmbone_t bone[num_bones]; unsigned int ofs_meshs; // dpmmesh_t mesh[num_meshs]; unsigned int ofs_frames; // dpmframe_t frame[num_frames]; } dpmheader_t; // there may be more than one of these typedef struct dpmmesh_s { // these offsets are relative to the file char shadername[32]; // name of the shader to use unsigned int num_verts; unsigned int num_tris; unsigned int ofs_verts; // dpmvertex_t vert[numvertices]; // see vertex struct unsigned int ofs_texcoords; // float texcoords[numvertices][2]; unsigned int ofs_indices; // unsigned int indices[numtris*3]; // designed for glDrawElements (each triangle is 3 unsigned int indices) unsigned int ofs_groupids; // unsigned int groupids[numtris]; // the meaning of these values is entirely up to the gamecode and modeler } dpmmesh_t; // if set on a bone, it must be protected from removal #define DPMBONEFLAG_ATTACHMENT 1 // one per bone typedef struct dpmbone_s { // name examples: upperleftarm leftfinger1 leftfinger2 hand, etc char name[32]; // parent bone number signed int parent; // flags for the bone unsigned int flags; } dpmbone_t; // a bonepose matrix is intended to be used like this: // (n = output vertex, v = input vertex, m = matrix, f = influence) // n[0] = v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2] + f * m[0][3]; // n[1] = v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2] + f * m[1][3]; // n[2] = v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2] + f * m[2][3]; typedef struct dpmbonepose_s { float matrix[3][4]; } dpmbonepose_t; // immediately followed by bone positions for the frame typedef struct dpmframe_s { // name examples: idle_1 idle_2 idle_3 shoot_1 shoot_2 shoot_3, etc char name[32]; float mins[3], maxs[3], yawradius, allradius; int ofs_bonepositions; // dpmbonepose_t bonepositions[bones]; } dpmframe_t; // one or more of these per vertex typedef struct dpmbonevert_s { float origin[3]; // vertex location (these blend) float influence; // influence fraction (these must add up to 1) float normal[3]; // surface normal (these blend) unsigned int bonenum; // number of the bone } dpmbonevert_t; // variable size, parsed sequentially typedef struct dpmvertex_s { unsigned int numbones; // immediately followed by 1 or more dpmbonevert_t structures } dpmvertex_t; void GLMod_LoadDarkPlacesModel(model_t *mod, void *buffer) { #ifndef SERVERONLY galiasskin_t *skin; galiastexnum_t *texnum; int skinfiles; #endif int i, j, k; int hunkstart, hunkend, hunktotal; dpmheader_t *header; galiasinfo_t *root, *m; dpmmesh_t *mesh; dpmvertex_t *vert; dpmbonevert_t *bonevert; galisskeletaltransforms_t *transforms; galiasbone_t *outbone; dpmbone_t *inbone; float *inst, *outst; float *outposedata; galiasgroup_t *outgroups; float *inposedata; dpmframe_t *inframes; unsigned int *index; index_t *outdex; // groan... int numtransforms; int numverts; loadmodel=mod; Mod_DoCRC(mod, buffer, com_filesize); hunkstart = Hunk_LowMark (); header = buffer; if (memcmp(header->id, "DARKPLACESMODEL\0", 16)) Sys_Error("GLMod_LoadDarkPlacesModel: doesn't appear to be a darkplaces model!\n"); if (BigLong(header->type) != 2) Sys_Error("GLMod_LoadDarkPlacesModel: only type 2 is supported\n"); for (i = 0; i < sizeof(dpmheader_t)/4; i++) ((int*)header)[i] = BigLong(((int*)header)[i]); if (!header->num_bones) Sys_Error("GLMod_LoadDarkPlacesModel: no bones\n"); if (!header->num_frames) Sys_Error("GLMod_LoadDarkPlacesModel: no frames\n"); if (!header->num_meshs) Sys_Error("GLMod_LoadDarkPlacesModel: no surfaces\n"); VectorCopy(header->mins, mod->mins); VectorCopy(header->maxs, mod->maxs); root = Hunk_AllocName(sizeof(galiasinfo_t)*header->num_meshs, loadname); mesh = (dpmmesh_t*)((char*)buffer + header->ofs_meshs); for (i = 0; i < header->num_meshs; i++, mesh++) { //work out how much memory we need to allocate mesh->num_verts = BigLong(mesh->num_verts); mesh->num_tris = BigLong(mesh->num_tris); mesh->ofs_verts = BigLong(mesh->ofs_verts); mesh->ofs_texcoords = BigLong(mesh->ofs_texcoords); mesh->ofs_indices = BigLong(mesh->ofs_indices); mesh->ofs_groupids = BigLong(mesh->ofs_groupids); numverts = mesh->num_verts; numtransforms = 0; //count and byteswap the transformations vert = (dpmvertex_t*)((char *)buffer+mesh->ofs_verts); for (j = 0; j < mesh->num_verts; j++) { vert->numbones = BigLong(vert->numbones); numtransforms += vert->numbones; bonevert = (dpmbonevert_t*)(vert+1); vert = (dpmvertex_t*)(bonevert+vert->numbones); } m = &root[i]; #ifdef SERVERONLY transforms = Hunk_AllocName(numtransforms*sizeof(galisskeletaltransforms_t) + mesh->num_tris*3*sizeof(index_t), loadname); #else outst = Hunk_AllocName(numverts*sizeof(vec2_t) + numtransforms*sizeof(galisskeletaltransforms_t) + mesh->num_tris*3*sizeof(index_t), loadname); m->ofs_st_array = (char*)outst - (char*)m; m->numverts = mesh->num_verts; inst = (float*)((char*)buffer + mesh->ofs_texcoords); for (j = 0; j < numverts; j++, outst+=2, inst+=2) { outst[0] = BigFloat(inst[0]); outst[1] = BigFloat(inst[1]); } #endif //build the transform list. transforms = (galisskeletaltransforms_t*)outst; m->ofstransforms = (char*)transforms - (char*)m; m->numtransforms = numtransforms; vert = (dpmvertex_t*)((char *)buffer+mesh->ofs_verts); for (j = 0; j < mesh->num_verts; j++) { bonevert = (dpmbonevert_t*)(vert+1); for (k = 0; k < vert->numbones; k++, bonevert++, transforms++) { transforms->boneindex = BigLong(bonevert->bonenum); transforms->vertexindex = j; transforms->org[0] = BigFloat(bonevert->origin[0]); transforms->org[1] = BigFloat(bonevert->origin[1]); transforms->org[2] = BigFloat(bonevert->origin[2]); transforms->org[3] = BigFloat(bonevert->influence); //do nothing with the normals. :( } vert = (dpmvertex_t*)bonevert; } index = (index_t*)((char*)buffer + mesh->ofs_indices); outdex = (index_t *)transforms; m->ofs_indexes = (char*)outdex - (char*)m; m->numindexes = mesh->num_tris*3; for (j = 0; j < m->numindexes; j++) { *outdex++ = BigLong(*index++); } } outbone = Hunk_Alloc(sizeof(galiasbone_t)*header->num_bones); inbone = (dpmbone_t*)((char*)buffer + header->ofs_bones); for (i = 0; i < header->num_bones; i++) { outbone[i].parent = BigLong(inbone[i].parent); if (outbone[i].parent >= i || outbone[i].parent < -1) Sys_Error("GLMod_LoadDarkPlacesModel: bad bone index in %s\n", mod->name); Q_strncpyz(outbone[i].name, inbone[i].name, sizeof(outbone[i].name)); //throw away the flags. } outgroups = Hunk_Alloc(sizeof(galiasgroup_t)*header->num_frames + sizeof(float)*header->num_frames*header->num_bones*12); outposedata = (float*)(outgroups+header->num_frames); inframes = (dpmframe_t*)((char*)buffer + header->ofs_frames); for (i = 0; i < header->num_frames; i++) { inframes[i].ofs_bonepositions = BigLong(inframes[i].ofs_bonepositions); inframes[i].allradius = BigLong(inframes[i].allradius); inframes[i].yawradius = BigLong(inframes[i].yawradius); inframes[i].mins[0] = BigLong(inframes[i].mins[0]); inframes[i].mins[1] = BigLong(inframes[i].mins[1]); inframes[i].mins[2] = BigLong(inframes[i].mins[2]); inframes[i].maxs[0] = BigLong(inframes[i].maxs[0]); inframes[i].maxs[1] = BigLong(inframes[i].maxs[1]); inframes[i].maxs[2] = BigLong(inframes[i].maxs[2]); outgroups[i].rate = 10; outgroups[i].numposes = 1; outgroups[i].isheirachical = true; outgroups[i].poseofs = (char*)outposedata - (char*)&outgroups[i]; inposedata = (float*)((char*)buffer + inframes[i].ofs_bonepositions); for (j = 0; j < header->num_bones*12; j++) *outposedata++ = BigFloat(*inposedata++); } #ifndef SERVERONLY skinfiles = GL_BuildSkinFileList(loadmodel->name); if (skinfiles < 1) skinfiles = 1; #endif mesh = (dpmmesh_t*)((char*)buffer + header->ofs_meshs); for (i = 0; i < header->num_meshs; i++, mesh++) { m = &root[i]; if (i < header->num_meshs-1) m->nextsurf = sizeof(galiasinfo_t); m->sharesbones = true; m->ofsbones = (char*)outbone-(char*)m; m->numbones = header->num_bones; m->groups = header->num_frames; m->groupofs = (char*)outgroups - (char*)m; #ifdef SERVERONLY m->numskins = 1; #else m->numskins = skinfiles; skin = Hunk_Alloc((sizeof(galiasskin_t)+sizeof(galiastexnum_t))*skinfiles); texnum = (galiastexnum_t*)(skin+skinfiles); for (j = 0; j < skinfiles; j++, texnum++) { skin[j].texnums = 1; //non-sequenced skins. skin[j].ofstexnums = (char *)texnum - (char *)&skin[j]; GL_LoadSkinFile(texnum, mesh->shadername, j, NULL, 0, 0, NULL); } m->ofsskins = (char *)skin - (char *)m; #endif } root[0].sharesbones = false; // // move the complete, relocatable alias model to the cache // hunkend = Hunk_LowMark (); mod->flags = Mod_ReadFlagsFromMD1(mod->name, 0); //file replacement - inherit flags from any defunc mdl files. Hunk_Alloc(0); hunktotal = hunkend - hunkstart; Cache_Alloc (&mod->cache, hunktotal, loadname); mod->type = mod_alias; if (!mod->cache.data) { Hunk_FreeToLowMark (hunkstart); return; } memcpy (mod->cache.data, root, hunktotal); Hunk_FreeToLowMark (hunkstart); mod->funcs.Trace = GLMod_Trace; } #endif //ZYMOTICMODELS #ifdef MD5MODELS static void GenMatrix(float x, float y, float z, float qx, float qy, float qz, float result[12]) { float qw; { //figure out qw float term = 1 - (qx*qx) - (qy*qy) - (qz*qz); if (term < 0) qw = 0; else qw = - (float) sqrt(term); } { //generate the matrix /* float xx = qx * qx; float xy = qx * qy; float xz = qx * qz; float xw = qx * qw; float yy = qy * qy; float yz = qy * qz; float yw = qy * qw; float zz = qz * qz; float zw = qz * qw; result[0*4+0] = 1 - 2 * ( yy + zz ); result[0*4+1] = 2 * ( xy - zw ); result[0*4+2] = 2 * ( xz + yw ); result[0*4+3] = x; result[1*4+0] = 2 * ( xy + zw ); result[1*4+1] = 1 - 2 * ( xx + zz ); result[1*4+2] = 2 * ( yz - xw ); result[1*4+3] = y; result[2*4+0] = 2 * ( xz - yw ); result[2*4+1] = 2 * ( yz + xw ); result[2*4+2] = 1 - 2 * ( xx + yy ); result[2*4+3] = z; */ float xx, xy, xz, xw, yy, yz, yw, zz, zw; float x2, y2, z2; x2 = qx + qx; y2 = qy + qy; z2 = qz + qz; xx = qx * x2; xy = qx * y2; xz = qx * z2; yy = qy * y2; yz = qy * z2; zz = qz * z2; xw = qw * x2; yw = qw * y2; zw = qw * z2; result[0*4+0] = 1.0f - (yy + zz); result[1*4+0] = xy + zw; result[2*4+0] = xz - yw; result[0*4+1] = xy - zw; result[1*4+1] = 1.0f - (xx + zz); result[2*4+1] = yz + xw; result[0*4+2] = xz + yw; result[1*4+2] = yz - xw; result[2*4+2] = 1.0f - (xx + yy); result[0*4+3] = x; result[1*4+3] = y; result[2*4+3] = z; } } galiasinfo_t *GLMod_ParseMD5MeshModel(char *buffer) { #define EXPECT(x) buffer = COM_Parse(buffer); if (strcmp(com_token, x)) Sys_Error("MD5MESH: expected %s", x); int numjoints = 0; int nummeshes = 0; qboolean foundjoints = false; int meshnum = 0; int i; galiasbone_t *bones = NULL; galiasgroup_t *pose = NULL; galiasinfo_t *inf, *root, *lastsurf; float *posedata; #ifndef SERVERONLY galiasskin_t *skin; galiastexnum_t *texnum; #endif float x, y, z, qx, qy, qz; buffer = COM_Parse(buffer); if (strcmp(com_token, "MD5Version")) Sys_Error("MD5 model without MD5Version identifier first\n"); buffer = COM_Parse(buffer); if (atoi(com_token) != 10) Sys_Error("MD5 model with unsupported MD5Version\n"); root = Hunk_Alloc(sizeof(galiasinfo_t)); lastsurf = NULL; for(;;) { buffer = COM_Parse(buffer); if (!buffer) break; if (!strcmp(com_token, "commandline")) { //we don't need this buffer = strchr(buffer, '\"'); buffer = strchr((char*)buffer+1, '\"')+1; // buffer = COM_Parse(buffer); } else if (!strcmp(com_token, "numJoints")) { if (numjoints) Sys_Error("MD5MESH: numMeshes was already declared"); buffer = COM_Parse(buffer); numjoints = atoi(com_token); if (numjoints <= 0) Sys_Error("MD5MESH: Needs some joints"); } else if (!strcmp(com_token, "numMeshes")) { if (nummeshes) Sys_Error("MD5MESH: numMeshes was already declared"); buffer = COM_Parse(buffer); nummeshes = atoi(com_token); if (nummeshes <= 0) Sys_Error("MD5MESH: Needs some meshes"); } else if (!strcmp(com_token, "joints")) { if (foundjoints) Sys_Error("MD5MESH: Duplicate joints section"); foundjoints=true; if (!numjoints) Sys_Error("MD5MESH: joints section before (or without) numjoints"); bones = Hunk_Alloc(sizeof(*bones) * numjoints); pose = Hunk_Alloc(sizeof(galiasgroup_t)); posedata = Hunk_Alloc(sizeof(float)*12 * numjoints); pose->isheirachical = false; pose->rate = 1; pose->numposes = 1; pose->poseofs = (char*)posedata - (char*)pose; EXPECT("{"); //"name" parent (x y z) (s t u) //stu are a normalized quaternion, which we will convert to a 3*4 matrix for no apparent reason for (i = 0; i < numjoints; i++) { buffer = COM_Parse(buffer); Q_strncpyz(bones[i].name, com_token, sizeof(bones[i].name)); buffer = COM_Parse(buffer); bones[i].parent = atoi(com_token); if (bones[i].parent >= i) Sys_Error("MD5MESH: joints parent's must be lower"); if ((bones[i].parent < 0 && i) || (!i && bones[i].parent!=-1)) Sys_Error("MD5MESH: Only the root joint may have a negative parent"); EXPECT("("); buffer = COM_Parse(buffer); x = atof(com_token); buffer = COM_Parse(buffer); y = atof(com_token); buffer = COM_Parse(buffer); z = atof(com_token); EXPECT(")"); EXPECT("("); buffer = COM_Parse(buffer); qx = atof(com_token); buffer = COM_Parse(buffer); qy = atof(com_token); buffer = COM_Parse(buffer); qz = atof(com_token); EXPECT(")"); GenMatrix(x, y, z, qx, qy, qz, posedata+i*12); } EXPECT("}"); } else if (!strcmp(com_token, "mesh")) { int numverts = 0; int numweights = 0; int numtris = 0; int num; int vnum; int numusableweights = 0; int *firstweightlist = NULL; int *numweightslist = NULL; galisskeletaltransforms_t *trans; #ifndef SERVERONLY float *stcoord = NULL; #endif int *indexes = NULL; float w; vec4_t *rawweight = NULL; int *rawweightbone = NULL; if (!nummeshes) Sys_Error("MD5MESH: mesh section before (or without) nummeshes"); if (!foundjoints || !bones || !pose) Sys_Error("MD5MESH: mesh must come after joints"); if (!lastsurf) { lastsurf = root; inf = root; } else { inf = Hunk_Alloc(sizeof(*inf)); lastsurf->nextsurf = (char*)inf - (char*)lastsurf; lastsurf = inf; } inf->ofsbones = (char*)bones - (char*)inf; inf->numbones = numjoints; inf->groups = 1; inf->groupofs = (char*)pose - (char*)inf; #ifndef SERVERONLY skin = Hunk_Alloc(sizeof(*skin)); texnum = Hunk_Alloc(sizeof(*texnum)); inf->numskins = 1; inf->ofsskins = (char*)skin - (char*)inf; skin->texnums = 1; skin->skinspeed = 1; skin->ofstexnums = (char*)texnum - (char*)skin; #endif EXPECT("{"); for(;;) { buffer = COM_Parse(buffer); if (!buffer) Sys_Error("MD5MESH: unexpected eof"); if (!strcmp(com_token, "shader")) { buffer = COM_Parse(buffer); #ifndef SERVERONLY // texnum->shader = R_RegisterSkin(com_token); texnum->base = Mod_LoadHiResTexture(com_token, "models", true, true, true); #endif } else if (!strcmp(com_token, "numverts")) { if (numverts) Sys_Error("MD5MESH: numverts was already specified"); buffer = COM_Parse(buffer); numverts = atoi(com_token); if (numverts < 0) Sys_Error("MD5MESH: numverts cannot be negative"); firstweightlist = Z_Malloc(sizeof(*firstweightlist) * numverts); numweightslist = Z_Malloc(sizeof(*numweightslist) * numverts); #ifndef SERVERONLY stcoord = Hunk_Alloc(sizeof(float)*2*numverts); inf->ofs_st_array = (char*)stcoord - (char*)inf; inf->numverts = numverts; #endif } else if (!strcmp(com_token, "vert")) { //vert num ( s t ) firstweight numweights buffer = COM_Parse(buffer); num = atoi(com_token); if (num < 0 || num >= numverts || !indexes) Sys_Error("MD5MESH: vertex out of range"); EXPECT("("); buffer = COM_Parse(buffer); #ifndef SERVERONLY if (!stcoord) Sys_Error("MD5MESH: vertex out of range"); stcoord[num*2+0] = atof(com_token); #endif buffer = COM_Parse(buffer); #ifndef SERVERONLY stcoord[num*2+1] = atof(com_token); #endif EXPECT(")"); buffer = COM_Parse(buffer); firstweightlist[num] = atoi(com_token); buffer = COM_Parse(buffer); numweightslist[num] = atoi(com_token); numusableweights += numweightslist[num]; } else if (!strcmp(com_token, "numtris")) { if (numtris) Sys_Error("MD5MESH: numtris was already specified"); buffer = COM_Parse(buffer); numtris = atoi(com_token); if (numtris < 0) Sys_Error("MD5MESH: numverts cannot be negative"); indexes = Hunk_Alloc(sizeof(int)*3*numtris); inf->ofs_indexes = (char*)indexes - (char*)inf; inf->numindexes = numtris*3; } else if (!strcmp(com_token, "tri")) { buffer = COM_Parse(buffer); num = atoi(com_token); if (num < 0 || num >= numtris) Sys_Error("MD5MESH: vertex out of range"); buffer = COM_Parse(buffer); indexes[num*3+0] = atoi(com_token); buffer = COM_Parse(buffer); indexes[num*3+1] = atoi(com_token); buffer = COM_Parse(buffer); indexes[num*3+2] = atoi(com_token); } else if (!strcmp(com_token, "numweights")) { if (numweights) Sys_Error("MD5MESH: numweights was already specified"); buffer = COM_Parse(buffer); numweights = atoi(com_token); rawweight = Z_Malloc(sizeof(*rawweight)*numweights); rawweightbone = Z_Malloc(sizeof(*rawweightbone)*numweights); } else if (!strcmp(com_token, "weight")) { //weight num bone scale ( x y z ) buffer = COM_Parse(buffer); num = atoi(com_token); if (num < 0 || num >= numweights) Sys_Error("MD5MESH: weight out of range"); buffer = COM_Parse(buffer); rawweightbone[num] = atoi(com_token); if (rawweightbone[num] < 0 || rawweightbone[num] >= numjoints) Sys_Error("MD5MESH: weight specifies bad bone"); buffer = COM_Parse(buffer); w = atof(com_token); EXPECT("("); buffer = COM_Parse(buffer); rawweight[num][0] = w*atof(com_token); buffer = COM_Parse(buffer); rawweight[num][1] = w*atof(com_token); buffer = COM_Parse(buffer); rawweight[num][2] = w*atof(com_token); EXPECT(")"); rawweight[num][3] = w; } else if (!strcmp(com_token, "}")) break; else Sys_Error("MD5MESH: Unrecognised token inside mesh (%s)", com_token); } trans = Hunk_Alloc(sizeof(*trans)*numusableweights); inf->ofstransforms = (char*)trans - (char*)inf; for (num = 0, vnum = 0; num < numverts; num++) { if (numweightslist[num] <= 0) Sys_Error("MD5MESH: weights not set on vertex"); while(numweightslist[num]) { trans[vnum].vertexindex = num; trans[vnum].boneindex = rawweightbone[firstweightlist[num]]; trans[vnum].org[0] = rawweight[firstweightlist[num]][0]; trans[vnum].org[1] = rawweight[firstweightlist[num]][1]; trans[vnum].org[2] = rawweight[firstweightlist[num]][2]; trans[vnum].org[3] = rawweight[firstweightlist[num]][3]; vnum++; firstweightlist[num]++; numweightslist[num]--; } } inf->numtransforms = vnum; if (firstweightlist) Z_Free(firstweightlist); if (numweightslist) Z_Free(numweightslist); if (rawweight) Z_Free(rawweight); if (rawweightbone) Z_Free(rawweightbone); } else Sys_Error("Unrecognised token in MD5 model (%s)", com_token); } if (!lastsurf) Sys_Error("MD5MESH: No meshes"); return root; #undef EXPECT } void GLMod_LoadMD5MeshModel(model_t *mod, void *buffer) { int numjoints = 0; int nummeshes = 0; qboolean foundjoints = false; int meshnum = 0; galiasinfo_t *root; int hunkstart, hunkend, hunktotal; loadmodel=mod; Mod_DoCRC(mod, buffer, com_filesize); hunkstart = Hunk_LowMark (); root = GLMod_ParseMD5MeshModel(buffer); hunkend = Hunk_LowMark (); mod->flags = Mod_ReadFlagsFromMD1(mod->name, 0); //file replacement - inherit flags from any defunc mdl files. Hunk_Alloc(0); hunktotal = hunkend - hunkstart; Cache_Alloc (&mod->cache, hunktotal, loadname); mod->type = mod_alias; if (!mod->cache.data) { Hunk_FreeToLowMark (hunkstart); return; } memcpy (mod->cache.data, root, hunktotal); Hunk_FreeToLowMark (hunkstart); mod->funcs.Trace = GLMod_Trace; } galiasgroup_t GLMod_ParseMD5Anim(char *buffer, galiasinfo_t *prototype, void**poseofs) { #define EXPECT(x) buffer = COM_Parse(buffer); if (strcmp(com_token, x)) Sys_Error("MD5ANIM: expected %s", x); unsigned int i, j; galiasgroup_t grp; unsigned int parent; unsigned int numframes; unsigned int numjoints; float framespersecond; unsigned int numanimatedparts; galiasbone_t *bonelist; unsigned char *boneflags; unsigned int *firstanimatedcomponants; float *animatedcomponants; float *baseframe; //6 componants. float *posedata; float tx, ty, tz, qx, qy, qz; int fac, flags; float f; EXPECT("MD5Version"); EXPECT("10"); EXPECT("commandline"); buffer = COM_Parse(buffer); EXPECT("numFrames"); buffer = COM_Parse(buffer); numframes = atoi(com_token); EXPECT("numJoints"); buffer = COM_Parse(buffer); numjoints = atoi(com_token); EXPECT("frameRate"); buffer = COM_Parse(buffer); framespersecond = atof(com_token); EXPECT("numAnimatedComponents"); buffer = COM_Parse(buffer); numanimatedparts = atoi(com_token); firstanimatedcomponants = BZ_Malloc(sizeof(int)*numjoints); animatedcomponants = BZ_Malloc(sizeof(float)*numanimatedparts); boneflags = BZ_Malloc(sizeof(unsigned char)*numjoints); baseframe = BZ_Malloc(sizeof(float)*12*numjoints); *poseofs = posedata = Hunk_Alloc(sizeof(float)*12*numjoints*numframes); if (prototype) { if (prototype->numbones != numjoints) Sys_Error("MD5ANIM: number of bones doesn't match"); bonelist = (galiasbone_t *)((char*)prototype + prototype->ofsbones); } else { bonelist = Hunk_Alloc(sizeof(galiasbone_t)*numjoints); prototype->ofsbones = (char*)bonelist - (char*)prototype; prototype->numbones = numjoints; } EXPECT("hierarchy"); EXPECT("{"); for (i = 0; i < numjoints; i++, bonelist++) { buffer = COM_Parse(buffer); if (prototype) { if (strcmp(bonelist->name, com_token)) Sys_Error("MD5ANIM: bone name doesn't match (%s)", com_token); } else Q_strncpyz(bonelist->name, com_token, sizeof(bonelist->name)); buffer = COM_Parse(buffer); parent = atoi(com_token); if (prototype) { if (bonelist->parent != parent) Sys_Error("MD5ANIM: bone name doesn't match (%s)", com_token); } else bonelist->parent = parent; buffer = COM_Parse(buffer); boneflags[i] = atoi(com_token); buffer = COM_Parse(buffer); firstanimatedcomponants[i] = atoi(com_token); } EXPECT("}"); EXPECT("bounds"); EXPECT("{"); for (i = 0; i < numframes; i++) { EXPECT("("); buffer = COM_Parse(buffer);f=atoi(com_token); if (f < loadmodel->mins[0]) loadmodel->mins[0] = f; buffer = COM_Parse(buffer);f=atoi(com_token); if (f < loadmodel->mins[1]) loadmodel->mins[1] = f; buffer = COM_Parse(buffer);f=atoi(com_token); if (f < loadmodel->mins[2]) loadmodel->mins[2] = f; EXPECT(")"); EXPECT("("); buffer = COM_Parse(buffer);f=atoi(com_token); if (f > loadmodel->maxs[0]) loadmodel->maxs[0] = f; buffer = COM_Parse(buffer);f=atoi(com_token); if (f > loadmodel->maxs[1]) loadmodel->maxs[1] = f; buffer = COM_Parse(buffer);f=atoi(com_token); if (f > loadmodel->maxs[2]) loadmodel->maxs[2] = f; EXPECT(")"); } EXPECT("}"); EXPECT("baseframe"); EXPECT("{"); for (i = 0; i < numjoints; i++) { EXPECT("("); buffer = COM_Parse(buffer); baseframe[i*6+0] = atof(com_token); buffer = COM_Parse(buffer); baseframe[i*6+1] = atof(com_token); buffer = COM_Parse(buffer); baseframe[i*6+2] = atof(com_token); EXPECT(")"); EXPECT("("); buffer = COM_Parse(buffer); baseframe[i*6+3] = atof(com_token); buffer = COM_Parse(buffer); baseframe[i*6+4] = atof(com_token); buffer = COM_Parse(buffer); baseframe[i*6+5] = atof(com_token); EXPECT(")"); } EXPECT("}"); for (i = 0; i < numframes; i++) { EXPECT("frame"); EXPECT(va("%i", i)); EXPECT("{"); for (j = 0; j < numanimatedparts; j++) { buffer = COM_Parse(buffer); animatedcomponants[j] = atof(com_token); } EXPECT("}"); for (j = 0; j < numjoints; j++) { fac = firstanimatedcomponants[j]; flags = boneflags[j]; if (flags&1) tx = animatedcomponants[fac++]; else tx = baseframe[j*6+0]; if (flags&2) ty = animatedcomponants[fac++]; else ty = baseframe[j*6+1]; if (flags&4) tz = animatedcomponants[fac++]; else tz = baseframe[j*6+2]; if (flags&8) qx = animatedcomponants[fac++]; else qx = baseframe[j*6+3]; if (flags&16) qy = animatedcomponants[fac++]; else qy = baseframe[j*6+4]; if (flags&32) qz = animatedcomponants[fac++]; else qz = baseframe[j*6+5]; GenMatrix(tx, ty, tz, qx, qy, qz, posedata+12*(j+numjoints*i)); } } BZ_Free(firstanimatedcomponants); BZ_Free(animatedcomponants); BZ_Free(boneflags); BZ_Free(baseframe); grp.isheirachical = true; grp.numposes = numframes; grp.rate = framespersecond; grp.loop = true; return grp; #undef EXPECT } /* EXTERNALANIM //File what specifies md5 model/anim stuff. model test/imp.md5mesh group test/idle1.md5anim clampgroup test/idle1.md5anim frames test/idle1.md5anim */ void GLMod_LoadCompositeAnim(model_t *mod, void *buffer) { #define EXPECT(x) buffer = COM_Parse(buffer); if (strcmp(com_token, x)) Sys_Error("MD5MESH: expected %s", x); int numjoints = 0; int nummeshes = 0; qboolean foundjoints = false; int meshnum = 0; int i; char *file; galiasinfo_t *root = NULL; int numgroups = 0; galiasgroup_t *grouplist = NULL; galiasgroup_t *newgroup = NULL; void **poseofs; int hunkstart, hunkend, hunktotal; loadmodel=mod; Mod_DoCRC(mod, buffer, com_filesize); hunkstart = Hunk_LowMark (); buffer = COM_Parse(buffer); if (strcmp(com_token, "EXTERNALANIM")) Sys_Error("EXTERNALANIM: header is not compleate"); buffer = COM_Parse(buffer); if (!strcmp(com_token, "model")) { buffer = COM_Parse(buffer); file = COM_LoadTempFile2(com_token); if (!file) //FIXME: make non fatal somehow.. Sys_Error("Couldn't open %s", com_token); root = GLMod_ParseMD5MeshModel(file); newgroup = (galiasgroup_t*)((char*)root + root->groupofs); grouplist = BZ_Malloc(sizeof(galiasgroup_t)*(numgroups+root->groups)); memcpy(grouplist, newgroup, sizeof(galiasgroup_t)*(numgroups+root->groups)); poseofs = BZ_Malloc(sizeof(galiasgroup_t)*(numgroups+root->groups)); for (i = 0; i < root->groups; i++) { grouplist[numgroups] = newgroup[i]; poseofs[numgroups] = (char*)&newgroup[i] + newgroup[i].poseofs; numgroups++; } } else { Sys_Error("EXTERNALANIM: model must be defined immediatly after the header"); return; } for (;;) { buffer = COM_Parse(buffer); if (!buffer) break; if (!strcmp(com_token, "group")) { grouplist = BZ_Realloc(grouplist, sizeof(galiasgroup_t)*(numgroups+1)); poseofs = BZ_Realloc(poseofs, sizeof(*poseofs)*(numgroups+1)); buffer = COM_Parse(buffer); file = COM_LoadTempFile2(com_token); if (file) //FIXME: make non fatal somehow.. { grouplist[numgroups] = GLMod_ParseMD5Anim(file, root, &poseofs[numgroups]); numgroups++; } } else if (!strcmp(com_token, "clampgroup")) { Sys_Error("EXTERNALANIM: clampgroup not yet supported"); return; } else if (!strcmp(com_token, "frames")) { Sys_Error("EXTERNALANIM: frames not yet supported"); return; } else { Sys_Error("EXTERNALANIM: unrecognised token"); return; } } newgroup = grouplist; grouplist = Hunk_Alloc(sizeof(galiasgroup_t)*numgroups); for(;;) { root->groupofs = (char*)grouplist - (char*)root; root->groups = numgroups; if (!root->nextsurf) break; root = (galiasinfo_t*)((char*)root + root->nextsurf); } for (i = 0; i < numgroups; i++) { grouplist[i] = newgroup[i]; grouplist[i].poseofs = (char*)poseofs[i] - (char*)&grouplist[i]; } hunkend = Hunk_LowMark (); mod->flags = Mod_ReadFlagsFromMD1(mod->name, 0); //file replacement - inherit flags from any defunc mdl files. Hunk_Alloc(0); hunktotal = hunkend - hunkstart; Cache_Alloc (&mod->cache, hunktotal, loadname); mod->type = mod_alias; if (!mod->cache.data) { Hunk_FreeToLowMark (hunkstart); return; } memcpy (mod->cache.data, root, hunktotal); Hunk_FreeToLowMark (hunkstart); mod->funcs.Trace = GLMod_Trace; } #endif //MD5MODELS #endif // defined(RGLQUAKE) || defined(SERVERONLY)