/* Copyright (C) 2003-2006 Andrey Nazarov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include "gl.h" typedef struct { unsigned oldframenum; unsigned newframenum; float frontlerp; float backlerp; vec3_t origin; vec3_t oldscale; vec3_t newscale; vec3_t translate; vec_t shellscale; vec4_t color; const vec_t *shadelight; vec3_t shadedir; float celscale; mat4_t shadowmatrix; } tess_info_t; static void setup_dotshading(tess_info_t *t) { float cp, cy, sp, sy; vec_t yaw; if (!gl_dotshading->integer) return; if (glr.ent->flags & RF_SHELL_MASK) return; t->shadelight = t->color; // matches the anormtab.h precalculations yaw = -DEG2RAD(glr.ent->angles[YAW]); cy = cos(yaw); sy = sin(yaw); cp = cos(-M_PI / 4); sp = sin(-M_PI / 4); t->shadedir[0] = cp * cy; t->shadedir[1] = cp * sy; t->shadedir[2] = -sp; } static inline vec_t shadedot(const tess_info_t *t, const vec_t *normal) { vec_t d = DotProduct(normal, t->shadedir); // matches the anormtab.h precalculations if (d < 0) { d *= 0.3f; } return d + 1; } static inline vec_t *get_static_normal(vec_t *normal, const maliasvert_t *vert) { unsigned int lat = vert->norm[0]; unsigned int lng = vert->norm[1]; normal[0] = TAB_SIN(lat) * TAB_COS(lng); normal[1] = TAB_SIN(lat) * TAB_SIN(lng); normal[2] = TAB_COS(lat); return normal; } static inline vec_t *get_lerped_normal(const tess_info_t *t, vec_t *normal, const maliasvert_t *oldvert, const maliasvert_t *newvert) { vec3_t oldnorm, newnorm; get_static_normal(oldnorm, oldvert); get_static_normal(newnorm, newvert); LerpVector2(oldnorm, newnorm, t->backlerp, t->frontlerp, normal); VectorNormalize(normal); return normal; } static void tess_static_shell(const tess_info_t *t, const maliasmesh_t *mesh) { maliasvert_t *src_vert = &mesh->verts[t->newframenum * mesh->numverts]; vec_t *dst_vert = tess.vertices; int count = mesh->numverts; vec3_t normal; while (count--) { get_static_normal(normal, src_vert); VectorCopy(t->translate, dst_vert); VectorScaleAcc(normal, t->shellscale, dst_vert); VectorVectorScaleAcc(src_vert->pos, t->newscale, dst_vert); dst_vert += 4; src_vert++; } } static void tess_static_shade(const tess_info_t *t, const maliasmesh_t *mesh) { maliasvert_t *src_vert = &mesh->verts[t->newframenum * mesh->numverts]; vec_t *dst_vert = tess.vertices; int count = mesh->numverts; vec3_t normal; vec_t d; while (count--) { get_static_normal(normal, src_vert); d = shadedot(t, normal); VectorCopy(t->translate, dst_vert); VectorVectorScaleAcc(src_vert->pos, t->newscale, dst_vert); Vector4Copy(t->shadelight, dst_vert + 4); VectorScale(dst_vert + 4, d, dst_vert + 4); dst_vert += VERTEX_SIZE; src_vert++; } } static void tess_static_plain(const tess_info_t *t, const maliasmesh_t *mesh) { maliasvert_t *src_vert = &mesh->verts[t->newframenum * mesh->numverts]; vec_t *dst_vert = tess.vertices; int count = mesh->numverts; while (count--) { VectorCopy(t->translate, dst_vert); VectorVectorScaleAcc(src_vert->pos, t->newscale, dst_vert); dst_vert += 4; src_vert++; } } static void tess_lerped_shell(const tess_info_t *t, const maliasmesh_t *mesh) { maliasvert_t *src_oldvert = &mesh->verts[t->oldframenum * mesh->numverts]; maliasvert_t *src_newvert = &mesh->verts[t->newframenum * mesh->numverts]; vec_t *dst_vert = tess.vertices; int count = mesh->numverts; vec3_t normal; while (count--) { get_lerped_normal(t, normal, src_oldvert, src_newvert); VectorCopy(t->translate, dst_vert); VectorVectorScaleAcc(src_oldvert->pos, t->oldscale, dst_vert); VectorVectorScaleAcc(src_newvert->pos, t->newscale, dst_vert); VectorScaleAcc(normal, t->shellscale, dst_vert); dst_vert += 4; src_oldvert++; src_newvert++; } } static void tess_lerped_shade(const tess_info_t *t, const maliasmesh_t *mesh) { maliasvert_t *src_oldvert = &mesh->verts[t->oldframenum * mesh->numverts]; maliasvert_t *src_newvert = &mesh->verts[t->newframenum * mesh->numverts]; vec_t *dst_vert = tess.vertices; int count = mesh->numverts; vec3_t normal; vec_t d; while (count--) { get_lerped_normal(t, normal, src_oldvert, src_newvert); d = shadedot(t, normal); VectorCopy(t->translate, dst_vert); VectorVectorScaleAcc(src_oldvert->pos, t->oldscale, dst_vert); VectorVectorScaleAcc(src_newvert->pos, t->newscale, dst_vert); Vector4Copy(t->shadelight, dst_vert + 4); VectorScale(dst_vert + 4, d, dst_vert + 4); dst_vert += VERTEX_SIZE; src_oldvert++; src_newvert++; } } static void tess_lerped_plain(const tess_info_t *t, const maliasmesh_t *mesh) { maliasvert_t *src_oldvert = &mesh->verts[t->oldframenum * mesh->numverts]; maliasvert_t *src_newvert = &mesh->verts[t->newframenum * mesh->numverts]; vec_t *dst_vert = tess.vertices; int count = mesh->numverts; while (count--) { VectorCopy(t->translate, dst_vert); VectorVectorScaleAcc(src_oldvert->pos, t->oldscale, dst_vert); VectorVectorScaleAcc(src_newvert->pos, t->newscale, dst_vert); dst_vert += 4; src_oldvert++; src_newvert++; } } static glCullResult_t cull_static_model(tess_info_t *t, const model_t *model) { maliasframe_t *newframe = &model->frames[t->newframenum]; vec3_t bounds[2]; glCullResult_t cull; if (glr.entrotated) { cull = GL_CullSphere(t->origin, newframe->radius); if (cull == CULL_OUT) { c.spheresCulled++; return cull; } if (cull == CULL_CLIP) { cull = GL_CullLocalBox(t->origin, newframe->bounds); if (cull == CULL_OUT) { c.rotatedBoxesCulled++; return cull; } } } else { VectorAdd(newframe->bounds[0], t->origin, bounds[0]); VectorAdd(newframe->bounds[1], t->origin, bounds[1]); cull = GL_CullBox(bounds); if (cull == CULL_OUT) { c.boxesCulled++; return cull; } } VectorCopy(newframe->scale, t->newscale); VectorCopy(newframe->translate, t->translate); return cull; } static glCullResult_t cull_lerped_model(tess_info_t *t, const model_t *model) { maliasframe_t *newframe = &model->frames[t->newframenum]; maliasframe_t *oldframe = &model->frames[t->oldframenum]; vec3_t bounds[2]; vec_t radius; glCullResult_t cull; if (glr.entrotated) { radius = max(newframe->radius, oldframe->radius); cull = GL_CullSphere(t->origin, radius); if (cull == CULL_OUT) { c.spheresCulled++; return cull; } UnionBounds(newframe->bounds, oldframe->bounds, bounds); if (cull == CULL_CLIP) { cull = GL_CullLocalBox(t->origin, bounds); if (cull == CULL_OUT) { c.rotatedBoxesCulled++; return cull; } } } else { UnionBounds(newframe->bounds, oldframe->bounds, bounds); VectorAdd(bounds[0], t->origin, bounds[0]); VectorAdd(bounds[1], t->origin, bounds[1]); cull = GL_CullBox(bounds); if (cull == CULL_OUT) { c.boxesCulled++; return cull; } } VectorScale(oldframe->scale, t->backlerp, t->oldscale); VectorScale(newframe->scale, t->frontlerp, t->newscale); LerpVector2(oldframe->translate, newframe->translate, t->backlerp, t->frontlerp, t->translate); return cull; } /* sets t->color */ static void setup_color(tess_info_t *t) { int flags = glr.ent->flags; float f, m; int i; if (flags & RF_SHELL_MASK) { VectorClear(t->color); if (flags & RF_SHELL_HALF_DAM) VectorSet(t->color, 0.56f, 0.59f, 0.45f); if (flags & RF_SHELL_DOUBLE) { t->color[0] = 0.9f; t->color[1] = 0.7f; } if (flags & RF_SHELL_RED) t->color[0] = 1; if (flags & RF_SHELL_GREEN) t->color[1] = 1; if (flags & RF_SHELL_BLUE) t->color[2] = 1; } else if (flags & RF_FULLBRIGHT) { VectorSet(t->color, 1, 1, 1); } else if ((flags & RF_IR_VISIBLE) && (glr.fd.rdflags & RDF_IRGOGGLES)) { VectorSet(t->color, 1, 0, 0); } else { GL_LightPoint(t->origin, t->color); if (flags & RF_MINLIGHT) { for (i = 0; i < 3; i++) { if (t->color[i] <= 0.1f) { VectorSet(t->color, 0.1f, 0.1f, 0.1f); break; } } } if (flags & RF_GLOW) { f = 0.1f * sin(glr.fd.time * 7); for (i = 0; i < 3; i++) { m = t->color[i] * 0.8f; t->color[i] += f; if (t->color[i] < m) t->color[i] = m; } } for (i = 0; i < 3; i++) clamp(t->color[i], 0, 1); } if (flags & RF_TRANSLUCENT) { t->color[3] = glr.ent->alpha; } else { t->color[3] = 1; } } /* sets t->celscale */ static void setup_celshading(tess_info_t *t) { float value = Cvar_ClampValue(gl_celshading, 0, 10); vec3_t dir; if (value == 0) return; if (glr.ent->flags & (RF_TRANSLUCENT | RF_SHELL_MASK)) return; VectorSubtract(t->origin, glr.fd.vieworg, dir); t->celscale = 1.0f - VectorLength(dir) / 700.0f; } static void draw_celshading(const tess_info_t *t, const maliasmesh_t *mesh) { if (t->celscale < 0.01f || t->celscale > 1) return; GL_BindTexture(0, TEXNUM_BLACK); GL_StateBits(GLS_BLEND_BLEND); GL_ArrayBits(GLA_VERTEX); qglLineWidth(gl_celshading->value * t->celscale); qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE); qglCullFace(GL_FRONT); qglColor4f(0, 0, 0, t->color[3] * t->celscale); qglDrawElements(GL_TRIANGLES, mesh->numindices, QGL_INDEX_ENUM, mesh->indices); qglCullFace(GL_BACK); qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL); qglLineWidth(1); } static void setup_shadow(tess_info_t *t) { mat4_t m, tmp; cplane_t *plane; vec3_t dir; t->shadowmatrix.i[3][3] = 0; if (!gl_shadows->integer) return; if (glr.ent->flags & (RF_WEAPONMODEL | RF_NOSHADOW)) return; if (!glr.lightpoint.surf) return; // position fake light source straight over the model if (glr.lightpoint.surf->drawflags & DSURF_PLANEBACK) VectorSet(dir, 0, 0, -1); else VectorSet(dir, 0, 0, 1); // project shadow on ground plane plane = &glr.lightpoint.plane; /* XXX what operation is this? */ m.i[0][0] = plane->normal[1] * dir[1] + plane->normal[2] * dir[2]; m.i[1][0] = -plane->normal[1] * dir[0]; m.i[2][0] = -plane->normal[2] * dir[0]; m.i[3][0] = plane->dist * dir[0]; m.i[0][1] = -plane->normal[0] * dir[1]; m.i[1][1] = plane->normal[0] * dir[0] + plane->normal[2] * dir[2]; m.i[2][1] = -plane->normal[2] * dir[1]; m.i[3][1] = plane->dist * dir[1]; m.i[0][2] = -plane->normal[0] * dir[2]; m.i[1][2] = -plane->normal[1] * dir[2]; m.i[2][2] = plane->normal[0] * dir[0] + plane->normal[1] * dir[1]; m.i[3][2] = plane->dist * dir[2]; m.i[0][3] = 0; m.i[1][3] = 0; m.i[2][3] = 0; m.i[3][3] = DotProduct(plane->normal, dir); tmp = mul_mat4(&glr.viewmatrix, &m); // rotate for entity m = AffineMatrix(glr.entaxis, t->origin); t->shadowmatrix = mul_mat4(&tmp, &m); } static void draw_shadow(const tess_info_t *t, const maliasmesh_t *mesh) { if (t->shadowmatrix.i[3][3] < 0.5f) return; // load shadow projection matrix GL_LoadMatrix(&t->shadowmatrix); // eliminate z-fighting by utilizing stencil buffer, if available if (gl_config.stencilbits) { qglEnable(GL_STENCIL_TEST); qglStencilFunc(GL_EQUAL, 0, 0xff); qglStencilOp(GL_KEEP, GL_KEEP, GL_INCR); } GL_StateBits(GLS_BLEND_BLEND); GL_BindTexture(0, TEXNUM_WHITE); GL_ArrayBits(GLA_VERTEX); qglEnable(GL_POLYGON_OFFSET_FILL); qglPolygonOffset(-1.0f, -2.0f); qglColor4f(0, 0, 0, t->color[3] * 0.5f); qglDrawElements(GL_TRIANGLES, mesh->numindices, QGL_INDEX_ENUM, mesh->indices); qglDisable(GL_POLYGON_OFFSET_FILL); // once we have drawn something to stencil buffer, continue to clear it for // the lifetime of OpenGL context. leaving stencil buffer "dirty" and // clearing just depth is slower (verified for Nvidia and ATI drivers). if (gl_config.stencilbits) { qglDisable(GL_STENCIL_TEST); gl_static.stencil_buffer_bit |= GL_STENCIL_BUFFER_BIT; } } static int texnum_for_mesh(const maliasmesh_t *mesh) { entity_t *ent = glr.ent; if (ent->flags & RF_SHELL_MASK) return TEXNUM_WHITE; if (ent->skin) return IMG_ForHandle(ent->skin)->texnum; if (!mesh->numskins) return TEXNUM_DEFAULT; if (ent->skinnum < 0 || ent->skinnum >= mesh->numskins) { Com_DPrintf("%s: no such skin: %d\n", "GL_DrawAliasModel", ent->skinnum); return mesh->skins[0]->texnum; } if (mesh->skins[ent->skinnum]->texnum == TEXNUM_DEFAULT) return mesh->skins[0]->texnum; return mesh->skins[ent->skinnum]->texnum; } typedef void (*tessfunc_t)(const tess_info_t *, const maliasmesh_t *); static void draw_alias_mesh(const tess_info_t *t, tessfunc_t tessfunc, const maliasmesh_t *mesh) { glStateBits_t state = GLS_DEFAULT; // fall back to entity matrix GL_LoadMatrix(&glr.entmatrix); if (t->shadelight) state |= GLS_SHADE_SMOOTH; if (glr.ent->flags & RF_TRANSLUCENT) state |= GLS_BLEND_BLEND | GLS_DEPTHMASK_FALSE; GL_StateBits(state); GL_BindTexture(0, texnum_for_mesh(mesh)); tessfunc(t, mesh); c.trisDrawn += mesh->numtris; if (t->shadelight) { GL_ArrayBits(GLA_VERTEX | GLA_TC | GLA_COLOR); GL_VertexPointer(3, VERTEX_SIZE, tess.vertices); GL_ColorFloatPointer(4, VERTEX_SIZE, tess.vertices + 4); } else { GL_ArrayBits(GLA_VERTEX | GLA_TC); GL_VertexPointer(3, 4, tess.vertices); qglColor4fv(t->color); } GL_TexCoordPointer(2, 0, (GLfloat *) mesh->tcoords); GL_LockArrays(mesh->numverts); qglDrawElements(GL_TRIANGLES, mesh->numindices, QGL_INDEX_ENUM, mesh->indices); draw_celshading(t, mesh); if (gl_showtris->integer) { GL_EnableOutlines(); qglDrawElements(GL_TRIANGLES, mesh->numindices, QGL_INDEX_ENUM, mesh->indices); GL_DisableOutlines(); } // FIXME: unlock arrays before changing matrix? draw_shadow(t, mesh); GL_UnlockArrays(); } void GL_DrawAliasModel(model_t *model) { entity_t *ent = glr.ent; tessfunc_t tessfunc; glCullResult_t cull; int i, do_lerp; tess_info_t t = { .oldframenum = ent->oldframe, .newframenum = ent->frame, .frontlerp = 1.0f - ent->backlerp, .backlerp = ent->backlerp, .shadelight = NULL, .celscale = 0, }; #if 0 if (t.newframenum >= model->numframes) { Com_DPrintf("%s: no such frame %d\n", __func__, t.newframenum); t.newframenum = 0; } if (t.oldframenum >= model->numframes) { Com_DPrintf("%s: no such oldframe %d\n", __func__, t.oldframenum); t.oldframenum = 0; } #else assert(t.oldframenum <= model->numframes); assert(t.newframenum <= model->numframes); #endif // optimized case if (t.backlerp == 0) t.oldframenum = t.newframenum; do_lerp = t.newframenum != t.oldframenum; // interpolate origin, if necessarry if (ent->flags & RF_FRAMELERP) LerpVector2(ent->oldorigin, ent->origin, t.backlerp, t.frontlerp, t.origin); else VectorCopy(ent->origin, t.origin); // cull the model, setup scale and translate vectors cull = do_lerp ? cull_lerped_model(&t, model) : cull_static_model(&t, model); if (cull == CULL_OUT) return; memset(&glr.lightpoint, 0, sizeof(glr.lightpoint)); // setup parameters common for all meshes setup_color(&t); setup_celshading(&t); setup_dotshading(&t); setup_shadow(&t); // select proper tessfunc if (ent->flags & RF_SHELL_MASK) { t.shellscale = (ent->flags & RF_WEAPONMODEL) ? WEAPONSHELL_SCALE : POWERSUIT_SCALE; tessfunc = do_lerp ? tess_lerped_shell : tess_static_shell; } else if (t.shadelight) { tessfunc = do_lerp ? tess_lerped_shade : tess_static_shade; } else { tessfunc = do_lerp ? tess_lerped_plain : tess_static_plain; } GL_RotateForEntity(t.origin); if ((ent->flags & (RF_WEAPONMODEL | RF_LEFTHAND)) == (RF_WEAPONMODEL | RF_LEFTHAND)) { qglMatrixMode(GL_PROJECTION); qglScalef(-1, 1, 1); qglMatrixMode(GL_MODELVIEW); qglFrontFace(GL_CCW); } if (ent->flags & RF_DEPTHHACK) qglDepthRange(0, 0.25f); // draw all the meshes for (i = 0; i < model->nummeshes; i++) draw_alias_mesh(&t, tessfunc, &model->meshes[i]); if (ent->flags & RF_DEPTHHACK) qglDepthRange(0, 1); if ((ent->flags & (RF_WEAPONMODEL | RF_LEFTHAND)) == (RF_WEAPONMODEL | RF_LEFTHAND)) { qglMatrixMode(GL_PROJECTION); qglScalef(-1, 1, 1); qglMatrixMode(GL_MODELVIEW); qglFrontFace(GL_CW); } }