static bool R_LoadMD5Anim( skelAnimation_t *skelAnim, void *buffer, const char *name ) { int i, j; md5Animation_t *anim; md5Frame_t *frame; md5Channel_t *channel; char *token; int version; const char *buf_p; buf_p = (char*) buffer; skelAnim->type = animType_t::AT_MD5; skelAnim->md5 = anim = (md5Animation_t*) ri.Hunk_Alloc( sizeof( *anim ), ha_pref::h_low ); // skip MD5Version indent string COM_ParseExt2( &buf_p, false ); // check version token = COM_ParseExt2( &buf_p, false ); version = atoi( token ); if ( version != MD5_VERSION ) { Log::Warn("RE_RegisterAnimation: '%s' has wrong version (%i should be %i)", name, version, MD5_VERSION ); return false; } // skip commandline <arguments string> token = COM_ParseExt2( &buf_p, true ); token = COM_ParseExt2( &buf_p, true ); // parse numFrames <number> token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "numFrames" ) ) { Log::Warn("RE_RegisterAnimation: expected 'numFrames' found '%s' in model '%s'", token, name ); return false; } token = COM_ParseExt2( &buf_p, false ); anim->numFrames = atoi( token ); // parse numJoints <number> token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "numJoints" ) ) { Log::Warn("RE_RegisterAnimation: expected 'numJoints' found '%s' in model '%s'", token, name ); return false; } token = COM_ParseExt2( &buf_p, false ); anim->numChannels = atoi( token ); // parse frameRate <number> token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "frameRate" ) ) { Log::Warn("RE_RegisterAnimation: expected 'frameRate' found '%s' in model '%s'", token, name ); return false; } token = COM_ParseExt2( &buf_p, false ); anim->frameRate = atoi( token ); // parse numAnimatedComponents <number> token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "numAnimatedComponents" ) ) { Log::Warn("RE_RegisterAnimation: expected 'numAnimatedComponents' found '%s' in model '%s'", token, name ); return false; } token = COM_ParseExt2( &buf_p, false ); anim->numAnimatedComponents = atoi( token ); // parse hierarchy { token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "hierarchy" ) ) { Log::Warn("RE_RegisterAnimation: expected 'hierarchy' found '%s' in model '%s'", token, name ); return false; } token = COM_ParseExt2( &buf_p, false ); if ( Q_stricmp( token, "{" ) ) { Log::Warn("RE_RegisterAnimation: expected '{' found '%s' in model '%s'", token, name ); return false; } // parse all the channels anim->channels = (md5Channel_t*) ri.Hunk_Alloc( sizeof( md5Channel_t ) * anim->numChannels, ha_pref::h_low ); for ( i = 0, channel = anim->channels; i < anim->numChannels; i++, channel++ ) { token = COM_ParseExt2( &buf_p, true ); Q_strncpyz( channel->name, token, sizeof( channel->name ) ); //Log::Notice("RE_RegisterAnimation: '%s' has channel '%s'", name, channel->name); token = COM_ParseExt2( &buf_p, false ); channel->parentIndex = atoi( token ); if ( channel->parentIndex >= anim->numChannels ) { ri.Error( errorParm_t::ERR_DROP, "RE_RegisterAnimation: '%s' has channel '%s' with bad parent index %i while numBones is %i", name, channel->name, channel->parentIndex, anim->numChannels ); } token = COM_ParseExt2( &buf_p, false ); channel->componentsBits = atoi( token ); token = COM_ParseExt2( &buf_p, false ); channel->componentsOffset = atoi( token ); } // parse } token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "}" ) ) { Log::Warn("RE_RegisterAnimation: expected '}' found '%s' in model '%s'", token, name ); return false; } // parse bounds { token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "bounds" ) ) { Log::Warn("RE_RegisterAnimation: expected 'bounds' found '%s' in model '%s'", token, name ); return false; } token = COM_ParseExt2( &buf_p, false ); if ( Q_stricmp( token, "{" ) ) { Log::Warn("RE_RegisterAnimation: expected '{' found '%s' in model '%s'", token, name ); return false; } anim->frames = (md5Frame_t*) ri.Hunk_Alloc( sizeof( md5Frame_t ) * anim->numFrames, ha_pref::h_low ); for ( i = 0, frame = anim->frames; i < anim->numFrames; i++, frame++ ) { // skip ( token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "(" ) ) { Log::Warn("RE_RegisterAnimation: expected '(' found '%s' in model '%s'", token, name ); return false; } for ( j = 0; j < 3; j++ ) { token = COM_ParseExt2( &buf_p, false ); frame->bounds[ 0 ][ j ] = atof( token ); } // skip ) token = COM_ParseExt2( &buf_p, false ); if ( Q_stricmp( token, ")" ) ) { Log::Warn("RE_RegisterAnimation: expected ')' found '%s' in model '%s'", token, name ); return false; } // skip ( token = COM_ParseExt2( &buf_p, false ); if ( Q_stricmp( token, "(" ) ) { Log::Warn("RE_RegisterAnimation: expected '(' found '%s' in model '%s'", token, name ); return false; } for ( j = 0; j < 3; j++ ) { token = COM_ParseExt2( &buf_p, false ); frame->bounds[ 1 ][ j ] = atof( token ); } // skip ) token = COM_ParseExt2( &buf_p, false ); if ( Q_stricmp( token, ")" ) ) { Log::Warn("RE_RegisterAnimation: expected ')' found '%s' in model '%s'", token, name ); return false; } } // parse } token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "}" ) ) { Log::Warn("RE_RegisterAnimation: expected '}' found '%s' in model '%s'", token, name ); return false; } // parse baseframe { token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "baseframe" ) ) { Log::Warn("RE_RegisterAnimation: expected 'baseframe' found '%s' in model '%s'", token, name ); return false; } token = COM_ParseExt2( &buf_p, false ); if ( Q_stricmp( token, "{" ) ) { Log::Warn("RE_RegisterAnimation: expected '{' found '%s' in model '%s'", token, name ); return false; } for ( i = 0, channel = anim->channels; i < anim->numChannels; i++, channel++ ) { // skip ( token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "(" ) ) { Log::Warn("RE_RegisterAnimation: expected '(' found '%s' in model '%s'", token, name ); return false; } for ( j = 0; j < 3; j++ ) { token = COM_ParseExt2( &buf_p, false ); channel->baseOrigin[ j ] = atof( token ); } // skip ) token = COM_ParseExt2( &buf_p, false ); if ( Q_stricmp( token, ")" ) ) { Log::Warn("RE_RegisterAnimation: expected ')' found '%s' in model '%s'", token, name ); return false; } // skip ( token = COM_ParseExt2( &buf_p, false ); if ( Q_stricmp( token, "(" ) ) { Log::Warn("RE_RegisterAnimation: expected '(' found '%s' in model '%s'", token, name ); return false; } for ( j = 0; j < 3; j++ ) { token = COM_ParseExt2( &buf_p, false ); channel->baseQuat[ j ] = atof( token ); } QuatCalcW( channel->baseQuat ); // skip ) token = COM_ParseExt2( &buf_p, false ); if ( Q_stricmp( token, ")" ) ) { Log::Warn("RE_RegisterAnimation: expected ')' found '%s' in model '%s'", token, name ); return false; } } // parse } token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "}" ) ) { Log::Warn("RE_RegisterAnimation: expected '}' found '%s' in model '%s'", token, name ); return false; } for ( i = 0, frame = anim->frames; i < anim->numFrames; i++, frame++ ) { // parse frame <number> { token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "frame" ) ) { Log::Warn("RE_RegisterAnimation: expected 'baseframe' found '%s' in model '%s'", token, name ); return false; } token = COM_ParseExt2( &buf_p, false ); if ( Q_stricmp( token, va( "%i", i ) ) ) { Log::Warn("RE_RegisterAnimation: expected '%i' found '%s' in model '%s'", i, token, name ); return false; } token = COM_ParseExt2( &buf_p, false ); if ( Q_stricmp( token, "{" ) ) { Log::Warn("RE_RegisterAnimation: expected '{' found '%s' in model '%s'", token, name ); return false; } frame->components = (float*) ri.Hunk_Alloc( sizeof( float ) * anim->numAnimatedComponents, ha_pref::h_low ); for (unsigned j = 0; j < anim->numAnimatedComponents; j++ ) { token = COM_ParseExt2( &buf_p, true ); frame->components[ j ] = atof( token ); } // parse } token = COM_ParseExt2( &buf_p, true ); if ( Q_stricmp( token, "}" ) ) { Log::Warn("RE_RegisterAnimation: expected '}' found '%s' in model '%s'", token, name ); return false; } } // everything went ok return true; }
qboolean R_LoadMD5(model_t *mod, void *buffer, int bufferSize, const char *modName) { int i, j, k; md5Model_t *md5; md5Bone_t *bone; md5Surface_t *surf; srfTriangle_t *tri; md5Vertex_t *v; md5Weight_t *weight; int version; shader_t *sh; char *buf_p = ( char * ) buffer; char *token; vec3_t boneOrigin; quat_t boneQuat; matrix_t boneMat; int numRemaining; growList_t sortedTriangles; growList_t vboTriangles; growList_t vboSurfaces; int numBoneReferences; int boneReferences[MAX_BONES]; // skip MD5Version indent string COM_ParseExt2(&buf_p, qfalse); // check version token = COM_ParseExt2(&buf_p, qfalse); version = atoi(token); if (version != MD5_VERSION) { Ren_Warning("R_LoadMD5: %s has wrong version (%i should be %i)\n", modName, version, MD5_VERSION); return qfalse; } mod->type = MOD_MD5; mod->dataSize += sizeof(md5Model_t); md5 = mod->md5 = ri.Hunk_Alloc(sizeof(md5Model_t), h_low); // skip commandline <arguments string> token = COM_ParseExt2(&buf_p, qtrue); token = COM_ParseExt2(&buf_p, qtrue); // Ren_Print("%s\n", token); // parse numJoints <number> token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "numJoints")) { Ren_Warning("R_LoadMD5: expected 'numJoints' found '%s' in model '%s'\n", token, modName); return qfalse; } token = COM_ParseExt2(&buf_p, qfalse); md5->numBones = atoi(token); // parse numMeshes <number> token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "numMeshes")) { Ren_Warning("R_LoadMD5: expected 'numMeshes' found '%s' in model '%s'\n", token, modName); return qfalse; } token = COM_ParseExt2(&buf_p, qfalse); md5->numSurfaces = atoi(token); //Ren_Print("R_LoadMD5: '%s' has %i surfaces\n", modName, md5->numSurfaces); if (md5->numBones < 1) { Ren_Warning("R_LoadMD5: '%s' has no bones\n", modName); return qfalse; } if (md5->numBones > MAX_BONES) { Ren_Warning("R_LoadMD5: '%s' has more than %i bones (%i)\n", modName, MAX_BONES, md5->numBones); return qfalse; } //Ren_Print("R_LoadMD5: '%s' has %i bones\n", modName, md5->numBones); // parse all the bones md5->bones = ri.Hunk_Alloc(sizeof(*bone) * md5->numBones, h_low); // parse joints { token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "joints")) { Ren_Warning("R_LoadMD5: expected 'joints' found '%s' in model '%s'\n", token, modName); return qfalse; } token = COM_ParseExt2(&buf_p, qfalse); if (Q_stricmp(token, "{")) { Ren_Warning("R_LoadMD5: expected '{' found '%s' in model '%s'\n", token, modName); return qfalse; } for (i = 0, bone = md5->bones; i < md5->numBones; i++, bone++) { token = COM_ParseExt2(&buf_p, qtrue); Q_strncpyz(bone->name, token, sizeof(bone->name)); //Ren_Print("R_LoadMD5: '%s' has bone '%s'\n", modName, bone->name); token = COM_ParseExt2(&buf_p, qfalse); bone->parentIndex = atoi(token); //Ren_Print("R_LoadMD5: '%s' has bone '%s' with parent index %i\n", modName, bone->name, bone->parentIndex); if (bone->parentIndex >= md5->numBones) { Ren_Drop("R_LoadMD5: '%s' has bone '%s' with bad parent index %i while numBones is %i", modName, bone->name, bone->parentIndex, md5->numBones); } // skip ( token = COM_ParseExt2(&buf_p, qfalse); if (Q_stricmp(token, "(")) { Ren_Warning("R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName); return qfalse; } for (j = 0; j < 3; j++) { token = COM_ParseExt2(&buf_p, qfalse); boneOrigin[j] = atof(token); } // skip ) token = COM_ParseExt2(&buf_p, qfalse); if (Q_stricmp(token, ")")) { Ren_Warning("R_LoadMD5: expected ')' found '%s' in model '%s'\n", token, modName); return qfalse; } // skip ( token = COM_ParseExt2(&buf_p, qfalse); if (Q_stricmp(token, "(")) { Ren_Warning("R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName); return qfalse; } for (j = 0; j < 3; j++) { token = COM_ParseExt2(&buf_p, qfalse); boneQuat[j] = atof(token); } QuatCalcW(boneQuat); MatrixFromQuat(boneMat, boneQuat); VectorCopy(boneOrigin, bone->origin); QuatCopy(boneQuat, bone->rotation); MatrixSetupTransformFromQuat(bone->inverseTransform, boneQuat, boneOrigin); MatrixInverse(bone->inverseTransform); // skip ) token = COM_ParseExt2(&buf_p, qfalse); if (Q_stricmp(token, ")")) { Ren_Warning("R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName); return qfalse; } } // parse } token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "}")) { Ren_Warning("R_LoadMD5: expected '}' found '%s' in model '%s'\n", token, modName); return qfalse; } // parse all the surfaces if (md5->numSurfaces < 1) { Ren_Warning("R_LoadMD5: '%s' has no surfaces\n", modName); return qfalse; } //Ren_Print("R_LoadMD5: '%s' has %i surfaces\n", modName, md5->numSurfaces); md5->surfaces = ri.Hunk_Alloc(sizeof(*surf) * md5->numSurfaces, h_low); for (i = 0, surf = md5->surfaces; i < md5->numSurfaces; i++, surf++) { // parse mesh { token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "mesh")) { Ren_Warning("R_LoadMD5: expected 'mesh' found '%s' in model '%s'\n", token, modName); return qfalse; } token = COM_ParseExt2(&buf_p, qfalse); if (Q_stricmp(token, "{")) { Ren_Warning("R_LoadMD5: expected '{' found '%s' in model '%s'\n", token, modName); return qfalse; } // change to surface identifier surf->surfaceType = SF_MD5; // give pointer to model for Tess_SurfaceMD5 surf->model = md5; // parse shader <name> token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "shader")) { Ren_Warning("R_LoadMD5: expected 'shader' found '%s' in model '%s'\n", token, modName); return qfalse; } token = COM_ParseExt2(&buf_p, qfalse); Q_strncpyz(surf->shader, token, sizeof(surf->shader)); //Ren_Print("R_LoadMD5: '%s' uses shader '%s'\n", modName, surf->shader); // FIXME .md5mesh meshes don't have surface names // lowercase the surface name so skin compares are faster //Q_strlwr(surf->name); //Ren_Print("R_LoadMD5: '%s' has surface '%s'\n", modName, surf->name); // register the shaders sh = R_FindShader(surf->shader, SHADER_3D_DYNAMIC, qtrue); if (sh->defaultShader) { surf->shaderIndex = 0; } else { surf->shaderIndex = sh->index; } // parse numVerts <number> token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "numVerts")) { Ren_Warning("R_LoadMD5: expected 'numVerts' found '%s' in model '%s'\n", token, modName); return qfalse; } token = COM_ParseExt2(&buf_p, qfalse); surf->numVerts = atoi(token); if (surf->numVerts > SHADER_MAX_VERTEXES) { Ren_Drop("R_LoadMD5: '%s' has more than %i verts on a surface (%i)", modName, SHADER_MAX_VERTEXES, surf->numVerts); } surf->verts = ri.Hunk_Alloc(sizeof(*v) * surf->numVerts, h_low); for (j = 0, v = surf->verts; j < surf->numVerts; j++, v++) { // skip vert <number> token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "vert")) { Ren_Warning("R_LoadMD5: expected 'vert' found '%s' in model '%s'\n", token, modName); return qfalse; } COM_ParseExt2(&buf_p, qfalse); // skip ( token = COM_ParseExt2(&buf_p, qfalse); if (Q_stricmp(token, "(")) { Ren_Warning("R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName); return qfalse; } for (k = 0; k < 2; k++) { token = COM_ParseExt2(&buf_p, qfalse); v->texCoords[k] = atof(token); } // skip ) token = COM_ParseExt2(&buf_p, qfalse); if (Q_stricmp(token, ")")) { Ren_Warning("R_LoadMD5: expected ')' found '%s' in model '%s'\n", token, modName); return qfalse; } token = COM_ParseExt2(&buf_p, qfalse); v->firstWeight = atoi(token); token = COM_ParseExt2(&buf_p, qfalse); v->numWeights = atoi(token); if (v->numWeights > MAX_WEIGHTS) { Ren_Drop("R_LoadMD5: vertex %i requires more than %i weights on surface (%i) in model '%s'", j, MAX_WEIGHTS, i, modName); } } // parse numTris <number> token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "numTris")) { Ren_Warning("R_LoadMD5: expected 'numTris' found '%s' in model '%s'\n", token, modName); return qfalse; } token = COM_ParseExt2(&buf_p, qfalse); surf->numTriangles = atoi(token); if (surf->numTriangles > SHADER_MAX_TRIANGLES) { Ren_Drop("R_LoadMD5: '%s' has more than %i triangles on a surface (%i)", modName, SHADER_MAX_TRIANGLES, surf->numTriangles); } surf->triangles = ri.Hunk_Alloc(sizeof(*tri) * surf->numTriangles, h_low); for (j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++) { // skip tri <number> token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "tri")) { Ren_Warning("R_LoadMD5: expected 'tri' found '%s' in model '%s'\n", token, modName); return qfalse; } COM_ParseExt2(&buf_p, qfalse); for (k = 0; k < 3; k++) { token = COM_ParseExt2(&buf_p, qfalse); tri->indexes[k] = atoi(token); } } // parse numWeights <number> token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "numWeights")) { Ren_Warning("R_LoadMD5: expected 'numWeights' found '%s' in model '%s'\n", token, modName); return qfalse; } token = COM_ParseExt2(&buf_p, qfalse); surf->numWeights = atoi(token); surf->weights = ri.Hunk_Alloc(sizeof(*weight) * surf->numWeights, h_low); for (j = 0, weight = surf->weights; j < surf->numWeights; j++, weight++) { // skip weight <number> token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "weight")) { Ren_Warning("R_LoadMD5: expected 'weight' found '%s' in model '%s'\n", token, modName); return qfalse; } COM_ParseExt2(&buf_p, qfalse); token = COM_ParseExt2(&buf_p, qfalse); weight->boneIndex = atoi(token); token = COM_ParseExt2(&buf_p, qfalse); weight->boneWeight = atof(token); // skip ( token = COM_ParseExt2(&buf_p, qfalse); if (Q_stricmp(token, "(")) { Ren_Warning("R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName); return qfalse; } for (k = 0; k < 3; k++) { token = COM_ParseExt2(&buf_p, qfalse); weight->offset[k] = atof(token); } // skip ) token = COM_ParseExt2(&buf_p, qfalse); if (Q_stricmp(token, ")")) { Ren_Warning("R_LoadMD5: expected ')' found '%s' in model '%s'\n", token, modName); return qfalse; } } // parse } token = COM_ParseExt2(&buf_p, qtrue); if (Q_stricmp(token, "}")) { Ren_Warning("R_LoadMD5: expected '}' found '%s' in model '%s'\n", token, modName); return qfalse; } // loop trough all vertices and set up the vertex weights for (j = 0, v = surf->verts; j < surf->numVerts; j++, v++) { v->weights = ri.Hunk_Alloc(sizeof(*v->weights) * v->numWeights, h_low); for (k = 0; k < v->numWeights; k++) { v->weights[k] = surf->weights + (v->firstWeight + k); } } } // loading is done now calculate the bounding box and tangent spaces ClearBounds(md5->bounds[0], md5->bounds[1]); for (i = 0, surf = md5->surfaces; i < md5->numSurfaces; i++, surf++) { for (j = 0, v = surf->verts; j < surf->numVerts; j++, v++) { vec3_t tmpVert; md5Weight_t *w; VectorClear(tmpVert); for (k = 0, w = v->weights[0]; k < v->numWeights; k++, w++) { vec3_t offsetVec; bone = &md5->bones[w->boneIndex]; QuatTransformVector(bone->rotation, w->offset, offsetVec); VectorAdd(bone->origin, offsetVec, offsetVec); VectorMA(tmpVert, w->boneWeight, offsetVec, tmpVert); } VectorCopy(tmpVert, v->position); AddPointToBounds(tmpVert, md5->bounds[0], md5->bounds[1]); } // calc tangent spaces #if 1 { const float *v0, *v1, *v2; const float *t0, *t1, *t2; vec3_t tangent; vec3_t binormal; vec3_t normal; for (j = 0, v = surf->verts; j < surf->numVerts; j++, v++) { VectorClear(v->tangent); VectorClear(v->binormal); VectorClear(v->normal); } for (j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++) { v0 = surf->verts[tri->indexes[0]].position; v1 = surf->verts[tri->indexes[1]].position; v2 = surf->verts[tri->indexes[2]].position; t0 = surf->verts[tri->indexes[0]].texCoords; t1 = surf->verts[tri->indexes[1]].texCoords; t2 = surf->verts[tri->indexes[2]].texCoords; #if 1 R_CalcTangentSpace(tangent, binormal, normal, v0, v1, v2, t0, t1, t2); #else R_CalcNormalForTriangle(normal, v0, v1, v2); R_CalcTangentsForTriangle(tangent, binormal, v0, v1, v2, t0, t1, t2); #endif for (k = 0; k < 3; k++) { float *v; v = surf->verts[tri->indexes[k]].tangent; VectorAdd(v, tangent, v); v = surf->verts[tri->indexes[k]].binormal; VectorAdd(v, binormal, v); v = surf->verts[tri->indexes[k]].normal; VectorAdd(v, normal, v); } } for (j = 0, v = surf->verts; j < surf->numVerts; j++, v++) { VectorNormalize(v->tangent); VectorNormalize(v->binormal); VectorNormalize(v->normal); } } #else { int k; float bb, s, t; vec3_t bary; vec3_t faceNormal; md5Vertex_t *dv[3]; for (j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++) { dv[0] = &surf->verts[tri->indexes[0]]; dv[1] = &surf->verts[tri->indexes[1]]; dv[2] = &surf->verts[tri->indexes[2]]; R_CalcNormalForTriangle(faceNormal, dv[0]->position, dv[1]->position, dv[2]->position); // calculate barycentric basis for the triangle bb = (dv[1]->texCoords[0] - dv[0]->texCoords[0]) * (dv[2]->texCoords[1] - dv[0]->texCoords[1]) - (dv[2]->texCoords[0] - dv[0]->texCoords[0]) * (dv[1]->texCoords[1] - dv[0]->texCoords[1]); if (fabs(bb) < 0.00000001f) { continue; } // do each vertex for (k = 0; k < 3; k++) { // calculate s tangent vector s = dv[k]->texCoords[0] + 10.0f; t = dv[k]->texCoords[1]; bary[0] = ((dv[1]->texCoords[0] - s) * (dv[2]->texCoords[1] - t) - (dv[2]->texCoords[0] - s) * (dv[1]->texCoords[1] - t)) / bb; bary[1] = ((dv[2]->texCoords[0] - s) * (dv[0]->texCoords[1] - t) - (dv[0]->texCoords[0] - s) * (dv[2]->texCoords[1] - t)) / bb; bary[2] = ((dv[0]->texCoords[0] - s) * (dv[1]->texCoords[1] - t) - (dv[1]->texCoords[0] - s) * (dv[0]->texCoords[1] - t)) / bb; dv[k]->tangent[0] = bary[0] * dv[0]->position[0] + bary[1] * dv[1]->position[0] + bary[2] * dv[2]->position[0]; dv[k]->tangent[1] = bary[0] * dv[0]->position[1] + bary[1] * dv[1]->position[1] + bary[2] * dv[2]->position[1]; dv[k]->tangent[2] = bary[0] * dv[0]->position[2] + bary[1] * dv[1]->position[2] + bary[2] * dv[2]->position[2]; VectorSubtract(dv[k]->tangent, dv[k]->position, dv[k]->tangent); VectorNormalize(dv[k]->tangent); // calculate t tangent vector (binormal) s = dv[k]->texCoords[0]; t = dv[k]->texCoords[1] + 10.0f; bary[0] = ((dv[1]->texCoords[0] - s) * (dv[2]->texCoords[1] - t) - (dv[2]->texCoords[0] - s) * (dv[1]->texCoords[1] - t)) / bb; bary[1] = ((dv[2]->texCoords[0] - s) * (dv[0]->texCoords[1] - t) - (dv[0]->texCoords[0] - s) * (dv[2]->texCoords[1] - t)) / bb; bary[2] = ((dv[0]->texCoords[0] - s) * (dv[1]->texCoords[1] - t) - (dv[1]->texCoords[0] - s) * (dv[0]->texCoords[1] - t)) / bb; dv[k]->binormal[0] = bary[0] * dv[0]->position[0] + bary[1] * dv[1]->position[0] + bary[2] * dv[2]->position[0]; dv[k]->binormal[1] = bary[0] * dv[0]->position[1] + bary[1] * dv[1]->position[1] + bary[2] * dv[2]->position[1]; dv[k]->binormal[2] = bary[0] * dv[0]->position[2] + bary[1] * dv[1]->position[2] + bary[2] * dv[2]->position[2]; VectorSubtract(dv[k]->binormal, dv[k]->position, dv[k]->binormal); VectorNormalize(dv[k]->binormal); // calculate the normal as cross product N=TxB #if 0 CrossProduct(dv[k]->tangent, dv[k]->binormal, dv[k]->normal); VectorNormalize(dv[k]->normal); // Gram-Schmidt orthogonalization process for B // compute the cross product B=NxT to obtain // an orthogonal basis CrossProduct(dv[k]->normal, dv[k]->tangent, dv[k]->binormal); if (DotProduct(dv[k]->normal, faceNormal) < 0) { VectorInverse(dv[k]->normal); //VectorInverse(dv[k]->tangent); //VectorInverse(dv[k]->binormal); } #else VectorAdd(dv[k]->normal, faceNormal, dv[k]->normal); #endif } } #if 1 for (j = 0, v = surf->verts; j < surf->numVerts; j++, v++) { //VectorNormalize(v->tangent); //VectorNormalize(v->binormal); VectorNormalize(v->normal); } #endif } #endif #if 0 // do another extra smoothing for normals to avoid flat shading for (j = 0; j < surf->numVerts; j++) { for (k = 0; k < surf->numVerts; k++) { if (j == k) { continue; } if (VectorCompare(surf->verts[j].position, surf->verts[k].position)) { VectorAdd(surf->verts[j].normal, surf->verts[k].normal, surf->verts[j].normal); } } VectorNormalize(surf->verts[j].normal); } #endif } // split the surfaces into VBO surfaces by the maximum number of GPU vertex skinning bones Com_InitGrowList(&vboSurfaces, 10); for (i = 0, surf = md5->surfaces; i < md5->numSurfaces; i++, surf++) { // sort triangles Com_InitGrowList(&sortedTriangles, 1000); for (j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++) { skelTriangle_t *sortTri = Com_Allocate(sizeof(*sortTri)); for (k = 0; k < 3; k++) { sortTri->indexes[k] = tri->indexes[k]; sortTri->vertexes[k] = &surf->verts[tri->indexes[k]]; } sortTri->referenced = qfalse; Com_AddToGrowList(&sortedTriangles, sortTri); } //qsort(sortedTriangles.elements, sortedTriangles.currentElements, sizeof(void *), CompareTrianglesByBoneReferences); #if 0 for (j = 0; j < sortedTriangles.currentElements; j++) { int b[MAX_WEIGHTS * 3]; skelTriangle_t *sortTri = Com_GrowListElement(&sortedTriangles, j); for (k = 0; k < 3; k++) { v = sortTri->vertexes[k]; for (l = 0; l < MAX_WEIGHTS; l++) { b[k * 3 + l] = (l < v->numWeights) ? v->weights[l]->boneIndex : 9999; } qsort(b, MAX_WEIGHTS * 3, sizeof(int), CompareBoneIndices); //Ren_Print("bone indices: %i %i %i %i\n", b[k * 3 + 0], b[k * 3 + 1], b[k * 3 + 2], b[k * 3 + 3]); } } #endif numRemaining = sortedTriangles.currentElements; while (numRemaining) { numBoneReferences = 0; Com_Memset(boneReferences, 0, sizeof(boneReferences)); Com_InitGrowList(&vboTriangles, 1000); for (j = 0; j < sortedTriangles.currentElements; j++) { skelTriangle_t *sortTri = Com_GrowListElement(&sortedTriangles, j); if (sortTri->referenced) { continue; } if (AddTriangleToVBOTriangleList(&vboTriangles, sortTri, &numBoneReferences, boneReferences)) { sortTri->referenced = qtrue; } } if (!vboTriangles.currentElements) { Ren_Warning("R_LoadMD5: could not add triangles to a remaining VBO surfaces for model '%s'\n", modName); Com_DestroyGrowList(&vboTriangles); break; } AddSurfaceToVBOSurfacesList(&vboSurfaces, &vboTriangles, md5, surf, i, numBoneReferences, boneReferences); numRemaining -= vboTriangles.currentElements; Com_DestroyGrowList(&vboTriangles); } for (j = 0; j < sortedTriangles.currentElements; j++) { skelTriangle_t *sortTri = Com_GrowListElement(&sortedTriangles, j); Com_Dealloc(sortTri); } Com_DestroyGrowList(&sortedTriangles); } // move VBO surfaces list to hunk md5->numVBOSurfaces = vboSurfaces.currentElements; md5->vboSurfaces = ri.Hunk_Alloc(md5->numVBOSurfaces * sizeof(*md5->vboSurfaces), h_low); for (i = 0; i < md5->numVBOSurfaces; i++) { md5->vboSurfaces[i] = ( srfVBOMD5Mesh_t * ) Com_GrowListElement(&vboSurfaces, i); } Com_DestroyGrowList(&vboSurfaces); return qtrue; }
/** * @brief Finds and loads all .shader files, combining them into * a single large text block that can be scanned for shader names */ int ScanAndLoadShaderFilesR1() { char **shaderFiles; char *buffers[MAX_SHADER_FILES]; char *p; int numShaderFiles, i; char *oldp, *token, *textEnd; char **hashMem; int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash; unsigned int size; char filename[MAX_QPATH]; long sum = 0, summand; Com_Memset(buffers, 0, MAX_SHADER_FILES); Com_Memset(shaderTextHashTableSizes, 0, MAX_SHADER_FILES); // scan for shader files shaderFiles = ri.FS_ListFiles("scripts", ".shader", &numShaderFiles); if (!shaderFiles || !numShaderFiles) { Ren_Print("----- ScanAndLoadShaderFilesR1 (no files)-----\n"); return 0; } Ren_Print("----- ScanAndLoadShaderFilesR1 (%i files)-----\n", numShaderFiles); if (numShaderFiles >= MAX_SHADER_FILES) { Ren_Drop("MAX_SHADER_FILES limit is reached!"); } // load and parse shader files for (i = 0; i < numShaderFiles; i++) { Com_sprintf(filename, sizeof(filename), "scripts/%s", shaderFiles[i]); COM_BeginParseSession(filename); Ren_Developer("...loading '%s'\n", filename); summand = ri.FS_ReadFile(filename, (void **)&buffers[i]); if (!buffers[i]) { Ren_Drop("Couldn't load %s", filename); // in this case shader file is cought/listed but the file can't be read - drop! } p = buffers[i]; while (1) { token = COM_ParseExt(&p, qtrue); if (!*token) { break; } // Step over the "table"/"guide" and the name if (!Q_stricmp(token, "table") || !Q_stricmp(token, "guide")) { token = COM_ParseExt2(&p, qtrue); if (!*token) { break; } } oldp = p; token = COM_ParseExt2(&p, qtrue); if (token[0] != '{' && token[1] != '\0') { Ren_Warning("WARNING: Bad shader file %s has incorrect syntax near token '%s' line %i\n", filename, token, COM_GetCurrentParseLine()); ri.FS_FreeFile(buffers[i]); buffers[i] = NULL; break; } SkipBracedSection(&oldp); p = oldp; } if (buffers[i]) { sum += summand; } } // build single large buffer s_shaderTextR1 = (char *)ri.Hunk_Alloc(sum + numShaderFiles * 2, h_low); s_shaderTextR1[0] = '\0'; textEnd = s_shaderTextR1; // free in reverse order, so the temp files are all dumped for (i = numShaderFiles - 1; i >= 0 ; i--) { if (!buffers[i]) { continue; } strcat(textEnd, buffers[i]); strcat(textEnd, "\n"); textEnd += strlen(textEnd); ri.FS_FreeFile(buffers[i]); } COM_Compress(s_shaderTextR1); // free up memory ri.FS_FreeFileList(shaderFiles); Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); size = 0; p = s_shaderTextR1; // look for shader names while (1) { token = COM_ParseExt(&p, qtrue); if (token[0] == 0) { break; } // skip shader tables if (!Q_stricmp(token, "table")) { // skip table name (void) COM_ParseExt2(&p, qtrue); SkipBracedSection(&p); } // support shader templates else if (!Q_stricmp(token, "guide")) { // parse shader name token = COM_ParseExt2(&p, qtrue); //Ren_Print("...guided '%s'\n", token); hash = generateHashValue(token, MAX_SHADERTEXT_HASH); shaderTextHashTableSizes[hash]++; size++; // skip guide name token = COM_ParseExt2(&p, qtrue); // skip parameters token = COM_ParseExt2(&p, qtrue); if (Q_stricmp(token, "(")) { Ren_Warning("expected ( found '%s'\n", token); break; } while (1) { token = COM_ParseExt2(&p, qtrue); if (!token[0]) { break; } if (!Q_stricmp(token, ")")) { break; } } if (Q_stricmp(token, ")")) { Ren_Warning("expected ( found '%s'\n", token); break; } } else { hash = generateHashValue(token, MAX_SHADERTEXT_HASH); shaderTextHashTableSizes[hash]++; size++; SkipBracedSection(&p); } } //Ren_Print("Shader hash table size %i\n", size); size += MAX_SHADERTEXT_HASH; hashMem = (char **)ri.Hunk_Alloc(size * sizeof(char *), h_low); for (i = 0; i < MAX_SHADERTEXT_HASH; i++) { shaderTextHashTableR1[i] = hashMem; hashMem += shaderTextHashTableSizes[i] + 1; } Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); p = s_shaderTextR1; // look for shader names while (1) { oldp = p; token = COM_ParseExt(&p, qtrue); if (token[0] == 0) { break; } // parse shader tables if (!Q_stricmp(token, "table")) { int depth; float values[FUNCTABLE_SIZE]; int numValues; shaderTable_t *tb; qboolean alreadyCreated; Com_Memset(&values, 0, sizeof(values)); Com_Memset(&table, 0, sizeof(table)); token = COM_ParseExt2(&p, qtrue); Q_strncpyz(table.name, token, sizeof(table.name)); // check if already created alreadyCreated = qfalse; hash = generateHashValue(table.name, MAX_SHADERTABLE_HASH); for (tb = shaderTableHashTable[hash]; tb; tb = tb->next) { if (Q_stricmp(tb->name, table.name) == 0) { // match found alreadyCreated = qtrue; break; } } depth = 0; numValues = 0; do { token = COM_ParseExt2(&p, qtrue); if (!Q_stricmp(token, "snap")) { table.snap = qtrue; } else if (!Q_stricmp(token, "clamp")) { table.clamp = qtrue; } else if (token[0] == '{') { depth++; } else if (token[0] == '}') { depth--; } else if (token[0] == ',') { continue; } else { if (numValues == FUNCTABLE_SIZE) { Ren_Warning("WARNING: FUNCTABLE_SIZE hit\n"); break; } values[numValues++] = atof(token); } } while (depth && p); if (!alreadyCreated) { Ren_Developer("...generating '%s'\n", table.name); GeneratePermanentShaderTable(values, numValues); } } // support shader templates else if (!Q_stricmp(token, "guide")) { // parse shader name oldp = p; token = COM_ParseExt2(&p, qtrue); //Ren_Print("...guided '%s'\n", token); hash = generateHashValue(token, MAX_SHADERTEXT_HASH); shaderTextHashTableR1[hash][shaderTextHashTableSizes[hash]++] = oldp; // skip guide name token = COM_ParseExt2(&p, qtrue); // skip parameters token = COM_ParseExt2(&p, qtrue); if (Q_stricmp(token, "(")) { Ren_Warning("expected ( found '%s'\n", token); break; } while (1) { token = COM_ParseExt2(&p, qtrue); if (!token[0]) { break; } if (!Q_stricmp(token, ")")) { break; } } if (Q_stricmp(token, ")")) { Ren_Warning("expected ( found '%s'\n", token); break; } } else { hash = generateHashValue(token, MAX_SHADERTEXT_HASH); shaderTextHashTableR1[hash][shaderTextHashTableSizes[hash]++] = oldp; SkipBracedSection(&p); } } return numShaderFiles; }
/** * @brief The current text pointer is at the explicit text definition of the * shader. Parse it into the global shader variable. Later functions * will optimize it. * @param[in,out] _text * @return */ qboolean ParseShaderR1(char *_text) { char **text = &_text; char *token; int s = 0; shader.explicitlyDefined = qtrue; token = COM_ParseExt2(text, qtrue); if (token[0] != '{') { Ren_Warning("WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name); return qfalse; } while (1) { token = COM_ParseExt2(text, qtrue); if (!token[0]) { Ren_Warning("WARNING: no concluding '}' in shader %s\n", shader.name); return qfalse; } // end of shader definition if (token[0] == '}') { break; } // stage definition else if (token[0] == '{') { if (s >= MAX_SHADER_STAGES) { Ren_Warning("WARNING: too many stages in shader %s (max is %i)\n", shader.name, MAX_SHADER_STAGES); return qfalse; } if (!ParseStage(&stages[s], text)) { Ren_Warning("WARNING: can't parse stages of shader %s @[%.50s ...]\n", shader.name, _text); return qfalse; } stages[s].active = qtrue; s++; continue; } // skip stuff that only the QuakeEdRadient needs else if (!Q_stricmpn(token, "qer", 3)) { SkipRestOfLine(text); continue; } // skip description else if (!Q_stricmp(token, "description")) { SkipRestOfLine(text); continue; } // skip renderbump else if (!Q_stricmp(token, "renderbump")) { SkipRestOfLine(text); continue; } // skip unsmoothedTangents else if (!Q_stricmp(token, "unsmoothedTangents")) { Ren_Warning("WARNING: unsmoothedTangents keyword not supported in shader '%s'\n", shader.name); continue; } // skip guiSurf else if (!Q_stricmp(token, "guiSurf")) { SkipRestOfLine(text); continue; } // skip decalInfo else if (!Q_stricmp(token, "decalInfo")) { Ren_Warning("WARNING: decalInfo keyword not supported in shader '%s'\n", shader.name); SkipRestOfLine(text); continue; } // skip Quake4's extra material types else if (!Q_stricmp(token, "materialType")) { Ren_Warning("WARNING: materialType keyword not supported in shader '%s'\n", shader.name); SkipRestOfLine(text); continue; } // skip Prey's extra material types else if (!Q_stricmpn(token, "matter", 6)) { //Ren_Warning( "WARNING: materialType keyword not supported in shader '%s'\n", shader.name); SkipRestOfLine(text); continue; } // sun parms else if (!Q_stricmp(token, "xmap_sun") || !Q_stricmp(token, "q3map_sun")) { float a, b; token = COM_ParseExt2(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name); continue; } tr.sunLight[0] = atof(token); token = COM_ParseExt2(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name); continue; } tr.sunLight[1] = atof(token); token = COM_ParseExt2(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name); continue; } tr.sunLight[2] = atof(token); VectorNormalize(tr.sunLight); token = COM_ParseExt2(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name); continue; } a = atof(token); VectorScale(tr.sunLight, a, tr.sunLight); token = COM_ParseExt2(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name); continue; } a = atof(token); a = a / 180 * M_PI; token = COM_ParseExt2(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name); continue; } b = atof(token); b = b / 180 * M_PI; tr.sunDirection[0] = cos(a) * cos(b); tr.sunDirection[1] = sin(a) * cos(b); tr.sunDirection[2] = sin(b); continue; } // noShadows else if (!Q_stricmp(token, "noShadows")) { shader.noShadows = qtrue; continue; } // noSelfShadow else if (!Q_stricmp(token, "noSelfShadow")) { Ren_Warning("WARNING: noSelfShadow keyword not supported in shader '%s'\n", shader.name); continue; } // forceShadows else if (!Q_stricmp(token, "forceShadows")) { Ren_Warning("WARNING: forceShadows keyword not supported in shader '%s'\n", shader.name); continue; } // forceOverlays else if (!Q_stricmp(token, "forceOverlays")) { Ren_Warning("WARNING: forceOverlays keyword not supported in shader '%s'\n", shader.name); continue; } // noPortalFog else if (!Q_stricmp(token, "noPortalFog")) { Ren_Warning("WARNING: noPortalFog keyword not supported in shader '%s'\n", shader.name); continue; } // fogLight else if (!Q_stricmp(token, "fogLight")) { Ren_Warning("WARNING: fogLight keyword not supported in shader '%s'\n", shader.name); shader.fogLight = qtrue; continue; } // blendLight else if (!Q_stricmp(token, "blendLight")) { Ren_Warning("WARNING: blendLight keyword not supported in shader '%s'\n", shader.name); shader.blendLight = qtrue; continue; } // ambientLight else if (!Q_stricmp(token, "ambientLight")) { Ren_Warning("WARNING: ambientLight keyword not supported in shader '%s'\n", shader.name); shader.ambientLight = qtrue; continue; } // volumetricLight else if (!Q_stricmp(token, "volumetricLight")) { shader.volumetricLight = qtrue; continue; } // translucent else if (!Q_stricmp(token, "translucent")) { shader.translucent = qtrue; continue; } // forceOpaque else if (!Q_stricmp(token, "forceOpaque")) { shader.forceOpaque = qtrue; continue; } // forceSolid else if (!Q_stricmp(token, "forceSolid") || !Q_stricmp(token, "solid")) { continue; } else if (!Q_stricmp(token, "deformVertexes") || !Q_stricmp(token, "deform")) { ParseDeform(text); continue; } else if (!Q_stricmp(token, "tesssize")) { SkipRestOfLine(text); continue; } // skip noFragment if (!Q_stricmp(token, "noFragment")) { continue; } // skip stuff that only the xmap needs else if (!Q_stricmpn(token, "xmap", 4) || !Q_stricmpn(token, "q3map", 5)) { SkipRestOfLine(text); continue; } // skip stuff that only xmap or the server needs else if (!Q_stricmp(token, "surfaceParm")) { ParseSurfaceParm(text); continue; } // no mip maps else if (!Q_stricmp(token, "nomipmap") || !Q_stricmp(token, "nomipmaps")) { shader.filterType = FT_LINEAR; shader.noPicMip = qtrue; continue; } // no picmip adjustment else if (!Q_stricmp(token, "nopicmip")) { shader.noPicMip = qtrue; continue; } // RF, allow each shader to permit compression if available else if (!Q_stricmp(token, "allowcompress")) { shader.uncompressed = qfalse; continue; } else if (!Q_stricmp(token, "nocompress")) { shader.uncompressed = qtrue; continue; } // polygonOffset else if (!Q_stricmp(token, "polygonOffset")) { shader.polygonOffset = qtrue; continue; } // parallax mapping else if (!Q_stricmp(token, "parallax")) { shader.parallax = qtrue; continue; } // entityMergable, allowing sprite surfaces from multiple entities // to be merged into one batch. This is a savings for smoke // puffs and blood, but can't be used for anything where the // shader calcs (not the surface function) reference the entity color or scroll else if (!Q_stricmp(token, "entityMergable")) { shader.entityMergable = qtrue; continue; } // fogParms else if (!Q_stricmp(token, "fogParms")) { if (!ParseVector(text, 3, shader.fogParms.color)) { return qfalse; } //shader.fogParms.colorInt = ColorBytes4(shader.fogParms.color[0] * tr.identityLight, // shader.fogParms.color[1] * tr.identityLight, // shader.fogParms.color[2] * tr.identityLight, 1.0); token = COM_ParseExt2(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: 'fogParms' incomplete - missing opacity value in shader '%s' set to 1\n", shader.name); shader.fogParms.depthForOpaque = 1; } else { shader.fogParms.depthForOpaque = atof(token); shader.fogParms.depthForOpaque = shader.fogParms.depthForOpaque < 1 ? 1 : shader.fogParms.depthForOpaque; } //shader.fogParms.tcScale = 1.0f / shader.fogParms.depthForOpaque; shader.fogVolume = qtrue; shader.sort = SS_FOG; // skip any old gradient directions SkipRestOfLine(text); continue; } // noFog else if (!Q_stricmp(token, "noFog")) { shader.noFog = qtrue; continue; } // portal else if (!Q_stricmp(token, "portal")) { shader.sort = SS_PORTAL; shader.isPortal = qtrue; token = COM_ParseExt2(text, qfalse); if (token[0]) { shader.portalRange = atof(token); } else { shader.portalRange = 256; } continue; } // portal or mirror else if (!Q_stricmp(token, "mirror")) { shader.sort = SS_PORTAL; shader.isPortal = qtrue; continue; } // skyparms <cloudheight> <outerbox> <innerbox> else if (!Q_stricmp(token, "skyparms")) { ParseSkyParms(text); continue; } // This is fixed fog for the skybox/clouds determined solely by the shader // it will not change in a level and will not be necessary // to force clients to use a sky fog the server says to. // skyfogvars <(r,g,b)> <dist> else if (!Q_stricmp(token, "skyfogvars")) { vec3_t fogColor; if (!ParseVector(text, 3, fogColor)) { return qfalse; } token = COM_ParseExt(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing density value for sky fog\n"); continue; } if (atof(token) > 1) { Ren_Warning("WARNING: last value for skyfogvars is 'density' which needs to be 0.0-1.0\n"); continue; } RE_SetFog(FOG_SKY, 0, 5, fogColor[0], fogColor[1], fogColor[2], atof(token)); continue; } // ET waterfogvars else if (!Q_stricmp(token, "waterfogvars")) { vec3_t watercolor; float fogvar; if (!ParseVector(text, 3, watercolor)) { return qfalse; } token = COM_ParseExt(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing density/distance value for water fog\n"); continue; } fogvar = atof(token); // right now allow one water color per map. I'm sure this will need // to change at some point, but I'm not sure how to track fog parameters // on a "per-water volume" basis yet. if (fogvar == 0) { // '0' specifies "use the map values for everything except the fog color // TODO } else if (fogvar > 1) { // distance "linear" fog RE_SetFog(FOG_WATER, 0, fogvar, watercolor[0], watercolor[1], watercolor[2], 1.1); } else { // density "exp" fog RE_SetFog(FOG_WATER, 0, 5, watercolor[0], watercolor[1], watercolor[2], fogvar); } continue; } // ET fogvars else if (!Q_stricmp(token, "fogvars")) { vec3_t fogColor; float fogDensity; int fogFar; if (!ParseVector(text, 3, fogColor)) { return qfalse; } token = COM_ParseExt(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing density value for the fog\n"); continue; } // NOTE: fogFar > 1 means the shader is setting the farclip, < 1 means setting // density (so old maps or maps that just need softening fog don't have to care about farclip) fogDensity = atof(token); if (fogDensity > 1) { // linear fogFar = fogDensity; } else { fogFar = 5; } RE_SetFog(FOG_MAP, 0, fogFar, fogColor[0], fogColor[1], fogColor[2], fogDensity); RE_SetFog(FOG_CMD_SWITCHFOG, FOG_MAP, 50, 0, 0, 0, 0); continue; } // ET sunshader <name> else if (!Q_stricmp(token, "sunshader")) { size_t tokenLen; token = COM_ParseExt2(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing shader name for 'sunshader'\n"); continue; } // Don't call tr.sunShader = R_FindShader(token, SHADER_3D_STATIC, qtrue); // because it breaks the computation of the current shader tokenLen = strlen(token) + 1; tr.sunShaderName = (char *)ri.Hunk_Alloc(sizeof(char) * tokenLen, h_low); Q_strncpyz(tr.sunShaderName, token, tokenLen); } else if (!Q_stricmp(token, "lightgridmulamb")) { // ambient multiplier for lightgrid token = COM_ParseExt2(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing value for 'lightgrid ambient multiplier'\n"); continue; } if (atof(token) > 0) { tr.lightGridMulAmbient = atof(token); } } else if (!Q_stricmp(token, "lightgridmuldir")) { // directional multiplier for lightgrid token = COM_ParseExt2(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing value for 'lightgrid directional multiplier'\n"); continue; } if (atof(token) > 0) { tr.lightGridMulDirected = atof(token); } } // light <value> determines flaring in xmap, not needed here else if (!Q_stricmp(token, "light")) { (void) COM_ParseExt2(text, qfalse); continue; } // cull <face> else if (!Q_stricmp(token, "cull")) { token = COM_ParseExt2(text, qfalse); if (token[0] == 0) { Ren_Warning("WARNING: missing cull parms in shader '%s'\n", shader.name); continue; } if (!Q_stricmp(token, "none") || !Q_stricmp(token, "twoSided") || !Q_stricmp(token, "disable")) { shader.cullType = CT_TWO_SIDED; } else if (!Q_stricmp(token, "back") || !Q_stricmp(token, "backside") || !Q_stricmp(token, "backsided")) { shader.cullType = CT_BACK_SIDED; } else if (!Q_stricmp(token, "front")) { // CT_FRONT_SIDED is set per default see R_FindShader - nothing to do just don't throw a warning } else { Ren_Warning("WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name); } continue; } // distancecull <opaque distance> <transparent distance> <alpha threshold> else if (!Q_stricmp(token, "distancecull")) { int i; for (i = 0; i < 3; i++) { token = COM_ParseExt(text, qfalse); if (token[0] == 0) { Ren_Warning("WARNING: missing distancecull parms in shader '%s'\n", shader.name); } else { shader.distanceCull[i] = atof(token); } } if (shader.distanceCull[1] - shader.distanceCull[0] > 0) { // distanceCull[ 3 ] is an optimization shader.distanceCull[3] = 1.0f / (shader.distanceCull[1] - shader.distanceCull[0]); } else { shader.distanceCull[0] = 0; shader.distanceCull[1] = 0; shader.distanceCull[2] = 0; shader.distanceCull[3] = 0; } continue; } // twoSided else if (!Q_stricmp(token, "twoSided")) { shader.cullType = CT_TWO_SIDED; continue; } // backSided else if (!Q_stricmp(token, "backSided")) { shader.cullType = CT_BACK_SIDED; continue; } // clamp else if (!Q_stricmp(token, "clamp")) { shader.wrapType = WT_CLAMP; continue; } // edgeClamp else if (!Q_stricmp(token, "edgeClamp")) { shader.wrapType = WT_EDGE_CLAMP; continue; } // zeroClamp else if (!Q_stricmp(token, "zeroclamp")) { shader.wrapType = WT_ZERO_CLAMP; continue; } // alphaZeroClamp else if (!Q_stricmp(token, "alphaZeroClamp")) { shader.wrapType = WT_ALPHA_ZERO_CLAMP; continue; } // sort else if (!Q_stricmp(token, "sort")) { ParseSort(text); continue; } // implicit default mapping to eliminate redundant/incorrect explicit shader stages else if (!Q_stricmpn(token, "implicit", 8)) { //Ren_Warning( "WARNING: keyword '%s' not supported in shader '%s'\n", token, shader.name); //SkipRestOfLine(text); // set implicit mapping state if (!Q_stricmp(token, "implicitBlend")) { implicitStateBits = GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; implicitCullType = CT_TWO_SIDED; } else if (!Q_stricmp(token, "implicitMask")) { implicitStateBits = GLS_DEPTHMASK_TRUE | GLS_ATEST_GE_128; implicitCullType = CT_TWO_SIDED; } else // "implicitMap" { implicitStateBits = GLS_DEPTHMASK_TRUE; implicitCullType = CT_FRONT_SIDED; } // get image token = COM_ParseExt(text, qfalse); if (token[0] != '\0') { Q_strncpyz(implicitMap, token, sizeof(implicitMap)); } else { implicitMap[0] = '-'; implicitMap[1] = '\0'; } continue; } // spectrum else if (!Q_stricmp(token, "spectrum")) { Ren_Warning("WARNING: spectrum keyword not supported in shader '%s'\n", shader.name); token = COM_ParseExt2(text, qfalse); if (!token[0]) { Ren_Warning("WARNING: missing parm for 'spectrum' keyword in shader '%s'\n", shader.name); continue; } shader.spectrum = qtrue; shader.spectrumValue = atoi(token); continue; } // diffuseMap <image> else if (!Q_stricmp(token, "diffuseMap")) { ParseDiffuseMap(&stages[s], text); s++; continue; } // normalMap <image> else if (!Q_stricmp(token, "normalMap") || !Q_stricmp(token, "bumpMap")) { ParseNormalMap(&stages[s], text); s++; continue; } // specularMap <image> else if (!Q_stricmp(token, "specularMap")) { ParseSpecularMap(&stages[s], text); s++; continue; } // glowMap <image> else if (!Q_stricmp(token, "glowMap")) { ParseGlowMap(&stages[s], text); s++; continue; } // reflectionMap <image> else if (!Q_stricmp(token, "reflectionMap")) { ParseReflectionMap(&stages[s], text); s++; continue; } // reflectionMapBlended <image> else if (!Q_stricmp(token, "reflectionMapBlended")) { ParseReflectionMapBlended(&stages[s], text); s++; continue; } // lightMap <image> else if (!Q_stricmp(token, "lightMap")) { Ren_Warning("WARNING: obsolete lightMap keyword not supported in shader '%s'\n", shader.name); SkipRestOfLine(text); continue; } // lightFalloffImage <image> else if (!Q_stricmp(token, "lightFalloffImage")) { ParseLightFalloffImage(&stages[s], text); s++; continue; } // Doom 3 DECAL_MACRO else if (!Q_stricmp(token, "DECAL_MACRO")) { shader.polygonOffset = qtrue; shader.sort = SS_DECAL; SurfaceParm("discrete"); SurfaceParm("noShadows"); continue; } // Prey DECAL_ALPHATEST_MACRO else if (!Q_stricmp(token, "DECAL_ALPHATEST_MACRO")) { // what's different? shader.polygonOffset = qtrue; shader.sort = SS_DECAL; SurfaceParm("discrete"); SurfaceParm("noShadows"); continue; } else if (SurfaceParm(token)) { continue; } else { Ren_Warning("WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name); SkipRestOfLine(text); continue; } } // ignore shaders that don't have any stages, unless it is a sky or fog if (s == 0 && !shader.forceOpaque && !shader.isSky && !(shader.contentFlags & CONTENTS_FOG) && implicitMap[0] == '\0') { return qfalse; } return qtrue; }
/** * @brief Scans the combined text description of all the shader files for * the given shader name. * @param[in] shaderName * @return NULL if not found, otherwise it will return a valid shader */ char *FindShaderInShaderTextR1(const char *shaderName) { char *token, *p; int i, hash; hash = generateHashValue(shaderName, MAX_SHADERTEXT_HASH); for (i = 0; shaderTextHashTableR1[hash][i]; i++) { p = shaderTextHashTableR1[hash][i]; token = COM_ParseExt2(&p, qtrue); if (!Q_stricmp(token, shaderName)) { //Ren_Print("found shader '%s' by hashing\n", shaderName); return p; } } p = s_shaderTextR1; if (!p) { return NULL; } // look for label while (1) { token = COM_ParseExt2(&p, qtrue); if (token[0] == 0) { break; } if (!Q_stricmp(token, shaderName)) { //Ren_Print("found shader '%s' by linear search\n", shaderName); return p; } // skip shader tables else if (!Q_stricmp(token, "table")) { // skip table name (void) COM_ParseExt2(&p, qtrue); SkipBracedSection(&p); } // support shader templates else if (!Q_stricmp(token, "guide")) { // parse shader name token = COM_ParseExt2(&p, qtrue); if (!Q_stricmp(token, shaderName)) { Ren_Print("found shader '%s' by linear search\n", shaderName); return p; } // skip guide name token = COM_ParseExt2(&p, qtrue); // skip parameters token = COM_ParseExt2(&p, qtrue); if (Q_stricmp(token, "(")) { break; } while (1) { token = COM_ParseExt2(&p, qtrue); if (!token[0]) { break; } if (!Q_stricmp(token, ")")) { break; } } if (Q_stricmp(token, ")")) { break; } } else { // skip the shader body SkipBracedSection(&p); } } return NULL; }
/* ================= R_LoadMD5 ================= */ qboolean R_LoadMD5( model_t *mod, void *buffer, int bufferSize, const char *modName ) { int i, j, k; md5Model_t *md5; md5Bone_t *bone; md5Surface_t *surf; md5Triangle_t *tri; md5Vertex_t *v; md5Weight_t *weight; int version; shader_t *sh; char *buf_p; char *token; vec3_t boneOrigin; quat_t boneQuat; matrix_t boneMat; buf_p = ( char * ) buffer; // skip MD5Version indent string COM_ParseExt2( &buf_p, qfalse ); // check version token = COM_ParseExt2( &buf_p, qfalse ); version = atoi( token ); if ( version != MD5_VERSION ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: %s has wrong version (%i should be %i)\n", modName, version, MD5_VERSION ); return qfalse; } mod->type = MOD_MD5; mod->dataSize += sizeof( md5Model_t ); md5 = mod->model.md5 = ri.Hunk_Alloc( sizeof( md5Model_t ), h_low ); // skip commandline <arguments string> token = COM_ParseExt2( &buf_p, qtrue ); token = COM_ParseExt2( &buf_p, qtrue ); // ri.Printf(PRINT_ALL, "%s\n", token); // parse numJoints <number> token = COM_ParseExt2( &buf_p, qtrue ); if ( Q_stricmp( token, "numJoints" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'numJoints' found '%s' in model '%s'\n", token, modName ); return qfalse; } token = COM_ParseExt2( &buf_p, qfalse ); md5->numBones = atoi( token ); // parse numMeshes <number> token = COM_ParseExt2( &buf_p, qtrue ); if ( Q_stricmp( token, "numMeshes" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'numMeshes' found '%s' in model '%s'\n", token, modName ); return qfalse; } token = COM_ParseExt2( &buf_p, qfalse ); md5->numSurfaces = atoi( token ); //ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' has %i surfaces\n", modName, md5->numSurfaces); if ( md5->numBones < 1 ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: '%s' has no bones\n", modName ); return qfalse; } if ( md5->numBones > MAX_BONES ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: '%s' has more than %i bones (%i)\n", modName, MAX_BONES, md5->numBones ); return qfalse; } //ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' has %i bones\n", modName, md5->numBones); // parse all the bones md5->bones = ri.Hunk_Alloc( sizeof( *bone ) * md5->numBones, h_low ); // parse joints { token = COM_ParseExt2( &buf_p, qtrue ); if ( Q_stricmp( token, "joints" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'joints' found '%s' in model '%s'\n", token, modName ); return qfalse; } token = COM_ParseExt2( &buf_p, qfalse ); if ( Q_stricmp( token, "{" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '{' found '%s' in model '%s'\n", token, modName ); return qfalse; } for ( i = 0, bone = md5->bones; i < md5->numBones; i++, bone++ ) { token = COM_ParseExt2( &buf_p, qtrue ); Q_strncpyz( bone->name, token, sizeof( bone->name ) ); //ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' has bone '%s'\n", modName, bone->name); token = COM_ParseExt2( &buf_p, qfalse ); bone->parentIndex = atoi( token ); //ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' has bone '%s' with parent index %i\n", modName, bone->name, bone->parentIndex); if ( bone->parentIndex >= md5->numBones ) { ri.Error( ERR_DROP, "R_LoadMD5: '%s' has bone '%s' with bad parent index %i while numBones is %i\n", modName, bone->name, bone->parentIndex, md5->numBones ); } // skip ( token = COM_ParseExt2( &buf_p, qfalse ); if ( Q_stricmp( token, "(" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName ); return qfalse; } for ( j = 0; j < 3; j++ ) { token = COM_ParseExt2( &buf_p, qfalse ); boneOrigin[ j ] = atof( token ); } // skip ) token = COM_ParseExt2( &buf_p, qfalse ); if ( Q_stricmp( token, ")" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected ')' found '%s' in model '%s'\n", token, modName ); return qfalse; } // skip ( token = COM_ParseExt2( &buf_p, qfalse ); if ( Q_stricmp( token, "(" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName ); return qfalse; } for ( j = 0; j < 3; j++ ) { token = COM_ParseExt2( &buf_p, qfalse ); boneQuat[ j ] = atof( token ); } QuatCalcW( boneQuat ); MatrixFromQuat( boneMat, boneQuat ); VectorCopy( boneOrigin, bone->origin ); QuatCopy( boneQuat, bone->rotation ); MatrixSetupTransformFromQuat( bone->inverseTransform, boneQuat, boneOrigin ); MatrixInverse( bone->inverseTransform ); // skip ) token = COM_ParseExt2( &buf_p, qfalse ); if ( Q_stricmp( token, ")" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName ); return qfalse; } } // parse } token = COM_ParseExt2( &buf_p, qtrue ); if ( Q_stricmp( token, "}" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '}' found '%s' in model '%s'\n", token, modName ); return qfalse; } // parse all the surfaces if ( md5->numSurfaces < 1 ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: '%s' has no surfaces\n", modName ); return qfalse; } //ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' has %i surfaces\n", modName, md5->numSurfaces); md5->surfaces = ri.Hunk_Alloc( sizeof( *surf ) * md5->numSurfaces, h_low ); for ( i = 0, surf = md5->surfaces; i < md5->numSurfaces; i++, surf++ ) { // parse mesh { token = COM_ParseExt2( &buf_p, qtrue ); if ( Q_stricmp( token, "mesh" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'mesh' found '%s' in model '%s'\n", token, modName ); return qfalse; } token = COM_ParseExt2( &buf_p, qfalse ); if ( Q_stricmp( token, "{" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '{' found '%s' in model '%s'\n", token, modName ); return qfalse; } // change to surface identifier surf->surfaceType = SF_MD5; // give pointer to model for Tess_SurfaceMD5 surf->model = md5; // parse shader <name> token = COM_ParseExt2( &buf_p, qtrue ); if ( Q_stricmp( token, "shader" ) ) { Q_strncpyz( surf->shader, "<default>", sizeof( surf->shader ) ); surf->shaderIndex = 0; } else { token = COM_ParseExt2( &buf_p, qfalse ); Q_strncpyz( surf->shader, token, sizeof( surf->shader ) ); //ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' uses shader '%s'\n", modName, surf->shader); // FIXME .md5mesh meshes don't have surface names // lowercase the surface name so skin compares are faster //Q_strlwr(surf->name); //ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' has surface '%s'\n", modName, surf->name); // register the shaders sh = R_FindShader( surf->shader, LIGHTMAP_NONE, qtrue ); if ( sh->defaultShader ) { surf->shaderIndex = 0; } else { surf->shaderIndex = sh->index; } token = COM_ParseExt2( &buf_p, qtrue ); } // parse numVerts <number> if ( Q_stricmp( token, "numVerts" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'numVerts' found '%s' in model '%s'\n", token, modName ); return qfalse; } token = COM_ParseExt2( &buf_p, qfalse ); surf->numVerts = atoi( token ); if ( surf->numVerts > SHADER_MAX_VERTEXES ) { ri.Error( ERR_DROP, "R_LoadMD5: '%s' has more than %i verts on a surface (%i)", modName, SHADER_MAX_VERTEXES, surf->numVerts ); } surf->verts = ri.Hunk_Alloc( sizeof( *v ) * surf->numVerts, h_low ); for ( j = 0, v = surf->verts; j < surf->numVerts; j++, v++ ) { // skip vert <number> token = COM_ParseExt2( &buf_p, qtrue ); if ( Q_stricmp( token, "vert" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'vert' found '%s' in model '%s'\n", token, modName ); return qfalse; } COM_ParseExt2( &buf_p, qfalse ); // skip ( token = COM_ParseExt2( &buf_p, qfalse ); if ( Q_stricmp( token, "(" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName ); return qfalse; } for ( k = 0; k < 2; k++ ) { token = COM_ParseExt2( &buf_p, qfalse ); v->texCoords[ k ] = atof( token ); } // skip ) token = COM_ParseExt2( &buf_p, qfalse ); if ( Q_stricmp( token, ")" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected ')' found '%s' in model '%s'\n", token, modName ); return qfalse; } token = COM_ParseExt2( &buf_p, qfalse ); v->firstWeight = atoi( token ); token = COM_ParseExt2( &buf_p, qfalse ); v->numWeights = atoi( token ); if ( v->numWeights > MAX_WEIGHTS ) { ri.Error( ERR_DROP, "R_LoadMD5: vertex %i requires more than %i weights on surface (%i) in model '%s'", j, MAX_WEIGHTS, i, modName ); } } // parse numTris <number> token = COM_ParseExt2( &buf_p, qtrue ); if ( Q_stricmp( token, "numTris" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'numTris' found '%s' in model '%s'\n", token, modName ); return qfalse; } token = COM_ParseExt2( &buf_p, qfalse ); surf->numTriangles = atoi( token ); if ( surf->numTriangles > SHADER_MAX_TRIANGLES ) { ri.Error( ERR_DROP, "R_LoadMD5: '%s' has more than %i triangles on a surface (%i)", modName, SHADER_MAX_TRIANGLES, surf->numTriangles ); } surf->triangles = ri.Hunk_Alloc( sizeof( *tri ) * surf->numTriangles, h_low ); for ( j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++ ) { // skip tri <number> token = COM_ParseExt2( &buf_p, qtrue ); if ( Q_stricmp( token, "tri" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'tri' found '%s' in model '%s'\n", token, modName ); return qfalse; } COM_ParseExt2( &buf_p, qfalse ); for ( k = 0; k < 3; k++ ) { token = COM_ParseExt2( &buf_p, qfalse ); tri->indexes[ k ] = atoi( token ); } } // parse numWeights <number> token = COM_ParseExt2( &buf_p, qtrue ); if ( Q_stricmp( token, "numWeights" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'numWeights' found '%s' in model '%s'\n", token, modName ); return qfalse; } token = COM_ParseExt2( &buf_p, qfalse ); surf->numWeights = atoi( token ); surf->weights = ri.Hunk_Alloc( sizeof( *weight ) * surf->numWeights, h_low ); for ( j = 0, weight = surf->weights; j < surf->numWeights; j++, weight++ ) { // skip weight <number> token = COM_ParseExt2( &buf_p, qtrue ); if ( Q_stricmp( token, "weight" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'weight' found '%s' in model '%s'\n", token, modName ); return qfalse; } COM_ParseExt2( &buf_p, qfalse ); token = COM_ParseExt2( &buf_p, qfalse ); weight->boneIndex = atoi( token ); token = COM_ParseExt2( &buf_p, qfalse ); weight->boneWeight = atof( token ); // skip ( token = COM_ParseExt2( &buf_p, qfalse ); if ( Q_stricmp( token, "(" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName ); return qfalse; } for ( k = 0; k < 3; k++ ) { token = COM_ParseExt2( &buf_p, qfalse ); weight->offset[ k ] = atof( token ); } // skip ) token = COM_ParseExt2( &buf_p, qfalse ); if ( Q_stricmp( token, ")" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected ')' found '%s' in model '%s'\n", token, modName ); return qfalse; } } // parse } token = COM_ParseExt2( &buf_p, qtrue ); if ( Q_stricmp( token, "}" ) ) { ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '}' found '%s' in model '%s'\n", token, modName ); return qfalse; } // loop trough all vertices and set up the vertex weights for ( j = 0, v = surf->verts; j < surf->numVerts; j++, v++ ) { v->weights = ri.Hunk_Alloc( sizeof( *v->weights ) * v->numWeights, h_low ); for ( k = 0; k < v->numWeights; k++ ) { v->weights[ k ] = surf->weights + ( v->firstWeight + k ); } } } // loading is done now calculate the bounding box and tangent spaces ClearBounds( md5->bounds[ 0 ], md5->bounds[ 1 ] ); for ( i = 0, surf = md5->surfaces; i < md5->numSurfaces; i++, surf++ ) { for ( j = 0, v = surf->verts; j < surf->numVerts; j++, v++ ) { vec3_t tmpVert; md5Weight_t *w; VectorClear( tmpVert ); for ( k = 0, w = v->weights[ 0 ]; k < v->numWeights; k++, w++ ) { vec3_t offsetVec; bone = &md5->bones[ w->boneIndex ]; QuatTransformVector( bone->rotation, w->offset, offsetVec ); VectorAdd( bone->origin, offsetVec, offsetVec ); VectorMA( tmpVert, w->boneWeight, offsetVec, tmpVert ); } VectorCopy( tmpVert, v->position ); AddPointToBounds( tmpVert, md5->bounds[ 0 ], md5->bounds[ 1 ] ); } // calc normals { const float *v0, *v1, *v2; const float *t0, *t1, *t2; vec3_t normal; for ( j = 0, v = surf->verts; j < surf->numVerts; j++, v++ ) { VectorClear( v->tangent ); VectorClear( v->binormal ); VectorClear( v->normal ); } for ( j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++ ) { v0 = surf->verts[ tri->indexes[ 0 ] ].position; v1 = surf->verts[ tri->indexes[ 1 ] ].position; v2 = surf->verts[ tri->indexes[ 2 ] ].position; t0 = surf->verts[ tri->indexes[ 0 ] ].texCoords; t1 = surf->verts[ tri->indexes[ 1 ] ].texCoords; t2 = surf->verts[ tri->indexes[ 2 ] ].texCoords; R_CalcNormalForTriangle( normal, v0, v1, v2 ); for ( k = 0; k < 3; k++ ) { float *v; v = surf->verts[ tri->indexes[ k ] ].normal; VectorAdd( v, normal, v ); } } for ( j = 0, v = surf->verts; j < surf->numVerts; j++, v++ ) { VectorNormalize( v->normal ); } } #if 0 // do another extra smoothing for normals to avoid flat shading for ( j = 0; j < surf->numVerts; j++ ) { for ( k = 0; k < surf->numVerts; k++ ) { if ( j == k ) { continue; } if ( VectorCompare( surf->verts[ j ].position, surf->verts[ k ].position ) ) { VectorAdd( surf->verts[ j ].normal, surf->verts[ k ].normal, surf->verts[ j ].normal ); } } VectorNormalize( surf->verts[ j ].normal ); } #endif } return qtrue; }